Use package:record_use

- Rename resource identifiers in the VM to usage recordings.
- Use package:record_use for serialization.
- Rename and use the experimental flag for this feature.
- Recognize tear-offs and top-level methods as well.

Next steps:

- Add constant instance recording.
- Expose API in package:native_assets_cli's link callback.

TEST=pkg/vm/test/transformations/record_use_test.dart

Change-Id: I8af3625165f78925ae943711245af93a239d1012
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/383040
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Moritz Sümmermann <mosum@google.com>
This commit is contained in:
Moritz 2024-09-12 08:47:53 +00:00 committed by Commit Queue
parent 120dac365d
commit 81daf8e563
104 changed files with 1902 additions and 1202 deletions

View file

@ -164,6 +164,13 @@ enum ExperimentalFlag {
experimentEnabledVersion: const Version(3, 0),
experimentReleasedVersion: const Version(3, 0)),
recordUse(
name: 'record-use',
isEnabledByDefault: false,
isExpired: false,
experimentEnabledVersion: defaultLanguageVersion,
experimentReleasedVersion: defaultLanguageVersion),
records(
name: 'records',
isEnabledByDefault: true,
@ -171,13 +178,6 @@ enum ExperimentalFlag {
experimentEnabledVersion: const Version(3, 0),
experimentReleasedVersion: const Version(3, 0)),
resourceIdentifiers(
name: 'resource-identifiers',
isEnabledByDefault: false,
isExpired: false,
experimentEnabledVersion: defaultLanguageVersion,
experimentReleasedVersion: defaultLanguageVersion),
sealedClass(
name: 'sealed-class',
isEnabledByDefault: true,

View file

@ -15039,6 +15039,17 @@ const MessageCode messageRecordTypeZeroFieldsButTrailingComma =
correctionMessage: r"""Try removing the trailing comma.""",
);
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Code<Null> codeRecordUseCannotBePlacedHere =
messageRecordUseCannotBePlacedHere;
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const MessageCode messageRecordUseCannotBePlacedHere = const MessageCode(
"RecordUseCannotBePlacedHere",
problemMessage:
r"""`RecordUse` annotation cannot be placed on this element.""",
);
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Code<Null> codeRecordUsedAsCallable = messageRecordUsedAsCallable;
@ -15225,28 +15236,6 @@ const MessageCode messageRequiredParameterWithDefault = const MessageCode(
r"""Try removing the default value or making the parameter optional.""",
);
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Code<Null> codeResourceIdentifiersMultiple =
messageResourceIdentifiersMultiple;
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const MessageCode messageResourceIdentifiersMultiple = const MessageCode(
"ResourceIdentifiersMultiple",
problemMessage:
r"""Only one resource identifier pragma can be used at a time.""",
);
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Code<Null> codeResourceIdentifiersNotStatic =
messageResourceIdentifiersNotStatic;
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const MessageCode messageResourceIdentifiersNotStatic = const MessageCode(
"ResourceIdentifiersNotStatic",
problemMessage:
r"""Resource identifier pragma can be used on a static method only.""",
);
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Code<Null> codeRestPatternInMapPattern = messageRestPatternInMapPattern;

View file

@ -41,8 +41,8 @@ final _knownFeatures = <String, ExperimentalFeature>{
ExperimentalFeatures.nonfunction_type_aliases,
EnableString.null_aware_elements: ExperimentalFeatures.null_aware_elements,
EnableString.patterns: ExperimentalFeatures.patterns,
EnableString.record_use: ExperimentalFeatures.record_use,
EnableString.records: ExperimentalFeatures.records,
EnableString.resource_identifiers: ExperimentalFeatures.resource_identifiers,
EnableString.sealed_class: ExperimentalFeatures.sealed_class,
EnableString.set_literals: ExperimentalFeatures.set_literals,
EnableString.spread_collections: ExperimentalFeatures.spread_collections,
@ -123,12 +123,12 @@ class EnableString {
/// String to enable the experiment "patterns"
static const String patterns = 'patterns';
/// String to enable the experiment "record-use"
static const String record_use = 'record-use';
/// String to enable the experiment "records"
static const String records = 'records';
/// String to enable the experiment "resource-identifiers"
static const String resource_identifiers = 'resource-identifiers';
/// String to enable the experiment "sealed-class"
static const String sealed_class = 'sealed-class';
@ -383,8 +383,18 @@ class ExperimentalFeatures {
releaseVersion: Version.parse('3.0.0'),
);
static final records = ExperimentalFeature(
static final record_use = ExperimentalFeature(
index: 22,
enableString: EnableString.record_use,
isEnabledByDefault: IsEnabledByDefault.record_use,
isExpired: IsExpired.record_use,
documentation: 'Output arguments used by static functions.',
experimentalReleaseVersion: null,
releaseVersion: null,
);
static final records = ExperimentalFeature(
index: 23,
enableString: EnableString.records,
isEnabledByDefault: IsEnabledByDefault.records,
isExpired: IsExpired.records,
@ -393,16 +403,6 @@ class ExperimentalFeatures {
releaseVersion: Version.parse('3.0.0'),
);
static final resource_identifiers = ExperimentalFeature(
index: 23,
enableString: EnableString.resource_identifiers,
isEnabledByDefault: IsEnabledByDefault.resource_identifiers,
isExpired: IsExpired.resource_identifiers,
documentation: 'Output arguments used by static functions.',
experimentalReleaseVersion: null,
releaseVersion: null,
);
static final sealed_class = ExperimentalFeature(
index: 24,
enableString: EnableString.sealed_class,
@ -565,12 +565,12 @@ class IsEnabledByDefault {
/// Default state of the experiment "patterns"
static const bool patterns = true;
/// Default state of the experiment "record-use"
static const bool record_use = false;
/// Default state of the experiment "records"
static const bool records = true;
/// Default state of the experiment "resource-identifiers"
static const bool resource_identifiers = false;
/// Default state of the experiment "sealed-class"
static const bool sealed_class = true;
@ -669,12 +669,12 @@ class IsExpired {
/// Expiration status of the experiment "patterns"
static const bool patterns = true;
/// Expiration status of the experiment "record-use"
static const bool record_use = false;
/// Expiration status of the experiment "records"
static const bool records = true;
/// Expiration status of the experiment "resource-identifiers"
static const bool resource_identifiers = false;
/// Expiration status of the experiment "sealed-class"
static const bool sealed_class = true;
@ -780,13 +780,12 @@ mixin _CurrentState {
/// Current state for the flag "patterns"
bool get patterns => isEnabled(ExperimentalFeatures.patterns);
/// Current state for the flag "record-use"
bool get record_use => isEnabled(ExperimentalFeatures.record_use);
/// Current state for the flag "records"
bool get records => isEnabled(ExperimentalFeatures.records);
/// Current state for the flag "resource-identifiers"
bool get resource_identifiers =>
isEnabled(ExperimentalFeatures.resource_identifiers);
/// Current state for the flag "sealed-class"
bool get sealed_class => isEnabled(ExperimentalFeatures.sealed_class);

View file

@ -154,26 +154,17 @@ class Immutable {
const Immutable([this.reason = '']);
}
@experimental
class RecordUse {
const RecordUse();
}
class Required {
final String reason;
const Required([this.reason = '']);
}
@experimental
class ResourceIdentifier {
final Object? metadata;
const ResourceIdentifier([this.metadata])
: assert(
metadata == null ||
metadata is bool ||
metadata is num ||
metadata is String,
'Valid metadata types are bool, int, double, and String.',
);
}
@Target({
TargetKind.constructor,
TargetKind.field,

View file

@ -236,7 +236,7 @@ const _Literal literal = _Literal();
/// any warnings.
///
/// An example use could be the arguments to functions annotated with
/// [ResourceIdentifier], as only constant arguments can be made available
/// [RecordUse], as only constant arguments can be made available
/// to the post-compile steps.
///
/// ```dart
@ -539,6 +539,24 @@ class Immutable {
const Immutable([this.reason = '']);
}
/// Annotates a static method to be recorded.
///
/// Applies to static functions, top-level functions, or extension methods.
///
/// During compilation, all statically resolved calls to an annotated function
/// are registered, and information about the annotated functions, the calls,
/// and their arguments, is then made available to post-compile steps.
// TODO(srawlins): Enforce with `TargetKind.method`.
@experimental
class RecordUse {
/// Creates a [RecordUse] instance.
///
/// This annotation can be placed as an annotation on functions whose
/// statically resolved calls should be registered together with the optional
/// [metadata] information.
const RecordUse();
}
/// Used to annotate a named parameter `p` in a method or function `f`.
///
/// See [required] for more details.
@ -567,38 +585,6 @@ class Required {
const Required([this.reason = '']);
}
/// Annotates a static method as referencing a native resource.
///
/// Applies to static functions, top-level functions, or extension methods.
///
/// During compilation, all statically resolved calls to an annotated function
/// are registered, and information about the annotated functions, the calls,
/// and their arguments, is then made available to post-compile steps.
// TODO(srawlins): Enforce with `TargetKind.method`.
@experimental
class ResourceIdentifier {
/// Information which is stored together with the function call.
///
/// This could, for example, be the name of the package containing the
/// function annotated with this annotation. Allowed types are [bool], [int],
/// [double], and [String].
final Object? metadata;
/// Creates a [ResourceIdentifier] instance.
///
/// This annotation can be placed as an annotation on functions whose
/// statically resolved calls should be registered together with the optional
/// [metadata] information.
const ResourceIdentifier([this.metadata])
: assert(
metadata == null ||
metadata is bool ||
metadata is num ||
metadata is String,
'Valid metadata types are bool, int, double, and String.',
);
}
/// See [useResult] for more details.
@Target({
TargetKind.constructor,

View file

@ -103,8 +103,8 @@ Future<ProcessResult> markExecutable(String outputFile) {
/// Generates kernel by running the provided [genKernel] path.
///
/// Also takes a path to the [resourcesFile] JSON file, where the method calls
/// to static functions annotated with `@ResourceIdentifier` will be collected.
/// Also takes a path to the [recordedUsagesFile] JSON file, where the method
/// calls to static functions annotated with `@RecordUse` will be collected.
Future<ProcessResult> generateKernelHelper({
required String dartaotruntime,
String? sourceFile,
@ -115,7 +115,7 @@ Future<ProcessResult> generateKernelHelper({
String? targetOS,
List<String> extraGenKernelOptions = const [],
String? nativeAssets,
String? resourcesFile,
String? recordedUsagesFile,
String? depFile,
bool enableAsserts = false,
bool fromDill = false,
@ -138,7 +138,8 @@ Future<ProcessResult> generateKernelHelper({
...defines.map((d) => '-D$d'),
if (packages != null) '--packages=$packages',
if (nativeAssets != null) '--native-assets=$nativeAssets',
if (resourcesFile != null) '--resources-file=$resourcesFile',
if (recordedUsagesFile != null)
'--recorded-usages-file=$recordedUsagesFile',
if (depFile != null) '--depfile=$depFile',
'--output=$kernelFile',
...extraGenKernelOptions,

View file

@ -65,14 +65,14 @@ extension type KernelGenerator._(_Generator _generator) {
/// Generate a kernel file,
///
/// [resourcesFile] is the path to `resources.json`, where the tree-shaking
/// information collected during kernel compilation is stored.
/// [recordedUsagesFile] is the path to `recorded_usages.json`, where the
/// tree-shaking information collected during kernel compilation is stored.
Future<SnapshotGenerator> generate({
String? resourcesFile,
String? recordedUsagesFile,
List<String>? extraOptions,
}) =>
_generator.generateKernel(
resourcesFile: resourcesFile,
recordedUsagesFile: recordedUsagesFile,
extraOptions: extraOptions,
);
}
@ -192,7 +192,7 @@ class _Generator {
}
Future<SnapshotGenerator> generateKernel({
String? resourcesFile,
String? recordedUsagesFile,
List<String>? extraOptions,
}) async {
if (_verbose) {
@ -219,7 +219,7 @@ class _Generator {
if (_depFile != null) '--depfile-target=$_outputPath',
...?extraOptions,
],
resourcesFile: resourcesFile,
recordedUsagesFile: recordedUsagesFile,
aot: true,
);
await _forwardOutput(kernelResult);
@ -366,7 +366,7 @@ class _Generator {
///
/// [nativeAssets] is the path to `native_assets.yaml`.
///
/// [resourcesFile] is the path to `resources.json`.
/// [recordedUsagesFile] is the path to `recorded_usages.json`.
Future<void> generateKernel({
required String sourceFile,
required String outputFile,
@ -382,7 +382,7 @@ Future<void> generateKernel({
bool product = true,
bool verbose = false,
String? nativeAssets,
String? resourcesFile,
String? recordedUsagesFile,
String? depFile,
List<String>? extraOptions,
}) async {
@ -407,7 +407,7 @@ Future<void> generateKernel({
...?extraOptions,
],
nativeAssets: nativeAssets,
resourcesFile: resourcesFile,
recordedUsagesFile: recordedUsagesFile,
product: product,
);
await _forwardOutput(kernelResult);

View file

@ -99,7 +99,11 @@ class DartdevRunner extends CommandRunner<int> {
final nativeAssetsExperimentEnabled =
nativeAssetsEnabled(vmEnabledExperiments);
if (nativeAssetsExperimentEnabled) {
addCommand(BuildCommand(verbose: verbose));
final recordUseExperimentEnabled = recordUseEnabled(vmEnabledExperiments);
addCommand(BuildCommand(
verbose: verbose,
recordUseEnabled: recordUseExperimentEnabled,
));
}
addCommand(CompileCommand(
verbose: verbose,

View file

@ -29,8 +29,9 @@ class BuildCommand extends DartdevCommand {
static const String outputOptionName = 'output';
static const String formatOptionName = 'format';
static const int genericErrorExitCode = 255;
final bool recordUseEnabled;
BuildCommand({bool verbose = false})
BuildCommand({bool verbose = false, required this.recordUseEnabled})
: super(cmdName, 'Build a Dart application including native assets.',
verbose) {
argParser
@ -162,7 +163,10 @@ class BuildCommand extends DartdevCommand {
final tempDir = Directory.systemTemp.createTempSync();
try {
final packageConfig = await packageConfigUri(sourceUri);
final resources = path.join(tempDir.path, 'resources.json');
String? recordedUsagesPath;
if (recordUseEnabled) {
recordedUsagesPath = path.join(tempDir.path, 'recorded_usages.json');
}
final generator = KernelGenerator(
kind: format,
sourceFile: sourceUri.toFilePath(),
@ -177,12 +181,13 @@ class BuildCommand extends DartdevCommand {
);
final snapshotGenerator = await generator.generate(
resourcesFile: resources,
recordedUsagesFile: recordedUsagesPath,
);
// Start linking here.
final linkResult = await nativeAssetsBuildRunner.link(
resourceIdentifiers: Uri.file(resources),
resourceIdentifiers:
recordUseEnabled ? Uri.file(recordedUsagesPath!) : null,
workingDirectory: workingDirectory,
target: target,
linkModePreference: LinkModePreferenceImpl.dynamic,

View file

@ -98,3 +98,6 @@ List<String> parseVmEnabledExperiments(List<String> vmArgs) {
bool nativeAssetsEnabled(List<String> vmEnabledExperiments) =>
vmEnabledExperiments
.contains(ExperimentalFeatures.native_assets.enableString);
bool recordUseEnabled(List<String> vmEnabledExperiments) =>
vmEnabledExperiments.contains(ExperimentalFeatures.record_use.enableString);

View file

@ -233,6 +233,78 @@ void main(List<String> 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,
);
// The build directory exists
final allDirectory =
Directory.fromUri(dartAppUri.resolve('bin/drop_dylib_recording_all'));
expect(allDirectory.existsSync(), 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,
);
});
});
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/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,
);
});
});
}
Future<void> _withTempDir(Future<void> Function(Uri tempUri) fun) async {

View file

@ -62,11 +62,10 @@ Future<run_process.RunProcessResult> runProcess({
throwOnUnexpectedExitCode: throwOnUnexpectedExitCode,
);
Future<void> copyTestProjects(Uri copyTargetUri, Logger logger) async {
final pkgNativeAssetsBuilderUri = Platform.script.resolve(
'../../../../third_party/pkg/native/pkgs/native_assets_builder/');
Future<void> copyTestProjects(
Uri copyTargetUri, Logger logger, Uri packageLocation) async {
// Reuse the test projects from `pkg:native`.
final testProjectsUri = pkgNativeAssetsBuilderUri.resolve('test_data/');
final testProjectsUri = packageLocation.resolve('test_data/');
final manifestUri = testProjectsUri.resolve('manifest.yaml');
final manifestFile = File.fromUri(manifestUri);
final manifestString = await manifestFile.readAsString();
@ -100,7 +99,7 @@ Future<void> copyTestProjects(Uri copyTargetUri, Logger logger) async {
final sourceString = await sourceFile.readAsString();
final modifiedString = sourceString.replaceAll(
'path: ../../',
'path: ${pkgNativeAssetsBuilderUri.toFilePath().replaceAll('\\', '/')}',
'path: ${packageLocation.toFilePath().replaceAll('\\', '/')}',
);
await File.fromUri(targetUri).writeAsString(modifiedString);
}
@ -158,16 +157,46 @@ Future<void> nativeAssetsTest(
String packageUnderTest,
Future<void> Function(Uri) fun, {
bool skipPubGet = false,
}) async {
assert(const [
'add_asset_link',
'dart_app',
'drop_dylib_link',
'native_add_duplicate',
'native_add',
].contains(packageUnderTest));
}) async =>
await runPackageTest(
packageUnderTest,
skipPubGet,
fun,
const [
'add_asset_link',
'dart_app',
'drop_dylib_link',
'native_add_duplicate',
'native_add',
'treeshaking_native_libs',
],
Platform.script.resolve(
'../../../../third_party/pkg/native/pkgs/native_assets_builder/'),
);
Future<void> recordUseTest(
String packageUnderTest,
Future<void> Function(Uri) fun, {
bool skipPubGet = false,
}) async =>
await runPackageTest(
packageUnderTest,
skipPubGet,
fun,
const ['drop_dylib_recording'],
Platform.script.resolve('../../../record_use/'),
);
Future<void> runPackageTest(
String packageUnderTest,
bool skipPubGet,
Future<void> Function(Uri) fun,
List<String> validPackages,
Uri packageLocation,
) async {
assert(validPackages.contains(packageUnderTest));
return await inTempDir((tempUri) async {
await copyTestProjects(tempUri, logger);
await copyTestProjects(tempUri, logger, packageLocation);
final packageUri = tempUri.resolve('$packageUnderTest/');
if (!skipPubGet) {
await runPubGet(workingDirectory: packageUri, logger: logger);

View file

@ -235,6 +235,14 @@ class ExperimentalFlag {
experimentEnabledVersion: const Version(3, 0),
experimentReleasedVersion: const Version(3, 0));
static const ExperimentalFlag recordUse = const ExperimentalFlag(
name: 'record-use',
isEnabledByDefault: false,
isExpired: false,
enabledVersion: defaultLanguageVersion,
experimentEnabledVersion: defaultLanguageVersion,
experimentReleasedVersion: defaultLanguageVersion);
static const ExperimentalFlag records = const ExperimentalFlag(
name: 'records',
isEnabledByDefault: true,
@ -243,14 +251,6 @@ class ExperimentalFlag {
experimentEnabledVersion: const Version(3, 0),
experimentReleasedVersion: const Version(3, 0));
static const ExperimentalFlag resourceIdentifiers = const ExperimentalFlag(
name: 'resource-identifiers',
isEnabledByDefault: false,
isExpired: false,
enabledVersion: defaultLanguageVersion,
experimentEnabledVersion: defaultLanguageVersion,
experimentReleasedVersion: defaultLanguageVersion);
static const ExperimentalFlag sealedClass = const ExperimentalFlag(
name: 'sealed-class',
isEnabledByDefault: true,
@ -462,14 +462,14 @@ class GlobalFeatures {
GlobalFeature get patterns =>
_patterns ??= _computeGlobalFeature(ExperimentalFlag.patterns);
GlobalFeature? _recordUse;
GlobalFeature get recordUse =>
_recordUse ??= _computeGlobalFeature(ExperimentalFlag.recordUse);
GlobalFeature? _records;
GlobalFeature get records =>
_records ??= _computeGlobalFeature(ExperimentalFlag.records);
GlobalFeature? _resourceIdentifiers;
GlobalFeature get resourceIdentifiers => _resourceIdentifiers ??=
_computeGlobalFeature(ExperimentalFlag.resourceIdentifiers);
GlobalFeature? _sealedClass;
GlobalFeature get sealedClass =>
_sealedClass ??= _computeGlobalFeature(ExperimentalFlag.sealedClass);
@ -640,16 +640,16 @@ class LibraryFeatures {
_patterns ??= globalFeatures._computeLibraryFeature(
ExperimentalFlag.patterns, canonicalUri, libraryVersion);
LibraryFeature? _recordUse;
LibraryFeature get recordUse =>
_recordUse ??= globalFeatures._computeLibraryFeature(
ExperimentalFlag.recordUse, canonicalUri, libraryVersion);
LibraryFeature? _records;
LibraryFeature get records =>
_records ??= globalFeatures._computeLibraryFeature(
ExperimentalFlag.records, canonicalUri, libraryVersion);
LibraryFeature? _resourceIdentifiers;
LibraryFeature get resourceIdentifiers =>
_resourceIdentifiers ??= globalFeatures._computeLibraryFeature(
ExperimentalFlag.resourceIdentifiers, canonicalUri, libraryVersion);
LibraryFeature? _sealedClass;
LibraryFeature get sealedClass =>
_sealedClass ??= globalFeatures._computeLibraryFeature(
@ -743,10 +743,10 @@ class LibraryFeatures {
return nullAwareElements;
case shared.ExperimentalFlag.patterns:
return patterns;
case shared.ExperimentalFlag.recordUse:
return recordUse;
case shared.ExperimentalFlag.records:
return records;
case shared.ExperimentalFlag.resourceIdentifiers:
return resourceIdentifiers;
case shared.ExperimentalFlag.sealedClass:
return sealedClass;
case shared.ExperimentalFlag.setLiterals:
@ -817,10 +817,10 @@ ExperimentalFlag? parseExperimentalFlag(String flag) {
return ExperimentalFlag.nullAwareElements;
case "patterns":
return ExperimentalFlag.patterns;
case "record-use":
return ExperimentalFlag.recordUse;
case "records":
return ExperimentalFlag.records;
case "resource-identifiers":
return ExperimentalFlag.resourceIdentifiers;
case "sealed-class":
return ExperimentalFlag.sealedClass;
case "set-literals":
@ -886,9 +886,8 @@ final Map<ExperimentalFlag, bool> defaultExperimentalFlags = {
ExperimentalFlag.nullAwareElements:
ExperimentalFlag.nullAwareElements.isEnabledByDefault,
ExperimentalFlag.patterns: ExperimentalFlag.patterns.isEnabledByDefault,
ExperimentalFlag.recordUse: ExperimentalFlag.recordUse.isEnabledByDefault,
ExperimentalFlag.records: ExperimentalFlag.records.isEnabledByDefault,
ExperimentalFlag.resourceIdentifiers:
ExperimentalFlag.resourceIdentifiers.isEnabledByDefault,
ExperimentalFlag.sealedClass: ExperimentalFlag.sealedClass.isEnabledByDefault,
ExperimentalFlag.setLiterals: ExperimentalFlag.setLiterals.isEnabledByDefault,
ExperimentalFlag.spreadCollections:
@ -942,9 +941,8 @@ const Map<shared.ExperimentalFlag, ExperimentalFlag> sharedExperimentalFlags = {
ExperimentalFlag.nonfunctionTypeAliases,
shared.ExperimentalFlag.nullAwareElements: ExperimentalFlag.nullAwareElements,
shared.ExperimentalFlag.patterns: ExperimentalFlag.patterns,
shared.ExperimentalFlag.recordUse: ExperimentalFlag.recordUse,
shared.ExperimentalFlag.records: ExperimentalFlag.records,
shared.ExperimentalFlag.resourceIdentifiers:
ExperimentalFlag.resourceIdentifiers,
shared.ExperimentalFlag.sealedClass: ExperimentalFlag.sealedClass,
shared.ExperimentalFlag.setLiterals: ExperimentalFlag.setLiterals,
shared.ExperimentalFlag.spreadCollections: ExperimentalFlag.spreadCollections,

View file

@ -2,4 +2,4 @@
// 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.
export '../kernel/resource_identifier.dart';
export '../kernel/record_use.dart';

View file

@ -46,7 +46,7 @@ import '../type_inference/matching_cache.dart';
import '../type_inference/matching_expressions.dart';
import 'constant_int_folder.dart';
import 'exhaustiveness.dart';
import 'resource_identifier.dart' as ResourceIdentifiers;
import 'record_use.dart' as RecordUse;
import 'static_weak_references.dart' show StaticWeakReferences;
part 'constant_collection_builders.dart';
@ -352,11 +352,14 @@ class ConstantsTransformer extends RemovingTransformer {
parent, constantEvaluator.errorReporter);
}
final Iterable<InstanceConstant> resourceAnnotations =
ResourceIdentifiers.findResourceAnnotations(parent);
RecordUse.findRecordUseAnnotation(parent);
if (resourceAnnotations.isNotEmpty) {
// Coverage-ignore-block(suite): Not run.
ResourceIdentifiers.validateResourceIdentifierDeclaration(
parent, constantEvaluator.errorReporter, resourceAnnotations);
RecordUse.validateRecordUseDeclaration(
parent,
constantEvaluator.errorReporter,
resourceAnnotations,
);
}
}
}

View file

@ -2,53 +2,46 @@
// 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.
/// Recognition and validation of resource identification annotations.
/// Recognition and validation of usage recording annotations.
///
/// A static method to be collected as a resource identifier can be annotated
/// with `@ResourceIdentifier('some-id-string')`.
/// A static method to be recorded can be annotated with `@RecordUse()`.
library;
import 'package:kernel/ast.dart';
import '../base/messages.dart'
show
messageResourceIdentifiersNotStatic,
messageResourceIdentifiersMultiple;
import '../base/messages.dart' show messageRecordUseCannotBePlacedHere;
import 'constant_evaluator.dart' show ErrorReporter;
/// Get all of the `@ResourceIdentifier` annotations from `package:meta`
/// Get all of the `@RecordUse` annotations from `package:meta`
/// that are attached to the specified [node].
Iterable<InstanceConstant> findResourceAnnotations(Annotatable node) =>
Iterable<InstanceConstant> findRecordUseAnnotation(Annotatable node) =>
node.annotations
.whereType<ConstantExpression>()
.map((expression) => expression.constant)
.whereType<InstanceConstant>()
.where((instance) => isResourceIdentifier(instance.classNode))
.where((instance) => isRecordUse(instance.classNode))
.toList(growable: false);
// Coverage-ignore(suite): Not run.
final Uri _metaLibraryUri = new Uri(scheme: 'package', path: 'meta/meta.dart');
bool isResourceIdentifier(Class classNode) =>
classNode.name == 'ResourceIdentifier' &&
bool isRecordUse(Class classNode) =>
classNode.name == 'RecordUse' &&
// Coverage-ignore(suite): Not run.
classNode.enclosingLibrary.importUri == _metaLibraryUri;
// Coverage-ignore(suite): Not run.
/// Report if the resource annotations is placed on anything but a static
/// method.
void validateResourceIdentifierDeclaration(
void validateRecordUseDeclaration(
Annotatable node,
ErrorReporter errorReporter,
Iterable<InstanceConstant> resourceAnnotations,
) {
if (node is! Procedure ||
!node.isStatic ||
node.kind != ProcedureKind.Method) {
errorReporter.report(messageResourceIdentifiersNotStatic.withLocation(
node.location!.file, node.fileOffset, 1));
} else if (resourceAnnotations.length > 1) {
errorReporter.report(messageResourceIdentifiersMultiple.withLocation(
final bool onNonStaticMethod =
node is! Procedure || !node.isStatic || node.kind != ProcedureKind.Method;
if (onNonStaticMethod) {
errorReporter.report(messageRecordUseCannotBePlacedHere.withLocation(
node.location!.file, node.fileOffset, 1));
}
}

View file

@ -954,10 +954,8 @@ RequiredNamedParameterHasDefaultValueError/analyzerCode: Fail
RequiredNamedParameterHasDefaultValueError/example: Fail
RequiredNamedParameterHasDefaultValueWarning/analyzerCode: Fail
RequiredNamedParameterHasDefaultValueWarning/example: Fail
ResourceIdentifiersMultiple/analyzerCode: Fail
ResourceIdentifiersMultiple/example: Fail
ResourceIdentifiersNotStatic/analyzerCode: Fail
ResourceIdentifiersNotStatic/example: Fail
RecordUseCannotBePlacedHere/analyzerCode: Fail
RecordUseCannotBePlacedHere/example: Fail
RestPatternInMapPattern/analyzerCode: Fail
RestPatternMoreThanOne/analyzerCode: Fail
RethrowNotCatch/example: Fail

View file

@ -7017,11 +7017,8 @@ DefaultInSwitchExpression:
default => 'other'
};
ResourceIdentifiersNotStatic:
problemMessage: "Resource identifier pragma can be used on a static method only."
ResourceIdentifiersMultiple:
problemMessage: "Only one resource identifier pragma can be used at a time."
RecordUseCannotBePlacedHere:
problemMessage: "`RecordUse` annotation cannot be placed on this element."
WasmImportOrExportInUserCode:
problemMessage: "Pragmas `wasm:import` and `wasm:export` are for internal use only and cannot be used by user code."

View file

@ -684,7 +684,7 @@ const Map<String, ({int hitCount, int missCount})> _expect = {
missCount: 0,
),
// 100.0%.
"package:front_end/src/kernel/resource_identifier.dart": (
"package:front_end/src/kernel/record_use.dart": (
hitCount: 15,
missCount: 0,
),

View file

@ -11,7 +11,7 @@ front_end/lib/src/api_prototype/constant_evaluator/Exports: Fail
front_end/lib/src/api_prototype/front_end/Exports: Fail
front_end/lib/src/api_prototype/incremental_kernel_generator/Exports: Fail
front_end/lib/src/api_prototype/lowering_predicates/Exports: Fail
front_end/lib/src/api_prototype/resource_identifier/Exports: Fail
front_end/lib/src/api_prototype/record_use/Exports: Fail
front_end/lib/src/api_prototype/static_weak_references/Exports: Fail
front_end/lib/src/api_prototype/terminal_color_support/Exports: Fail
front_end/lib/src/api_prototype/testing/Exports: Fail

View file

@ -120,6 +120,7 @@ preexisting
pubspec.yaml
r
re
RecordUse
refutable
resource
sdksummary

View file

@ -57,6 +57,8 @@
extension type const FancyInt(@mustBeConst int _actual) {}
```
- Renamed `@ResourceIdentifier` to `@RecordUse`.
## 1.15.0
- Updated `@mustBeOverridden` to only flag missing overrides in concrete

View file

@ -236,7 +236,7 @@ const _Literal literal = _Literal();
/// any warnings.
///
/// An example use could be the arguments to functions annotated with
/// [ResourceIdentifier], as only constant arguments can be made available
/// [RecordUse], as only constant arguments can be made available
/// to the post-compile steps.
///
/// ```dart
@ -539,6 +539,24 @@ class Immutable {
const Immutable([this.reason = '']);
}
/// Annotates a static method to be recorded.
///
/// Applies to static functions, top-level functions, or extension methods.
///
/// During compilation, all statically resolved calls to an annotated function
/// are registered, and information about the annotated functions, the calls,
/// and their arguments, is then made available to post-compile steps.
// TODO(srawlins): Enforce with `TargetKind.method`.
@experimental
class RecordUse {
/// Creates a [RecordUse] instance.
///
/// This annotation can be placed as an annotation on functions whose
/// statically resolved calls should be registered together with the optional
/// [metadata] information.
const RecordUse();
}
/// Used to annotate a named parameter `p` in a method or function `f`.
///
/// See [required] for more details.
@ -567,38 +585,6 @@ class Required {
const Required([this.reason = '']);
}
/// Annotates a static method as referencing a native resource.
///
/// Applies to static functions, top-level functions, or extension methods.
///
/// During compilation, all statically resolved calls to an annotated function
/// are registered, and information about the annotated functions, the calls,
/// and their arguments, is then made available to post-compile steps.
// TODO(srawlins): Enforce with `TargetKind.method`.
@experimental
class ResourceIdentifier {
/// Information which is stored together with the function call.
///
/// This could, for example, be the name of the package containing the
/// function annotated with this annotation. Allowed types are [bool], [int],
/// [double], and [String].
final Object? metadata;
/// Creates a [ResourceIdentifier] instance.
///
/// This annotation can be placed as an annotation on functions whose
/// statically resolved calls should be registered together with the optional
/// [metadata] information.
const ResourceIdentifier([this.metadata])
: assert(
metadata == null ||
metadata is bool ||
metadata is num ||
metadata is String,
'Valid metadata types are bool, int, double, and String.',
);
}
/// See [useResult] for more details.
@Target({
TargetKind.constructor,

View file

@ -1,3 +1,7 @@
## 0.2.0
- Use maps instead of lists in serialization.
## 0.1.1
- Fix repository link.

View file

@ -0,0 +1,4 @@
include: package:dart_flutter_team_lints/analysis_options.yaml
analyzer:
exclude: [test_data/**]

View file

@ -35,11 +35,11 @@ class Definition {
}
Map<String, dynamic> toJson(
List<Identifier> identifiers,
List<String> uris,
Map<Identifier, int> identifiers,
Map<String, int> uris,
) =>
{
'id': identifiers.indexOf(identifier),
'id': identifiers[identifier]!,
'@': location.toJson(),
'loadingUnit': loadingUnit,
};

View file

@ -35,9 +35,9 @@ class Usage<T extends Reference> {
);
Map<String, dynamic> toJson(
List<Identifier> identifiers,
List<String> uris,
List<Constant> constants,
Map<Identifier, int> identifiers,
Map<String, int> uris,
Map<Constant, int> constants,
) =>
{
'definition': definition.toJson(identifiers, uris),

View file

@ -2,6 +2,8 @@
// 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:collection/collection.dart';
import '../helper.dart';
import '../public/arguments.dart';
import '../public/constant.dart';
@ -22,7 +24,7 @@ class UsageRecord {
});
factory UsageRecord.fromJson(Map<String, dynamic> json) {
final uris = json['uris'] as List<String>;
final uris = (json['uris'] as List).cast<String>();
final identifiers = (json['ids'] as List)
.whereType<Map<String, dynamic>>()
@ -66,9 +68,10 @@ class UsageRecord {
final identifiers = <Identifier>{
...calls.map((call) => call.definition.identifier),
...instances.map((instance) => instance.definition.identifier),
}.toList();
}.asMapToIndices;
final uris = <String>{
...identifiers.map((e) => e.uri),
...identifiers.keys.map((e) => e.uri),
...calls.expand((call) => [
call.definition.location.uri,
...call.references.map((reference) => reference.location.uri),
@ -77,7 +80,7 @@ class UsageRecord {
instance.definition.location.uri,
...instance.references.map((reference) => reference.location.uri),
]),
}.toList();
}.asMapToIndices;
final constants = {
...calls.expand((e) => e.references
@ -87,12 +90,15 @@ class UsageRecord {
...instances
.expand((element) => element.references)
.expand((e) => e.instanceConstant.fields.values)
}.flatten().toList();
}.flatten().asMapToIndices;
return {
'metadata': metadata.toJson(),
'uris': uris,
'ids': identifiers.map((identifier) => identifier.toJson(uris)).toList(),
'constants': constants.map((e) => e.toJson(constants)).toList(),
'uris': uris.keys.toList(),
'ids': identifiers.keys
.map((identifier) => identifier.toJson(uris))
.toList(),
'constants':
constants.keys.map((constant) => constant.toJson(constants)).toList(),
if (calls.isNotEmpty)
'calls': calls
.map((reference) => reference.toJson(identifiers, uris, constants))
@ -132,3 +138,9 @@ extension on Iterable<Constant> {
return constants;
}
}
extension _PrivateIterableExtension<T> on Iterable<T> {
Map<T, int> get asMapToIndices => Map.fromEntries(
mapIndexed((index, uri) => MapEntry(uri, index)),
);
}

View file

@ -31,7 +31,7 @@ class Arguments {
);
}
Map<String, dynamic> toJson(List<Constant> constants) {
Map<String, dynamic> toJson(Map<Constant, int> constants) {
final hasConst =
constArguments.named.isNotEmpty || constArguments.positional.isNotEmpty;
final hasNonConst = nonConstArguments.named.isNotEmpty ||
@ -70,22 +70,24 @@ class ConstArguments {
) =>
ConstArguments(
positional: json['positional'] != null
? (json['positional'] as Map<String, dynamic>).map((key, value) =>
MapEntry(int.parse(key), constants[value as int]))
? (json['positional'] as Map<String, dynamic>).map((position,
constantIndex) =>
MapEntry(int.parse(position), constants[constantIndex as int]))
: {},
named: json['named'] != null
? (json['named'] as Map<String, dynamic>)
.map((key, value) => MapEntry(key, constants[value as int]))
? (json['named'] as Map<String, dynamic>).map(
(name, constantIndex) =>
MapEntry(name, constants[constantIndex as int]))
: {},
);
Map<String, dynamic> toJson(List<Constant> constants) => {
Map<String, dynamic> toJson(Map<Constant, int> constants) => {
if (positional.isNotEmpty)
'positional': positional.map((key, value) =>
MapEntry(key.toString(), constants.indexOf(value))),
'positional': positional.map((position, constantIndex) =>
MapEntry(position.toString(), constants[constantIndex]!)),
if (named.isNotEmpty)
'named': named
.map((key, value) => MapEntry(key, constants.indexOf(value))),
'named': named.map((name, constantIndex) =>
MapEntry(name, constants[constantIndex]!)),
};
@override
@ -111,9 +113,10 @@ class NonConstArguments {
factory NonConstArguments.fromJson(Map<String, dynamic> json) =>
NonConstArguments(
positional:
json['positional'] != null ? json['positional'] as List<int> : [],
named: json['named'] != null ? json['named'] as List<String> : [],
positional: json['positional'] != null
? (json['positional'] as List).cast()
: <int>[],
named: json['named'] != null ? (json['named'] as List).cast() : [],
);
Map<String, dynamic> toJson() => {

View file

@ -7,7 +7,7 @@ import '../helper.dart';
sealed class Constant {
const Constant();
Map<String, dynamic> toJson(List<Constant> constants);
Map<String, dynamic> toJson(Map<Constant, int> constants);
static Constant fromJson(
Map<String, dynamic> value, List<Constant> constants) =>
@ -17,7 +17,8 @@ sealed class Constant {
IntConstant._type => IntConstant(value['value'] as int),
StringConstant._type => StringConstant(value['value'] as String),
ListConstant._type => ListConstant((value['value'] as List<dynamic>)
.map((e) => constants[e as int])
.map((value) => value as int)
.map((value) => constants[value])
.toList()),
MapConstant._type => MapConstant(
(value['value'] as Map<String, dynamic>)
@ -33,7 +34,8 @@ final class NullConstant extends Constant {
const NullConstant() : super();
@override
Map<String, dynamic> toJson(List<Constant> constants) => _toJson(_type, null);
Map<String, dynamic> toJson(Map<Constant, int> constants) =>
_toJson(_type, null);
}
sealed class PrimitiveConstant<T extends Object> extends Constant {
@ -52,7 +54,7 @@ sealed class PrimitiveConstant<T extends Object> extends Constant {
}
@override
Map<String, dynamic> toJson(List<Constant> constants) => valueToJson();
Map<String, dynamic> toJson(Map<Constant, int> constants) => valueToJson();
Map<String, dynamic> valueToJson();
}
@ -102,9 +104,9 @@ final class ListConstant<T extends Constant> extends Constant {
}
@override
Map<String, dynamic> toJson(List<Constant> constants) => _toJson(
Map<String, dynamic> toJson(Map<Constant, int> constants) => _toJson(
_type,
value.map((constant) => constants.indexOf(constant)).toList(),
value.map((constant) => constants[constant]).toList(),
);
}
@ -126,10 +128,9 @@ final class MapConstant<T extends Constant> extends Constant {
}
@override
Map<String, dynamic> toJson(List<Constant> constants) => _toJson(
Map<String, dynamic> toJson(Map<Constant, int> constants) => _toJson(
_type,
value
.map((key, constant) => MapEntry(key, constants.indexOf(constant))),
value.map((key, constant) => MapEntry(key, constants[constant]!)),
);
}

View file

@ -20,8 +20,8 @@ class Identifier {
name: json['name'] as String,
);
Map<String, dynamic> toJson(List<String> uris) => {
'uri': uris.indexOf(uri),
Map<String, dynamic> toJson(Map<String, int> uris) => {
'uri': uris[uri]!,
if (parent != null) 'parent': parent,
'name': name,
};

View file

@ -18,15 +18,15 @@ final class InstanceConstant {
) {
return InstanceConstant(
fields: (json['fields'] as Map<String, dynamic>).map(
(key, value) => MapEntry(key, constants[value as int]),
(key, constantIndex) => MapEntry(key, constants[constantIndex as int]),
),
);
}
Map<String, dynamic> toJson(List<Constant> constants) => {
Map<String, dynamic> toJson(Map<Constant, int> constants) => {
if (fields.isNotEmpty)
'fields': fields
.map((key, value) => MapEntry(key, constants.indexOf(value))),
'fields': fields.map((name, constantIndex) =>
MapEntry(name, constants[constantIndex]!)),
};
@override

View file

@ -22,9 +22,9 @@ class Location {
);
}
Map<String, dynamic> toJson({List<String>? uris}) {
Map<String, dynamic> toJson({Map<String, int>? uris}) {
return {
if (uris != null) 'uri': uris.indexOf(uri),
if (uris != null) 'uri': uris[uri]!,
'line': line,
'column': column,
};

View file

@ -15,7 +15,11 @@ sealed class Reference {
const Reference({this.loadingUnit, required this.location});
Map<String, dynamic> toJson(List<String> uris, List<Constant> constants) => {
Map<String, dynamic> toJson(
Map<String, int> uris,
Map<Constant, int> constants,
) =>
{
'loadingUnit': loadingUnit,
'@': location.toJson(uris: uris),
};
@ -59,7 +63,10 @@ final class CallReference extends Reference {
}
@override
Map<String, dynamic> toJson(List<String> uris, List<Constant> constants) {
Map<String, dynamic> toJson(
Map<String, int> uris,
Map<Constant, int> constants,
) {
final argumentJson = arguments?.toJson(constants) ?? {};
return {
if (argumentJson.isNotEmpty) 'arguments': argumentJson,
@ -108,7 +115,11 @@ final class InstanceReference extends Reference {
}
@override
Map<String, dynamic> toJson(List<String> uris, List<Constant> constants) => {
Map<String, dynamic> toJson(
Map<String, int> uris,
Map<Constant, int> constants,
) =>
{
'instanceConstant': instanceConstant.toJson(constants),
...super.toJson(uris, constants),
};

View file

@ -1,7 +1,7 @@
name: record_use
description: >
The serialization logic and API for the usage recording SDK feature.
version: 0.1.1
version: 0.2.0
repository: https://github.com/dart-lang/sdk/tree/main/pkg/record_use
environment:

View file

@ -2,24 +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 'dart:convert';
import 'package:record_use/record_use_internal.dart';
import 'package:test/test.dart';
import 'test_data.dart';
void main() {
final json = jsonDecode(recordedUsesJson) as Map<String, dynamic>;
test(
'JSON',
() => expect(recordedUses.toJson(), recordedUsesJson),
() => expect(recordedUses.toJson(), json),
);
test(
'Object',
() => expect(UsageRecord.fromJson(recordedUsesJson), recordedUses),
() => expect(UsageRecord.fromJson(json), recordedUses),
);
test('Json->Object->Json', () {
expect(UsageRecord.fromJson(recordedUsesJson).toJson(), recordedUsesJson);
expect(UsageRecord.fromJson(json).toJson(), json);
});
test('Object->Json->Object', () {

View file

@ -130,98 +130,149 @@ final recordedUses = UsageRecord(
],
);
final recordedUsesJson = {
'metadata': {
'comment':
'Recorded references at compile time and their argument values, as far'
' as known, to definitions annotated with @RecordUse',
'version': '1.6.2-wip+5.-.2.z'
final recordedUsesJson = '''{
"metadata": {
"comment":
"Recorded references at compile time and their argument values, as far as known, to definitions annotated with @RecordUse",
"version": "1.6.2-wip+5.-.2.z"
},
'uris': [
'file://lib/_internal/js_runtime/lib/js_helper.dart',
'file://benchmarks/OmnibusDeferred/dart/OmnibusDeferred.dart'
"uris": [
"file://lib/_internal/js_runtime/lib/js_helper.dart",
"file://benchmarks/OmnibusDeferred/dart/OmnibusDeferred.dart"
],
'ids': [
{'uri': 0, 'parent': 'MyClass', 'name': 'get:loadDeferredLibrary'},
{'uri': 0, 'name': 'MyAnnotation'}
"ids": [
{"uri": 0, "parent": "MyClass", "name": "get:loadDeferredLibrary"},
{"uri": 0, "name": "MyAnnotation"}
],
'constants': [
{'type': 'String', 'value': 'jenkins'},
{'type': 'String', 'value': 'mercury'},
{'type': 'String', 'value': 'lib_SHA1'},
{'type': 'bool', 'value': false},
{'type': 'int', 'value': 1},
{'type': 'String', 'value': 'camus'},
{'type': 'String', 'value': 'einstein'},
{'type': 'String', 'value': 'insert'},
"constants": [
{"type": "String", "value": "jenkins"},
{"type": "String", "value": "mercury"},
{"type": "String", "value": "lib_SHA1"},
{"type": "bool", "value": false},
{"type": "int", "value": 1},
{"type": "String", "value": "camus"},
{"type": "String", "value": "einstein"},
{"type": "String", "value": "insert"},
{
'type': 'list',
'value': [6, 7, 3]
"type": "list",
"value": [6, 7, 3]
},
{
'type': 'list',
'value': [5, 8, 6]
"type": "list",
"value": [5, 8, 6]
},
{'type': 'int', 'value': 0},
{'type': 'int', 'value': 99},
{"type": "int", "value": 0},
{"type": "int", "value": 99},
{
'type': 'map',
'value': {'key': 11}
"type": "map",
"value": {"key": 11}
},
{'type': 'int', 'value': 42},
{'type': 'Null'}
{"type": "int", "value": 42},
{"type": "Null"}
],
'calls': [
"calls": [
{
'definition': {
'id': 0,
'@': {'line': 12, 'column': 67},
'loadingUnit': 'part_15.js'
"definition": {
"id": 0,
"@": {"line": 12, "column": 67},
"loadingUnit": "part_15.js"
},
'references': [
"references": [
{
'arguments': {
'const': {
'positional': {'0': 2, '1': 3, '2': 4},
'named': {'leroy': 0, 'freddy': 1}
"arguments": {
"const": {
"positional": {"0": 2, "1": 3, "2": 4},
"named": {"leroy": 0, "freddy": 1}
}
},
'loadingUnit': 'o.js',
'@': {'uri': 1, 'line': 14, 'column': 49}
"loadingUnit": "o.js",
"@": {"uri": 1, "line": 14, "column": 49}
},
{
'arguments': {
'const': {
'positional': {'0': 2, '2': 10, '4': 12},
'named': {'leroy': 0, 'albert': 9}
"arguments": {
"const": {
"positional": {"0": 2, "2": 10, "4": 12},
"named": {"leroy": 0, "albert": 9}
},
'nonConst': {
'positional': [1],
'named': ['freddy']
"nonConst": {
"positional": [1],
"named": ["freddy"]
}
},
'loadingUnit': 'o.js',
'@': {'uri': 1, 'line': 14, 'column': 48}
"loadingUnit": "o.js",
"@": {"uri": 1, "line": 14, "column": 48}
}
]
}
],
'instances': [
"instances": [
{
'definition': {
'id': 1,
'@': {'line': 15, 'column': 30},
'loadingUnit': null
"definition": {
"id": 1,
"@": {"line": 15, "column": 30},
"loadingUnit": null
},
'references': [
"references": [
{
'instanceConstant': {
'fields': {'a': 13, 'b': 14}
"instanceConstant": {
"fields": {"a": 13, "b": 14}
},
'loadingUnit': '3',
'@': {'uri': 0, 'line': 40, 'column': 30}
"loadingUnit": "3",
"@": {"uri": 0, "line": 40, "column": 30}
}
]
}
]
};
}''';
final recordedUsesJson2 = '''{
"metadata": {
"comment": "Recorded usages of objects tagged with a `RecordUse` annotation",
"version": "0.1.0"
},
"uris": [
"package:drop_dylib_recording/src/drop_dylib_recording.dart",
"drop_dylib_recording_shake.dart"
],
"ids": [
{
"uri": 0,
"name": "getMathMethod"
}
],
"constants": [
{
"type": "String",
"value": "add"
}
],
"calls": [
{
"definition": {
"id": 0,
"@": {
"line": 10,
"column": 6
},
"loadingUnit": "1"
},
"references": [
{
"arguments": {
"const": {
"positional": {
"0": 0
}
}
},
"loadingUnit": "1",
"@": {
"uri": 1,
"line": 8,
"column": 3
}
}
]
}
]
}''';

View file

@ -2,6 +2,8 @@
// 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:convert';
import 'package:record_use/record_use_internal.dart';
import 'package:test/test.dart';
@ -10,17 +12,22 @@ import 'test_data.dart';
void main() {
test('All API calls', () {
expect(
RecordedUsages.fromJson(recordedUsesJson).argumentsTo(callId),
RecordedUsages.fromJson(
jsonDecode(recordedUsesJson) as Map<String, dynamic>)
.argumentsTo(callId),
recordedUses.calls.expand((e) => e.references).map((e) => e.arguments),
);
});
test('All API instances', () {
final references =
recordedUses.instances.expand((instance) => instance.references);
final instances =
RecordedUsages.fromJson(recordedUsesJson).instancesOf(instanceId);
final instances = RecordedUsages.fromJson(
jsonDecode(recordedUsesJson) as Map<String, dynamic>)
.instancesOf(instanceId);
expect(instances, references);
});
test('Specific API calls', () {
final callId = Identifier(
uri: Uri.parse('file://lib/_internal/js_runtime/lib/js_helper.dart')
@ -28,8 +35,10 @@ void main() {
parent: 'MyClass',
name: 'get:loadDeferredLibrary',
);
final arguments =
RecordedUsages.fromJson(recordedUsesJson).argumentsTo(callId)!.toList();
final arguments = RecordedUsages.fromJson(
jsonDecode(recordedUsesJson) as Map<String, dynamic>)
.argumentsTo(callId)!
.toList();
expect(
arguments[0].constArguments.named,
const {
@ -71,7 +80,10 @@ void main() {
name: 'MyAnnotation',
);
expect(
RecordedUsages.fromJson(recordedUsesJson).instancesOf(instanceId)?.first,
RecordedUsages.fromJson(
jsonDecode(recordedUsesJson) as Map<String, dynamic>)
.instancesOf(instanceId)
?.first,
InstanceReference(
instanceConstant: const InstanceConstant(
fields: {'a': IntConstant(42), 'b': NullConstant()},
@ -81,4 +93,17 @@ void main() {
),
);
});
test('HasNonConstInstance', () {
final id = const Identifier(
uri: 'package:drop_dylib_recording/src/drop_dylib_recording.dart',
name: 'getMathMethod',
);
expect(
RecordedUsages.fromJson(
jsonDecode(recordedUsesJson2) as Map<String, dynamic>)
.hasNonConstArguments(id),
false);
});
}

View file

@ -0,0 +1 @@
bin/drop_dylib_link/

View file

@ -0,0 +1,25 @@
This sample builds a native library for adding and multiplying. It then uses
the recorded usages feature to tree-shake unused libraries out.
## Usage:
### Keep all:
```
devdart --enable-experiment=native-assets,record-use build bin/drop_dylib_recording_all.dart
```
The `lib/` folder now contains both libraries
```
./bin/drop_dylib_recording_all/drop_dylib_recording_all.exe add
```
Prints `Hello world: 7!`
### Treeshake:
```
devdart --enable-experiment=native-assets,record-use build bin/drop_dylib_recording_shake.dart
```
The `lib/` folder now contains only the `add` library.
```
./bin/drop_dylib_recording_shake/drop_dylib_recording_shake.exe
```
Prints `Hello world: 7!`

View file

@ -2,15 +2,8 @@
// 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 ResourceIdentifier;
import 'package:drop_dylib_recording/drop_dylib_recording.dart';
void main() {
print(SomeClass.setMetadata(42));
}
class SomeClass {
@ResourceIdentifier({'a set'})
static setMetadata(int i) {
return i + 1;
}
void main(List<String> arguments) {
getMathMethod(arguments.first);
}

View file

@ -0,0 +1,9 @@
// 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:drop_dylib_recording/drop_dylib_recording.dart';
void main(List<String> arguments) {
getMathMethod('add');
}

View file

@ -0,0 +1,45 @@
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:logging/logging.dart';
import 'package:native_assets_cli/native_assets_cli.dart';
import 'package:native_toolchain_c/native_toolchain_c.dart';
void main(List<String> arguments) async {
await build(arguments, (config, output) async {
final logger = Logger('')
..level = Level.ALL
..onRecord.listen((record) {
print('${record.level.name}: ${record.time}: ${record.message}');
});
final linkInPackage = config.linkingEnabled ? config.packageName : null;
await CBuilder.library(
name: 'add',
assetName: 'dylib_add',
sources: [
'src/native_add.c',
],
linkModePreference: LinkModePreference.dynamic,
).run(
config: config,
output: output,
logger: logger,
linkInPackage: linkInPackage,
);
await CBuilder.library(
name: 'multiply',
assetName: 'dylib_multiply',
sources: [
'src/native_multiply.c',
],
linkModePreference: LinkModePreference.dynamic,
).run(
config: config,
output: output,
logger: logger,
linkInPackage: linkInPackage,
);
});
}

View file

@ -0,0 +1,64 @@
// 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 'dart:convert';
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',
);
void main(List<String> arguments) async {
await link(arguments, (config, output) async {
final file = File.fromUri(config.recordedUsagesFile!);
final string = await file.readAsString();
final usages =
RecordedUsages.fromJson(jsonDecode(string) as Map<String, dynamic>);
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);
output.addAssets(
config.assets.where((asset) => asset.id.endsWith(symbol)),
);
}
}
print('''
Keeping only ${output.assets.map((e) => e.id)}.
''');
output.addDependency(config.packageRoot.resolve('hook/link.dart'));
});
}

View file

@ -0,0 +1,5 @@
// 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.
export 'src/drop_dylib_recording.dart';

View file

@ -0,0 +1,24 @@
// 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';
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 {
static int add(int a, int b) => bindings.add(a, b);
static int multiply(int a, int b) => bindings.multiply(a, b);
}

View file

@ -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 'dart:ffi' as ffi;
@ffi.Native<ffi.Int32 Function(ffi.Int32, ffi.Int32)>(
assetId: 'package:drop_dylib_recording/dylib_add')
external int add(
int a,
int b,
);
@ffi.Native<ffi.Int32 Function(ffi.Int32, ffi.Int32)>(
assetId: 'package:drop_dylib_recording/dylib_multiply')
external int multiply(
int a,
int b,
);

View file

@ -0,0 +1,27 @@
name: drop_dylib_recording
description: Generate two dylibs, remove one in linking based on recorded usage.
version: 1.0.0
publish_to: none
environment:
sdk: ^3.0.0
dependencies:
logging: ^1.1.1
meta: any
native_assets_cli:
path: ../../../../third_party/pkg/native/pkgs/native_assets_cli/
native_toolchain_c:
path: ../../../../third_party/pkg/native/pkgs/native_toolchain_c/
record_use:
path: ../../../record_use/
dev_dependencies:
lints: any
test: any
dependency_overrides:
meta:
path: ../../../meta/

View file

@ -0,0 +1,9 @@
// 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.
#include "native_add.h"
MYLIB_EXPORT int32_t add(int32_t a, int32_t b) {
return a + b;
}

View file

@ -0,0 +1,13 @@
// 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.
#include <stdint.h>
#if _WIN32
#define MYLIB_EXPORT __declspec(dllexport)
#else
#define MYLIB_EXPORT
#endif
MYLIB_EXPORT int32_t add(int32_t a, int32_t b);

View file

@ -0,0 +1,9 @@
// 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.
#include "native_multiply.h"
MYLIB_EXPORT intptr_t multiply(intptr_t a, intptr_t b) {
return a * b;
}

View file

@ -0,0 +1,13 @@
// 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.
#include <stdint.h>
#if _WIN32
#define MYLIB_EXPORT __declspec(dllexport)
#else
#define MYLIB_EXPORT
#endif
MYLIB_EXPORT intptr_t multiply(intptr_t a, intptr_t b);

View file

@ -0,0 +1,26 @@
- 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/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

View file

@ -61,7 +61,7 @@ import 'transformations/no_dynamic_invocations_annotator.dart'
as no_dynamic_invocations_annotator show transformComponent;
import 'transformations/obfuscation_prohibitions_annotator.dart'
as obfuscationProhibitions;
import 'transformations/resource_identifier.dart' as resource_identifier;
import 'transformations/record_use/record_use.dart' as record_use;
import 'transformations/to_string_transformer.dart' as to_string_transformer;
import 'transformations/type_flow/transformer.dart' as globalTypeFlow
show transformComponent;
@ -113,8 +113,8 @@ void declareCompilerOptions(ArgParser args) {
args.addOption('native-assets',
help:
'Provide the native-assets mapping for @Native external functions.');
args.addOption('resources-file',
help: 'The path to store the collected usages of resource identifiers.');
args.addOption('recorded-usages-file',
help: 'The path to store the recorded usages.');
args.addOption('target',
help: 'Target model that determines what core libraries are available',
allowed: <String>['vm', 'flutter', 'flutter_runner', 'dart_runner'],
@ -205,7 +205,7 @@ Future<int> runCompiler(ArgResults options, String usage) async {
}
final String? nativeAssetsPath = options['native-assets'];
final String? resourcesFilePath = options['resources-file'];
final String? recordedUsagesFile = options['recorded-usages-file'];
final bool splitOutputByPackages = options['split-output-by-packages'];
final String? input = options.rest.singleOrNull;
if ((input == null && (nativeAssetsPath == null || splitOutputByPackages)) ||
@ -293,8 +293,8 @@ Future<int> runCompiler(ArgResults options, String usage) async {
final Uri? nativeAssetsUri =
nativeAssetsPath == null ? null : resolveInputUri(nativeAssetsPath);
final Uri? resourcesFileUri =
resourcesFilePath == null ? null : resolveInputUri(resourcesFilePath);
final Uri? recordedUsagesUri =
recordedUsagesFile == null ? null : resolveInputUri(recordedUsagesFile);
final String? dynamicInterfaceFilePath = options['dynamic-interface'];
final Uri? dynamicInterfaceUri = dynamicInterfaceFilePath == null
@ -340,7 +340,7 @@ Future<int> runCompiler(ArgResults options, String usage) async {
options: compilerOptions,
additionalSources: additionalSources,
nativeAssets: nativeAssetsUri,
resourcesFile: resourcesFileUri,
recordedUsages: recordedUsagesUri,
includePlatform: additionalDills.isNotEmpty,
deleteToStringPackageUris: options['delete-tostring-package-uri'],
keepClassNamesImplementing: options['keep-class-names-implementing'],
@ -449,7 +449,7 @@ class KernelCompilationArguments {
final CompilerOptions? options;
final List<Uri> additionalSources;
final Uri? nativeAssets;
final Uri? resourcesFile;
final Uri? recordedUsages;
final bool requireMain;
final bool includePlatform;
final List<String> deleteToStringPackageUris;
@ -471,7 +471,7 @@ class KernelCompilationArguments {
this.options,
this.additionalSources = const <Uri>[],
this.nativeAssets,
this.resourcesFile,
this.recordedUsages,
this.requireMain = true,
this.includePlatform = false,
this.deleteToStringPackageUris = const <String>[],
@ -660,9 +660,10 @@ Future runGlobalTransformations(Target target, Component component,
deferred_loading.transformComponent(component, coreTypes, target);
final resourcesFile = args.resourcesFile;
if (resourcesFile != null) {
resource_identifier.transformComponent(component, resourcesFile);
final recordedUsagesFile = args.recordedUsages;
if (recordedUsagesFile != null) {
assert(args.source != null);
record_use.transformComponent(component, recordedUsagesFile, args.source!);
}
}

View file

@ -0,0 +1,148 @@
// 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:collection/collection.dart';
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 StaticCallRecorder {
final Map<ast.Procedure, Usage<CallReference>> callsForMethod = {};
final List<LoadingUnit> _loadingUnits;
final Uri source;
StaticCallRecorder(this.source, this._loadingUnits);
void recordStaticCall(ast.StaticInvocation node) {
final annotations = recordUse.findRecordUseAnnotation(node.target);
if (annotations.isNotEmpty) {
final call = _getCall(node.target);
// Collect the (int, bool, double, or String) arguments passed in the call.
call.references.add(_createCallReference(node));
}
}
void recordTearoff(ast.ConstantExpression node) {
final constant = node.constant;
if (constant is ast.StaticTearOffConstant) {
final annotations = recordUse.findRecordUseAnnotation(constant.target);
if (annotations.isNotEmpty) {
final call = _getCall(constant.target);
CallReference reference = _collectTearOff(constant, node);
call.references.add(reference);
}
}
}
/// Record a tear off as a call with all non-const arguments.
CallReference _collectTearOff(
ast.StaticTearOffConstant constant,
ast.TreeNode node,
) {
final function = constant.target.function;
final nonConstArguments = NonConstArguments(
named:
function.namedParameters.map((parameter) => parameter.name!).toList(),
positional: List.generate(
function.positionalParameters.length,
(index) => index,
),
);
return CallReference(
location: node.location!.recordLocation(getIdentifierUri(
enclosingLibrary(node)!,
source,
)),
arguments: Arguments(nonConstArguments: nonConstArguments),
);
}
/// Collect the name and definition location of the invocation. This is
/// shared across multiple calls to the same method.
Usage _getCall(ast.Procedure target) {
final definition = _definitionFromMember(target);
return callsForMethod.putIfAbsent(
target,
() => Usage(definition: definition, references: []),
);
}
CallReference _createCallReference(ast.StaticInvocation node) {
// Get rid of the artificial `this` argument for extension methods.
final int argumentStart;
if (node.target.isExtensionMember || node.target.isExtensionTypeMember) {
argumentStart = 1;
} else {
argumentStart = 0;
}
final positionalArguments = node.arguments.positional
.skip(argumentStart)
.mapIndexed((i, argument) => MapEntry(i, _evaluateLiteral(argument)));
final namedArguments = node.arguments.named.map(
(argument) => MapEntry(argument.name, _evaluateLiteral(argument.value)),
);
// Group by the evaluated literal - if it exists, the argument was const.
final positionalGrouped = _groupByNull(positionalArguments);
final namedGrouped = _groupByNull(namedArguments);
return CallReference(
location: node.location!.recordLocation(getIdentifierUri(
enclosingLibrary(node)!,
source,
)),
loadingUnit: loadingUnitForNode(node, _loadingUnits).toString(),
arguments: Arguments(
constArguments: ConstArguments(
positional: positionalGrouped[false] != null
? Map.fromEntries(positionalGrouped[false]!
.map((e) => MapEntry(e.key, e.value!)))
: null,
named: namedGrouped[false] != null
? Map.fromEntries(
namedGrouped[false]!.map((e) => MapEntry(e.key, e.value!)))
: null,
),
nonConstArguments: NonConstArguments(
positional: positionalGrouped[true]?.map((e) => e.key).toList(),
named: namedGrouped[true]?.map((e) => e.key).toList(),
),
),
);
}
Map<bool, List<MapEntry<T, Constant?>>> _groupByNull<T>(
Iterable<MapEntry<T, Constant?>> arguments) =>
groupBy(arguments, (entry) => entry.value == null);
Constant? _evaluateLiteral(ast.Expression expression) {
if (expression is ast.BasicLiteral) {
return evaluateLiteral(expression);
} else if (expression is ast.ConstantExpression) {
return evaluateConstant(expression.constant);
} else {
return null;
}
}
Definition _definitionFromMember(ast.Member target) {
final enclosingLibrary = target.enclosingLibrary;
String file = getIdentifierUri(enclosingLibrary, source);
return Definition(
identifier: Identifier(
uri: file,
parent: target.enclosingClass?.name,
name: target.name.text,
),
location: target.location!.recordLocation(file),
loadingUnit:
loadingUnitForNode(enclosingLibrary, _loadingUnits).toString(),
);
}
}

View file

@ -0,0 +1,164 @@
// 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 'dart:convert';
import 'dart:io';
import 'package:_fe_analyzer_shared/src/util/relativize.dart'
show relativizeUri;
import 'package:collection/collection.dart';
import 'package:kernel/ast.dart' as ast;
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';
/// Collect calls to methods annotated with `@RecordUse`.
///
/// Identify and collect all calls to static methods annotated in the given
/// [component]. This requires the deferred loading to be handled already to
/// also save which loading unit the call is made in. Write the result into a
/// JSON at [recordedUsagesFile].
///
/// The purpose of this feature is to be able to pass the recorded information
/// to packages in a post-compilation step, allowing them to remove or modify
/// assets based on the actual usage in the code prior to bundling in the final
/// application.
ast.Component transformComponent(
ast.Component component,
Uri recordedUsagesFile,
Uri source,
) {
final tag = LoadingUnitsMetadataRepository.repositoryTag;
final loadingMetadata =
component.metadata[tag] as LoadingUnitsMetadataRepository;
final loadingUnits = loadingMetadata.mapping[component]?.loadingUnits ?? [];
final staticCallRecorder = StaticCallRecorder(source, loadingUnits);
component.accept(_RecordUseVisitor(
staticCallRecorder,
));
final usages = _usages(staticCallRecorder.callsForMethod.values, []);
var usagesStorageFormat = usages.toJson();
File.fromUri(recordedUsagesFile).writeAsStringSync(
JsonEncoder.withIndent(' ').convert(usagesStorageFormat),
);
return component;
}
class _RecordUseVisitor extends ast.RecursiveVisitor {
final StaticCallRecorder staticCallRecorder;
_RecordUseVisitor(this.staticCallRecorder);
@override
void visitStaticInvocation(ast.StaticInvocation node) {
staticCallRecorder.recordStaticCall(node);
node.visitChildren(this);
}
@override
void visitConstantExpression(ast.ConstantExpression node) {
staticCallRecorder.recordTearoff(node);
super.visitConstantExpression(node);
}
}
UsageRecord _usages(
Iterable<Usage<CallReference>> calls,
Iterable<Usage<InstanceReference>> instances,
) {
return UsageRecord(
metadata: Metadata(
comment:
'Recorded usages of objects tagged with a `RecordUse` annotation',
version: Version(0, 1, 0),
),
calls: calls.toList(),
instances: instances.toList(),
);
}
Constant evaluateConstant(ast.Constant constant) => switch (constant) {
ast.NullConstant() => NullConstant(),
ast.BoolConstant() => BoolConstant(constant.value),
ast.IntConstant() => IntConstant(constant.value),
ast.DoubleConstant() => _unsupported('DoubleConstant'),
ast.StringConstant() => StringConstant(constant.value),
ast.SymbolConstant() => StringConstant(constant.name),
ast.MapConstant() => MapConstant(Map.fromEntries(
constant.entries.map((e) => MapEntry(
(e.key as ast.StringConstant).value, evaluateConstant(e.value))),
)),
ast.ListConstant() =>
ListConstant(constant.entries.map(evaluateConstant).toList()),
// The following are not supported, but theoretically could be, so they
// are listed explicitly here.
ast.InstanceConstant() => _unsupported('InstanceConstant'),
ast.AuxiliaryConstant() => _unsupported('AuxiliaryConstant'),
ast.SetConstant() => _unsupported('SetConstant'),
ast.RecordConstant() => _unsupported('RecordConstant'),
ast.InstantiationConstant() => _unsupported('InstantiationConstant'),
ast.TearOffConstant() => _unsupported('TearOffConstant'),
ast.TypedefTearOffConstant() => _unsupported('TypedefTearOffConstant'),
ast.TypeLiteralConstant() => _unsupported('TypeLiteralConstant'),
ast.UnevaluatedConstant() => _unsupported('UnevaluatedConstant'),
};
Constant evaluateLiteral(ast.BasicLiteral expression) => switch (expression) {
ast.NullLiteral() => NullConstant(),
ast.IntLiteral() => IntConstant(expression.value),
ast.BoolLiteral() => BoolConstant(expression.value),
ast.StringLiteral() => StringConstant(expression.value),
ast.DoubleLiteral() => _unsupported('DoubleLiteral'),
ast.BasicLiteral() => _unsupported(expression.runtimeType.toString()),
};
Never _unsupported(String constantType) =>
throw UnsupportedError('$constantType is not supported for recording.');
extension RecordUseLocation on ast.Location {
Location recordLocation(String uri) => Location(
uri: uri,
line: line,
column: column,
);
}
String getIdentifierUri(ast.Library library, Uri source) {
String file;
final importUri = library.importUri;
if (importUri.isScheme('file')) {
file = relativizeUri(source, library.fileUri, Platform.isWindows);
} else {
file = library.importUri.toString();
}
return file;
}
ast.Library? enclosingLibrary(ast.TreeNode node) {
while (node is! ast.Library) {
final parent = node.parent;
if (parent == null) return null;
node = parent;
}
return node;
}
int _loadingUnitForLibrary(
ast.Library library,
List<LoadingUnit> loadingUnits,
) {
final importUri = library.importUri.toString();
return loadingUnits
.firstWhereOrNull((unit) => unit.libraryUris.contains(importUri))
?.id ??
-1;
}
int loadingUnitForNode(ast.TreeNode node, List<LoadingUnit> loadingUnits) {
final library = enclosingLibrary(node)!;
return _loadingUnitForLibrary(library, loadingUnits);
}

View file

@ -1,314 +0,0 @@
// Copyright (c) 2023, 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:convert';
import 'dart:io';
import 'package:_fe_analyzer_shared/src/util/relativize.dart'
show relativizeUri;
import 'package:collection/collection.dart';
import 'package:front_end/src/api_prototype/resource_identifier.dart'
as ResourceIdentifiers;
import 'package:kernel/ast.dart';
import 'package:kernel/kernel.dart';
import 'package:vm/metadata/loading_units.dart';
/// Collect calls to methods annotated with `@ResourceIdentifier`.
///
/// Identify and collect all calls to static methods annotated in the given
/// [component]. This requires the deferred loading to be handled already to
/// also save which loading unit the call is made in. Write the result into a
/// JSON at [resourcesFile].
///
/// The purpose of this feature is to be able to pass the recorded information
/// to packages in a post-compilation step, allowing them to remove or modify
/// assets based on the actual usage in the code prior to bundling in the final
/// application.
Component transformComponent(Component component, Uri resourcesFile) {
final tag = LoadingUnitsMetadataRepository.repositoryTag;
final loadingMetadata =
component.metadata[tag] as LoadingUnitsMetadataRepository;
final loadingUnits = loadingMetadata.mapping[component]?.loadingUnits ?? [];
final visitor = _ResourceIdentifierVisitor(loadingUnits);
for (final library in component.libraries) {
library.visitChildren(visitor);
}
File.fromUri(resourcesFile).writeAsStringSync(_toJson(visitor.identifiers));
return component;
}
String _toJson(List<Identifier> identifiers) {
return JsonEncoder.withIndent(' ').convert({
'_comment': 'Resources referenced by annotated resource identifiers',
'AppTag': 'TBD',
'environment': {
'dart.tool.dart2js': false,
},
'identifiers': identifiers,
});
}
class _ResourceIdentifierVisitor extends RecursiveVisitor {
final List<Identifier> identifiers = [];
final List<LoadingUnit> _loadingUnits;
_ResourceIdentifierVisitor(this._loadingUnits);
@override
void visitStaticInvocation(StaticInvocation node) {
final annotations =
ResourceIdentifiers.findResourceAnnotations(node.target);
if (annotations.isNotEmpty) {
_collectCallInformation(node, _firstResourceId(annotations.first));
annotations.forEach(node.target.annotations.remove);
}
node.visitChildren(this);
}
/// In case a method has multiple `ResourceIdentifier` annotations, we just
/// take the first.
String _firstResourceId(InstanceConstant instance) {
final fields = instance.fieldValues;
final firstField = fields.entries.first;
final fieldValue = firstField.value;
return _evaluateConstant(fieldValue);
}
String _evaluateConstant(Constant fieldValue) {
if (fieldValue case NullConstant()) {
return '';
} else if (fieldValue case PrimitiveConstant()) {
return fieldValue.value.toString();
} else {
// TODO(https://dartbug.com/55407): Support Map and List.
return throw UnsupportedError(
'The type ${fieldValue.runtimeType} is not a '
'supported metadata type for `@ResourceIdentifier` annotations');
}
}
/// Collects all the information needed to transform [node].
void _collectCallInformation(StaticInvocation node, String resourceId) {
// Collect the name and definition location of the invocation. This is
// shared across multiple calls to the same method.
final identifier = _identifierOf(node, resourceId);
identifiers.add(identifier);
// Collect the call location and loading unit of the call.
final resourceFile = _resourceFile(node, identifier);
identifier.files.add(resourceFile);
// Collect the (int, bool, double, or String) arguments passed in the call.
final reference = _reference(node);
resourceFile.references.add(reference);
}
Identifier _identifierOf(StaticInvocation node, String resourceId) {
final identifierUri = relativizeUri(
Uri.base, node.target.enclosingLibrary.fileUri, Platform.isWindows);
return identifiers
.where((id) => id.name == node.name.text && id.uri == identifierUri)
.firstOrNull ??
Identifier(
name: node.name.text,
id: resourceId,
uri: identifierUri,
nonConstant: !node.isConst,
files: [],
);
}
static Library? _enclosingLibrary(TreeNode node) {
while (node is! Library) {
final parent = node.parent;
if (parent == null) return null;
node = parent;
}
return node;
}
ResourceFile _resourceFile(StaticInvocation node, Identifier identifier) {
final enclosingLibrary = _enclosingLibrary(node)!;
final importUri = enclosingLibrary.importUri.toString();
final id = _loadingUnits
.firstWhereOrNull(
(element) => element.libraryUris.contains(importUri))
?.id ??
-1;
final resourceFile =
identifier.files.firstWhereOrNull((element) => element.part == id);
return resourceFile ?? ResourceFile(part: id, references: []);
}
ResourceReference _reference(StaticInvocation node) {
// Get rid of the artificial `this` argument for extension methods.
final int argumentStart;
if (node.target.isExtensionMember || node.target.isExtensionTypeMember) {
argumentStart = 1;
} else {
argumentStart = 0;
}
final arguments = {
// TODO(mosuem): Support more than just literals here,
// by adding visitors for enum indices and other const expressions.
for (var i = argumentStart; i < node.arguments.positional.length; i++)
if (_evaluateLiteral(node.arguments.positional[i]) case var value?)
'${i + 1 - argumentStart}': value,
for (var argument in node.arguments.named)
if (_evaluateLiteral(argument.value) case var value?)
argument.name: value,
};
final location = node.location!;
return ResourceReference(
uri: relativizeUri(Uri.base, location.file, Platform.isWindows),
line: location.line,
column: location.column,
arguments: arguments,
);
}
static Object? _evaluateLiteral(Expression expression) =>
expression is BasicLiteral ? expression.value : null;
}
// TODO(mosum): Expose these classes externally, as they will have to be used
// when parsing the generated JSON file.
/// A method with a `@ResourceIdentifier` annotation.
///
/// Each identifier has a list of [ResourceReference]s (method invocations).
/// These references are organized per [ResourceFile].
class Identifier {
/// The uri of the library which contains [name].
final String uri;
// TODO(https://dartbug.com/55494): Add the surrounding class/extension.
// TODO(https://dartbug.com/55494): Support extension getters/setters.
// Or make fully qualitified, non-conflicting canonical names in another way.
/// The name of the method that has a `@ResourceIdentifier` annotation.
final String name;
// TODO(https://dartbug.com/55494): Rename to metadata?
/// The metadata field of the first `@ResourceIdentifier` annotation on this
/// method.
final String id;
// TODO(dacoharkes): Replace with `isConstant` or `isConst`.
/// Whether the method is not `const`.
final bool nonConstant;
final List<ResourceFile> files;
Identifier({
required this.name,
required this.id,
required this.uri,
required this.nonConstant,
required this.files,
});
Map<String, dynamic> toJson() {
return {
'name': name,
'id': id,
'uri': uri,
'nonConstant': nonConstant,
'files': files,
};
}
@override
String toString() {
return 'Identifier(name: $name, id: $id, uri: $uri, nonConstant: $nonConstant, files: $files)';
}
}
// TODO(https://dartbug.com/55494): Rename to loading unit. This 'File' refers
// to an output file, not a source file.
/// A loading unit.
///
/// With deferred loading, Dart is compiled into separate loading units.
///
/// [ResourceReference]s are in a loading unit. Knowing from which loading
/// unit a resource is used means that loading such resource can be deferred
/// to when that loading unit is loaded.
class ResourceFile {
/// Unique identifier for the loading unit.
///
/// Loading units are constructed by the Dart compiler based on the `deferred`
/// keyword. As such these parts are not stable.
///
/// By convention, these unique identifiers are integers in the VM backend.
final int part;
/// The invocations of a method with a `@ResourceIdentifier` annotation.
final List<ResourceReference> references;
ResourceFile({required this.part, required this.references});
Map<String, dynamic> toJson() {
return {
'part': part,
'references': references,
};
}
@override
String toString() => 'ResourceFile(part: $part, references: $references)';
}
/// An invocation of a method with a `@ResourceIdentifier` annotation.
class ResourceReference {
// TODO(https://dartbug.com/55494): Make source locations optional.
/// Library uri of the invocation.
final String uri;
// TODO(https://dartbug.com/55494): Make source locations optional.
/// Line number of the invocation.
final int line;
// TODO(https://dartbug.com/55494): Make source locations optional.
/// Column of the invocation.
final int column;
// TODO(https://dartbug.com/55494): Should positional arguments be 0 indexed?
/// The mapping from parameters to constant argument value.
///
/// The map only contains entries for the arguments which are constant. (Note
/// that `null` is a valid constant argument.)
///
/// For arguments to positional parameters, the keys in this map are
/// [int.toString] of the position, 1 indexed.
///
/// For arguments to named parameters, the keys in this map are the name of
/// the parameter.
final Map<String, Object?> arguments;
ResourceReference({
required this.uri,
required this.line,
required this.column,
required this.arguments,
});
Map<String, dynamic> toJson() {
return {
'@': {
'uri': uri,
'line': line,
'column': column,
},
...arguments,
};
}
@override
String toString() {
return 'ResourceReference(uri: $uri, line: $line, column: $column, arguments: $arguments)';
}
}

View file

@ -6,8 +6,7 @@ import 'package:kernel/ast.dart';
import 'package:kernel/core_types.dart';
import 'package:kernel/type_environment.dart';
import 'package:front_end/src/api_unstable/vm.dart' show isExtensionTypeThis;
import 'package:front_end/src/api_prototype/resource_identifier.dart'
as ResourceIdentifiers;
import 'package:front_end/src/api_prototype/record_use.dart' as RecordUse;
import 'analysis.dart';
import 'table_selector_assigner.dart';
@ -212,10 +211,10 @@ class _ParameterInfo {
}
/// Disable signature shaking for annotated methods, to prevent removal of
/// parameters. The consumers of resources.json expect constant argument
/// values to be present for all parameters.
/// parameters. The consumers of recorded_usages.json expect constant
/// argument values to be present for all parameters.
if (member is Procedure &&
ResourceIdentifiers.findResourceAnnotations(member).isNotEmpty) {
RecordUse.findRecordUseAnnotation(member).isNotEmpty) {
isChecked = true;
}
}

View file

@ -9,8 +9,7 @@ import 'dart:core' hide Type;
import 'package:front_end/src/api_prototype/static_weak_references.dart'
show StaticWeakReferences;
import 'package:front_end/src/api_prototype/resource_identifier.dart'
as ResourceIdentifiers;
import 'package:front_end/src/api_prototype/record_use.dart' as RecordUse;
import 'package:kernel/ast.dart' hide Statement, StatementVisitor;
import 'package:kernel/ast.dart' as ast show Statement;
import 'package:kernel/class_hierarchy.dart'
@ -279,7 +278,7 @@ class CleanupAnnotations extends RecursiveVisitor {
/// We do not want to eliminate
/// * `pragma`s
/// * Protobuf annotations
/// * `ResourceIdentifier` annotations
/// * `RecordUse` annotations
///
/// as we need these later in the pipeline.
bool _keepAnnotation(Expression annotation) {
@ -289,11 +288,8 @@ class CleanupAnnotations extends RecursiveVisitor {
final cls = constant.classNode;
final usesProtobufAnnotation =
protobufHandler?.usesAnnotationClass(cls) ?? false;
bool usesResourceIdentifier =
ResourceIdentifiers.isResourceIdentifier(cls);
return cls == pragmaClass ||
usesProtobufAnnotation ||
usesResourceIdentifier;
bool usesRecordUse = RecordUse.isRecordUse(cls);
return cls == pragmaClass || usesProtobufAnnotation || usesRecordUse;
}
}
return false;

View file

@ -15,7 +15,9 @@ dependencies:
crypto: any
front_end: any
kernel: any
record_use: any
package_config: any
pub_semver: any
yaml: any
# Use 'any' constraints here; we get our versions from the DEPS file.

View file

@ -36,9 +36,9 @@ void runTestCaseAot(Uri source, bool throws) async {
final nopErrorDetector = ErrorDetector();
var tempDir = Directory.systemTemp.createTempSync().path;
var resourcesFile = Uri(
var recordedUsagesFile = Uri(
scheme: 'file',
path: path.join(tempDir, 'resources.json'),
path: path.join(tempDir, 'recorded_usages.json'),
);
runGlobalTransformations(
target,
@ -49,7 +49,8 @@ void runTestCaseAot(Uri source, bool throws) async {
enableAsserts: false,
useProtobufTreeShakerV2: true,
treeShakeWriteOnlyFields: true,
resourcesFile: resourcesFile,
recordedUsages: recordedUsagesFile,
source: source,
));
verifyComponent(
@ -64,7 +65,7 @@ void runTestCaseAot(Uri source, bool throws) async {
compareResultWithExpectationsFile(source, actual, expectFilePostfix: '.aot');
compareResultWithExpectationsFile(
source,
File.fromUri(resourcesFile).readAsStringSync(),
File.fromUri(recordedUsagesFile).readAsStringSync(),
expectFilePostfix: '.json',
);
}
@ -72,9 +73,9 @@ void runTestCaseAot(Uri source, bool throws) async {
void main(List<String> args) {
assert(args.isEmpty || args.length == 1);
final filter = args.firstOrNull;
group('resource-identifier-transformations', () {
group('record-use-transformations', () {
final testCasesDir = Directory.fromUri(
_pkgVmDir.resolve('testcases/transformations/resource_identifier'));
_pkgVmDir.resolve('testcases/transformations/record_use/'));
for (var file in testCasesDir
.listSync(recursive: true, followLinks: false)

View file

@ -3,7 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
import 'package:meta/meta.dart' show ResourceIdentifier;
import 'package:meta/meta.dart' show RecordUse;
void main() {
print(OtherClass().someMethod(argument: 'argument!'));
@ -26,7 +26,7 @@ class OtherClass {
);
}
@ResourceIdentifier('myresourceid')
@RecordUse()
static Future<String> generate(AssetBundle bundle, List args, String string,
AnotherClass object, int index) async {
final message = await bundle.byIndex(string: string, index: index);

View file

@ -6,7 +6,7 @@ import "package:meta/meta.dart" as meta;
import "dart:collection" as col;
import "dart:async";
import "package:meta/meta.dart" show ResourceIdentifier;
import "package:meta/meta.dart" show RecordUse;
class OtherClass extends core::Object {
@ -33,7 +33,7 @@ class OtherClass extends core::Object {
[@vm.inferred-return-type.metadata=dart.async::_Future]
[@vm.unboxing-info.metadata=(b,b,b,b,i)->b]
@#C3
@#C2
static method generate([@vm.inferred-arg-type.metadata=#lib::AssetBundle] self::AssetBundle bundle, [@vm.inferred-arg-type.metadata=dart.core::_GrowableList<dynamic>] core::List<dynamic> args, [@vm.inferred-arg-type.metadata=dart.core::_OneByteString (value: "somestring")] core::String string, [@vm.inferred-arg-type.metadata=#lib::AnotherClass] self::AnotherClass object, [@vm.inferred-arg-type.metadata=dart.core::_Smi (value: 42)] core::int index) → asy::Future<core::String> async /* emittedValueType= core::String */ {
final self::Message message = await [@vm.direct-call.metadata=#lib::AssetBundle.byIndex] [@vm.inferred-type.metadata=!? (skip check)] bundle.{self::AssetBundle::byIndex}(){({required index: core::int, required string: core::String}) → self::Message} /* runtimeCheckType= asy::Future<self::Message> */ ;
return [@vm.direct-call.metadata=#lib::Message.generateString] [@vm.inferred-type.metadata=!? (skip check)] message.{self::Message::generateString}(args){(core::List<dynamic>, {required object: self::AnotherClass}) → asy::Future<core::String>};
@ -73,6 +73,5 @@ static method main() → void {
}
constants {
#C1 = "argument!"
#C2 = "myresourceid"
#C3 = meta::ResourceIdentifier {metadata:#C2}
#C2 = meta::RecordUse {}
}

View file

@ -0,0 +1,59 @@
{
"metadata": {
"comment": "Recorded usages of objects tagged with a `RecordUse` annotation",
"version": "0.1.0"
},
"uris": [
"complex.dart"
],
"ids": [
{
"uri": 0,
"parent": "OtherClass",
"name": "generate"
}
],
"constants": [
{
"type": "int",
"value": 42
}
],
"calls": [
{
"definition": {
"id": 0,
"@": {
"line": 30,
"column": 25
},
"loadingUnit": "1"
},
"references": [
{
"arguments": {
"const": {
"positional": {
"4": 0
}
},
"nonConst": {
"positional": [
0,
1,
2,
3
]
}
},
"loadingUnit": "1",
"@": {
"uri": 0,
"line": 20,
"column": 18
}
}
]
}
]
}

View file

@ -0,0 +1,9 @@
{
"metadata": {
"comment": "Recorded usages of objects tagged with a `RecordUse` annotation",
"version": "0.1.0"
},
"uris": [],
"ids": [],
"constants": []
}

View file

@ -2,7 +2,7 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:meta/meta.dart' show ResourceIdentifier;
import 'package:meta/meta.dart' show RecordUse;
void main() {
SomeClass().callWithArgs('42');
@ -11,7 +11,7 @@ void main() {
class SomeClass {}
extension on SomeClass {
@ResourceIdentifier('id')
@RecordUse()
void callWithArgs(String s) {
s += "suffix";
}

View file

@ -3,7 +3,7 @@ import self as self;
import "dart:core" as core;
import "package:meta/meta.dart" as meta;
import "package:meta/meta.dart" show ResourceIdentifier;
import "package:meta/meta.dart" show RecordUse;
class SomeClass extends core::Object {
synthetic constructor •() → self::SomeClass
@ -20,11 +20,10 @@ static method main() → void {
}
[@vm.inferred-return-type.metadata=dart.core::Null? (value: null)]
@#C2
@#C1
static extension-member method _extension#0|callWithArgs([@vm.inferred-arg-type.metadata=#lib::SomeClass] lowered final self::SomeClass #this, [@vm.inferred-arg-type.metadata=dart.core::_OneByteString (value: "42")] core::String s) → void {
s = [@vm.direct-call.metadata=dart.core::_StringBase.+] [@vm.inferred-type.metadata=!? (skip check)] s.{core::String::+}("suffix"){(core::String) → core::String};
}
constants {
#C1 = "id"
#C2 = meta::ResourceIdentifier {metadata:#C1}
#C1 = meta::RecordUse {}
}

View file

@ -0,0 +1,50 @@
{
"metadata": {
"comment": "Recorded usages of objects tagged with a `RecordUse` annotation",
"version": "0.1.0"
},
"uris": [
"extension.dart"
],
"ids": [
{
"uri": 0,
"name": "_extension#0|callWithArgs"
}
],
"constants": [
{
"type": "String",
"value": "42"
}
],
"calls": [
{
"definition": {
"id": 0,
"@": {
"line": 15,
"column": 8
},
"loadingUnit": "1"
},
"references": [
{
"arguments": {
"const": {
"positional": {
"0": 0
}
}
},
"loadingUnit": "1",
"@": {
"uri": 0,
"line": 8,
"column": 15
}
}
]
}
]
}

View file

@ -3,8 +3,8 @@ import self as self;
import "loading_units_multiple_helper_shared.dart" as loa;
import "loading_units_multiple_helper.dart" as loa2;
import "org-dartlang-test:///testcases/transformations/resource_identifier/loading_units_multiple_helper_shared.dart";
import "org-dartlang-test:///testcases/transformations/resource_identifier/loading_units_multiple_helper.dart" deferred as helper;
import "org-dartlang-test:///testcases/transformations/record_use/loading_units_multiple_helper_shared.dart";
import "org-dartlang-test:///testcases/transformations/record_use/loading_units_multiple_helper.dart" deferred as helper;
[@vm.inferred-return-type.metadata=dart.async::_Future]

View file

@ -0,0 +1,68 @@
{
"metadata": {
"comment": "Recorded usages of objects tagged with a `RecordUse` annotation",
"version": "0.1.0"
},
"uris": [
"loading_units_multiple_helper_shared.dart",
"loading_units_multiple.dart",
"loading_units_multiple_helper.dart"
],
"ids": [
{
"uri": 0,
"parent": "SomeClass",
"name": "someStaticMethod"
}
],
"constants": [
{
"type": "int",
"value": 42
}
],
"calls": [
{
"definition": {
"id": 0,
"@": {
"line": 9,
"column": 15
},
"loadingUnit": "1"
},
"references": [
{
"arguments": {
"const": {
"positional": {
"0": 0
}
}
},
"loadingUnit": "1",
"@": {
"uri": 1,
"line": 12,
"column": 13
}
},
{
"arguments": {
"const": {
"positional": {
"0": 0
}
}
},
"loadingUnit": "2",
"@": {
"uri": 2,
"line": 8,
"column": 13
}
}
]
}
]
}

View file

@ -2,9 +2,9 @@
// 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 ResourceIdentifier;
import 'package:meta/meta.dart' show RecordUse;
class SomeClass {
@ResourceIdentifier('id')
@RecordUse()
static void someStaticMethod(int i) {}
}

View file

@ -6,7 +6,7 @@
// Both are only used in their respective loading units.
// So, no dominant loading unit logic is applied.
import 'package:meta/meta.dart' show ResourceIdentifier;
import 'package:meta/meta.dart' show RecordUse;
import 'loading_units_simple_helper.dart' deferred as helper;
@ -19,6 +19,6 @@ void main() async {
}
class SomeClass {
@ResourceIdentifier('id')
@RecordUse()
static void someStaticMethod(int i) {}
}

View file

@ -4,14 +4,14 @@ import "dart:core" as core;
import "package:meta/meta.dart" as meta;
import "loading_units_simple_helper.dart" as loa;
import "package:meta/meta.dart" show ResourceIdentifier;
import "org-dartlang-test:///testcases/transformations/resource_identifier/loading_units_simple_helper.dart" deferred as helper;
import "package:meta/meta.dart" show RecordUse;
import "org-dartlang-test:///testcases/transformations/record_use/loading_units_simple_helper.dart" deferred as helper;
abstract class SomeClass extends core::Object {
[@vm.inferred-return-type.metadata=dart.core::Null? (value: null)]
[@vm.unboxing-info.metadata=(i)->b]
@#C2
@#C1
static method someStaticMethod([@vm.inferred-arg-type.metadata=dart.core::_Smi (value: 42)] core::int i) → void {}
}
@ -22,6 +22,5 @@ static method main() → void async /* emittedValueType= void */ {
let final dynamic #t1 = CheckLibraryIsLoaded(helper) in loa::invokeDeferred();
}
constants {
#C1 = "id"
#C2 = meta::ResourceIdentifier {metadata:#C1}
#C1 = meta::RecordUse {}
}

View file

@ -0,0 +1,84 @@
{
"metadata": {
"comment": "Recorded usages of objects tagged with a `RecordUse` annotation",
"version": "0.1.0"
},
"uris": [
"loading_units_simple.dart",
"loading_units_simple_helper.dart"
],
"ids": [
{
"uri": 0,
"parent": "SomeClass",
"name": "someStaticMethod"
},
{
"uri": 1,
"parent": "SomeClass",
"name": "someStaticMethod"
}
],
"constants": [
{
"type": "int",
"value": 42
}
],
"calls": [
{
"definition": {
"id": 0,
"@": {
"line": 23,
"column": 15
},
"loadingUnit": "1"
},
"references": [
{
"arguments": {
"const": {
"positional": {
"0": 0
}
}
},
"loadingUnit": "1",
"@": {
"uri": 0,
"line": 14,
"column": 13
}
}
]
},
{
"definition": {
"id": 1,
"@": {
"line": 13,
"column": 15
},
"loadingUnit": "2"
},
"references": [
{
"arguments": {
"const": {
"positional": {
"0": 0
}
}
},
"loadingUnit": "2",
"@": {
"uri": 1,
"line": 8,
"column": 13
}
}
]
}
]
}

View file

@ -2,13 +2,13 @@
// 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 ResourceIdentifier;
import 'package:meta/meta.dart' show RecordUse;
void invokeDeferred() {
SomeClass.someStaticMethod(42);
}
class SomeClass {
@ResourceIdentifier('id')
@RecordUse()
static void someStaticMethod(int i) {}
}

View file

@ -2,14 +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:meta/meta.dart' show ResourceIdentifier;
import 'package:meta/meta.dart' show RecordUse;
void main() {
print(SomeClass.someStaticMethod(42));
}
class SomeClass {
@ResourceIdentifier('id')
@RecordUse()
static someStaticMethod(int i) {
return i + 1;
}

View file

@ -3,13 +3,13 @@ import self as self;
import "dart:core" as core;
import "package:meta/meta.dart" as meta;
import "package:meta/meta.dart" show ResourceIdentifier;
import "package:meta/meta.dart" show RecordUse;
abstract class SomeClass extends core::Object {
[@vm.inferred-return-type.metadata=int]
[@vm.unboxing-info.metadata=(i)->i]
@#C2
@#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};
}
@ -20,6 +20,5 @@ static method main() → void {
core::print([@vm.inferred-type.metadata=int] self::SomeClass::someStaticMethod(42));
}
constants {
#C1 = "id"
#C2 = meta::ResourceIdentifier {metadata:#C1}
#C1 = meta::RecordUse {}
}

View file

@ -0,0 +1,51 @@
{
"metadata": {
"comment": "Recorded usages of objects tagged with a `RecordUse` annotation",
"version": "0.1.0"
},
"uris": [
"simple.dart"
],
"ids": [
{
"uri": 0,
"parent": "SomeClass",
"name": "someStaticMethod"
}
],
"constants": [
{
"type": "int",
"value": 42
}
],
"calls": [
{
"definition": {
"id": 0,
"@": {
"line": 13,
"column": 10
},
"loadingUnit": "1"
},
"references": [
{
"arguments": {
"const": {
"positional": {
"0": 0
}
}
},
"loadingUnit": "1",
"@": {
"uri": 0,
"line": 8,
"column": 19
}
}
]
}
]
}

View file

@ -1,15 +1,17 @@
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
// Copyright (c) 2023, 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 ResourceIdentifier;
import 'package:meta/meta.dart' show RecordUse;
void main() {
print(SomeClass.someStaticMethod(42));
print(m(SomeClass.someStaticMethod)(42));
}
Function m(Function f) => f;
class SomeClass {
@ResourceIdentifier()
@RecordUse()
static someStaticMethod(int i) {
return i + 1;
}

View file

@ -0,0 +1,29 @@
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;
abstract class SomeClass extends core::Object {
[@vm.inferred-return-type.metadata=int]
[@vm.unboxing-info.metadata=(b)->i]
@#C1
static method someStaticMethod(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.direct-call.metadata=closure 0 in #lib::SomeClass.someStaticMethod] [@vm.inferred-type.metadata=int (receiver not int)] [@vm.inferred-type.metadata=dart.core::_Closure (value: #lib::SomeClass.someStaticMethod) (closure 0 in #lib::SomeClass.someStaticMethod)] self::m()(42));
}
[@vm.inferred-return-type.metadata=dart.core::_Closure (value: #lib::SomeClass.someStaticMethod) (closure 0 in #lib::SomeClass.someStaticMethod)]
static method m() → core::Function
return #C2;
constants {
#C1 = meta::RecordUse {}
#C2 = static-tearoff self::SomeClass::someStaticMethod
}

View file

@ -0,0 +1,46 @@
{
"metadata": {
"comment": "Recorded usages of objects tagged with a `RecordUse` annotation",
"version": "0.1.0"
},
"uris": [
"tearoff.dart"
],
"ids": [
{
"uri": 0,
"parent": "SomeClass",
"name": "someStaticMethod"
}
],
"constants": [],
"calls": [
{
"definition": {
"id": 0,
"@": {
"line": 15,
"column": 10
},
"loadingUnit": "1"
},
"references": [
{
"arguments": {
"nonConst": {
"positional": [
0
]
}
},
"loadingUnit": null,
"@": {
"uri": 0,
"line": 11,
"column": 27
}
}
]
}
]
}

View file

@ -0,0 +1,14 @@
// Copyright (c) 2023, 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(someTopLevelMethod(42));
}
@RecordUse()
int someTopLevelMethod(int i) {
return i + 1;
}

View file

@ -0,0 +1,22 @@
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;
[@vm.inferred-return-type.metadata=dart.core::Null? (value: null)]
static method main() → void {
core::print([@vm.inferred-type.metadata=int] self::someTopLevelMethod(42));
}
[@vm.inferred-return-type.metadata=int]
[@vm.unboxing-info.metadata=(i)->i]
@#C1
static method someTopLevelMethod([@vm.inferred-arg-type.metadata=dart.core::_Smi (value: 42)] core::int i) → core::int {
return [@vm.direct-call.metadata=dart.core::_IntegerImplementation.+] [@vm.inferred-type.metadata=int (skip check)] i.{core::num::+}(1){(core::num) → core::int};
}
constants {
#C1 = meta::RecordUse {}
}

View file

@ -0,0 +1,50 @@
{
"metadata": {
"comment": "Recorded usages of objects tagged with a `RecordUse` annotation",
"version": "0.1.0"
},
"uris": [
"top_level_method.dart"
],
"ids": [
{
"uri": 0,
"name": "someTopLevelMethod"
}
],
"constants": [
{
"type": "int",
"value": 42
}
],
"calls": [
{
"definition": {
"id": 0,
"@": {
"line": 12,
"column": 5
},
"loadingUnit": "1"
},
"references": [
{
"arguments": {
"const": {
"positional": {
"0": 0
}
}
},
"loadingUnit": "1",
"@": {
"uri": 0,
"line": 8,
"column": 9
}
}
]
}
]
}

View file

@ -0,0 +1,23 @@
// 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(SomeClass.someStaticMethod(42));
print(SomeClass.someStaticMethod(null));
print(SomeClass.someStaticMethod('s'));
print(SomeClass.someStaticMethod(true));
print(SomeClass.someStaticMethod(const {
'a': ['a1', 'a2'],
'b': ['b1', 'b2'],
}));
print(SomeClass.someStaticMethod([true, false].first));
}
class SomeClass {
@RecordUse()
static String someStaticMethod(Object? a) => a.runtimeType.toString();
}

View file

@ -0,0 +1,36 @@
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;
abstract class SomeClass extends core::Object {
[@vm.inferred-return-type.metadata=!]
@#C1
static method someStaticMethod(core::Object? a) → core::String
return [@vm.direct-call.metadata=dart.core::_AbstractType.toString] [@vm.inferred-type.metadata=! (skip check)] [@vm.inferred-type.metadata=!] a.{core::Object::runtimeType}{<object>}.{core::Type}.{core::Type::toString}(){() → core::String};
}
[@vm.inferred-return-type.metadata=dart.core::Null? (value: null)]
static method main() → void {
core::print([@vm.inferred-type.metadata=!] self::SomeClass::someStaticMethod(42));
core::print([@vm.inferred-type.metadata=!] self::SomeClass::someStaticMethod(null));
core::print([@vm.inferred-type.metadata=!] self::SomeClass::someStaticMethod("s"));
core::print([@vm.inferred-type.metadata=!] self::SomeClass::someStaticMethod(true));
core::print([@vm.inferred-type.metadata=!] self::SomeClass::someStaticMethod(#C10));
core::print([@vm.inferred-type.metadata=!] self::SomeClass::someStaticMethod([@vm.direct-call.metadata=dart.core::_GrowableList.first] [@vm.inferred-type.metadata=dart.core::bool] [@vm.inferred-type.metadata=dart.core::_GrowableList<dart.core::bool>] core::_GrowableList::_literal2<core::bool>(true, false).{core::Iterable::first}{core::bool}));
}
constants {
#C1 = meta::RecordUse {}
#C2 = "a"
#C3 = "a1"
#C4 = "a2"
#C5 = <core::String>[#C3, #C4]
#C6 = "b"
#C7 = "b1"
#C8 = "b2"
#C9 = <core::String>[#C7, #C8]
#C10 = <core::String, core::List<core::String>>{#C2:#C5, #C6:#C9}
}

View file

@ -0,0 +1,174 @@
{
"metadata": {
"comment": "Recorded usages of objects tagged with a `RecordUse` annotation",
"version": "0.1.0"
},
"uris": [
"types_of_arguments.dart"
],
"ids": [
{
"uri": 0,
"parent": "SomeClass",
"name": "someStaticMethod"
}
],
"constants": [
{
"type": "int",
"value": 42
},
{
"type": "Null"
},
{
"type": "String",
"value": "s"
},
{
"type": "bool",
"value": true
},
{
"type": "String",
"value": "a1"
},
{
"type": "String",
"value": "a2"
},
{
"type": "list",
"value": [
4,
5
]
},
{
"type": "String",
"value": "b1"
},
{
"type": "String",
"value": "b2"
},
{
"type": "list",
"value": [
7,
8
]
},
{
"type": "map",
"value": {
"a": 6,
"b": 9
}
}
],
"calls": [
{
"definition": {
"id": 0,
"@": {
"line": 22,
"column": 17
},
"loadingUnit": "1"
},
"references": [
{
"arguments": {
"const": {
"positional": {
"0": 0
}
}
},
"loadingUnit": "1",
"@": {
"uri": 0,
"line": 8,
"column": 19
}
},
{
"arguments": {
"const": {
"positional": {
"0": 1
}
}
},
"loadingUnit": "1",
"@": {
"uri": 0,
"line": 9,
"column": 19
}
},
{
"arguments": {
"const": {
"positional": {
"0": 2
}
}
},
"loadingUnit": "1",
"@": {
"uri": 0,
"line": 10,
"column": 19
}
},
{
"arguments": {
"const": {
"positional": {
"0": 3
}
}
},
"loadingUnit": "1",
"@": {
"uri": 0,
"line": 11,
"column": 19
}
},
{
"arguments": {
"const": {
"positional": {
"0": 10
}
}
},
"loadingUnit": "1",
"@": {
"uri": 0,
"line": 12,
"column": 19
}
},
{
"arguments": {
"nonConst": {
"positional": [
0
]
}
},
"loadingUnit": "1",
"@": {
"uri": 0,
"line": 17,
"column": 19
}
}
]
}
]
}

View file

@ -1,30 +0,0 @@
{
"_comment": "Resources referenced by annotated resource identifiers",
"AppTag": "TBD",
"environment": {
"dart.tool.dart2js": false
},
"identifiers": [
{
"name": "generate",
"id": "myresourceid",
"uri": "pkg/vm/testcases/transformations/resource_identifier/complex.dart",
"nonConstant": true,
"files": [
{
"part": 1,
"references": [
{
"@": {
"uri": "pkg/vm/testcases/transformations/resource_identifier/complex.dart",
"line": 20,
"column": 18
},
"5": 42
}
]
}
]
}
]
}

View file

@ -1,34 +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 'package:meta/meta.dart' show ResourceIdentifier;
void main() {
print(SomeClass.stringMetadata(42));
print(SomeClass.doubleMetadata(42));
print(SomeClass.intMetadata(42));
print(SomeClass.boolMetadata(42));
}
class SomeClass {
@ResourceIdentifier('leroyjenkins')
static stringMetadata(int i) {
return i + 1;
}
@ResourceIdentifier(3.14)
static doubleMetadata(int i) {
return i + 1;
}
@ResourceIdentifier(42)
static intMetadata(int i) {
return i + 1;
}
@ResourceIdentifier(true)
static boolMetadata(int i) {
return i + 1;
}
}

View file

@ -1,55 +0,0 @@
library #lib;
import self as self;
import "dart:core" as core;
import "package:meta/meta.dart" as meta;
import "package:meta/meta.dart" show ResourceIdentifier;
abstract class SomeClass extends core::Object {
[@vm.inferred-return-type.metadata=int]
[@vm.unboxing-info.metadata=(i)->i]
@#C2
static method stringMetadata([@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=int]
[@vm.unboxing-info.metadata=(i)->i]
@#C4
static method doubleMetadata([@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=int]
[@vm.unboxing-info.metadata=(i)->i]
@#C6
static method intMetadata([@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=int]
[@vm.unboxing-info.metadata=(i)->i]
@#C8
static method boolMetadata([@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::stringMetadata(42));
core::print([@vm.inferred-type.metadata=int] self::SomeClass::doubleMetadata(42));
core::print([@vm.inferred-type.metadata=int] self::SomeClass::intMetadata(42));
core::print([@vm.inferred-type.metadata=int] self::SomeClass::boolMetadata(42));
}
constants {
#C1 = "leroyjenkins"
#C2 = meta::ResourceIdentifier {metadata:#C1}
#C3 = 3.14
#C4 = meta::ResourceIdentifier {metadata:#C3}
#C5 = 42
#C6 = meta::ResourceIdentifier {metadata:#C5}
#C7 = true
#C8 = meta::ResourceIdentifier {metadata:#C7}
}

View file

@ -1,93 +0,0 @@
{
"_comment": "Resources referenced by annotated resource identifiers",
"AppTag": "TBD",
"environment": {
"dart.tool.dart2js": false
},
"identifiers": [
{
"name": "stringMetadata",
"id": "leroyjenkins",
"uri": "pkg/vm/testcases/transformations/resource_identifier/complex_metadata.dart",
"nonConstant": true,
"files": [
{
"part": 1,
"references": [
{
"@": {
"uri": "pkg/vm/testcases/transformations/resource_identifier/complex_metadata.dart",
"line": 8,
"column": 19
},
"1": 42
}
]
}
]
},
{
"name": "doubleMetadata",
"id": "3.14",
"uri": "pkg/vm/testcases/transformations/resource_identifier/complex_metadata.dart",
"nonConstant": true,
"files": [
{
"part": 1,
"references": [
{
"@": {
"uri": "pkg/vm/testcases/transformations/resource_identifier/complex_metadata.dart",
"line": 9,
"column": 19
},
"1": 42
}
]
}
]
},
{
"name": "intMetadata",
"id": "42",
"uri": "pkg/vm/testcases/transformations/resource_identifier/complex_metadata.dart",
"nonConstant": true,
"files": [
{
"part": 1,
"references": [
{
"@": {
"uri": "pkg/vm/testcases/transformations/resource_identifier/complex_metadata.dart",
"line": 10,
"column": 19
},
"1": 42
}
]
}
]
},
{
"name": "boolMetadata",
"id": "true",
"uri": "pkg/vm/testcases/transformations/resource_identifier/complex_metadata.dart",
"nonConstant": true,
"files": [
{
"part": 1,
"references": [
{
"@": {
"uri": "pkg/vm/testcases/transformations/resource_identifier/complex_metadata.dart",
"line": 11,
"column": 19
},
"1": 42
}
]
}
]
}
]
}

View file

@ -1,8 +0,0 @@
{
"_comment": "Resources referenced by annotated resource identifiers",
"AppTag": "TBD",
"environment": {
"dart.tool.dart2js": false
},
"identifiers": []
}

View file

@ -1,25 +0,0 @@
library #lib;
import self as self;
import "dart:core" as core;
import "package:meta/meta.dart" as meta;
import "package:meta/meta.dart" show ResourceIdentifier;
abstract class SomeClass extends core::Object {
[@vm.inferred-return-type.metadata=int]
[@vm.unboxing-info.metadata=(i)->i]
@#C2
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 = null
#C2 = meta::ResourceIdentifier {metadata:#C1}
}

View file

@ -1,30 +0,0 @@
{
"_comment": "Resources referenced by annotated resource identifiers",
"AppTag": "TBD",
"environment": {
"dart.tool.dart2js": false
},
"identifiers": [
{
"name": "someStaticMethod",
"id": "",
"uri": "pkg/vm/testcases/transformations/resource_identifier/empty_metadata.dart",
"nonConstant": true,
"files": [
{
"part": 1,
"references": [
{
"@": {
"uri": "pkg/vm/testcases/transformations/resource_identifier/empty_metadata.dart",
"line": 8,
"column": 19
},
"1": 42
}
]
}
]
}
]
}

View file

@ -1,30 +0,0 @@
{
"_comment": "Resources referenced by annotated resource identifiers",
"AppTag": "TBD",
"environment": {
"dart.tool.dart2js": false
},
"identifiers": [
{
"name": "_extension#0|callWithArgs",
"id": "id",
"uri": "pkg/vm/testcases/transformations/resource_identifier/extension.dart",
"nonConstant": true,
"files": [
{
"part": 1,
"references": [
{
"@": {
"uri": "pkg/vm/testcases/transformations/resource_identifier/extension.dart",
"line": 8,
"column": 15
},
"1": "42"
}
]
}
]
}
]
}

View file

@ -1,77 +0,0 @@
{
"_comment": "Resources referenced by annotated resource identifiers",
"AppTag": "TBD",
"environment": {
"dart.tool.dart2js": false
},
"identifiers": [
{
"name": "someStaticMethod",
"id": "id",
"uri": "pkg/vm/testcases/transformations/resource_identifier/loading_units_multiple_helper_shared.dart",
"nonConstant": true,
"files": [
{
"part": 1,
"references": [
{
"@": {
"uri": "pkg/vm/testcases/transformations/resource_identifier/loading_units_multiple.dart",
"line": 12,
"column": 13
},
"1": 42
}
]
},
{
"part": 2,
"references": [
{
"@": {
"uri": "pkg/vm/testcases/transformations/resource_identifier/loading_units_multiple_helper.dart",
"line": 8,
"column": 13
},
"1": 42
}
]
}
]
},
{
"name": "someStaticMethod",
"id": "id",
"uri": "pkg/vm/testcases/transformations/resource_identifier/loading_units_multiple_helper_shared.dart",
"nonConstant": true,
"files": [
{
"part": 1,
"references": [
{
"@": {
"uri": "pkg/vm/testcases/transformations/resource_identifier/loading_units_multiple.dart",
"line": 12,
"column": 13
},
"1": 42
}
]
},
{
"part": 2,
"references": [
{
"@": {
"uri": "pkg/vm/testcases/transformations/resource_identifier/loading_units_multiple_helper.dart",
"line": 8,
"column": 13
},
"1": 42
}
]
}
]
}
]
}

Some files were not shown because too many files have changed in this diff Show more