[record_use] Only record definitions with a package: uri

Record only for definitions in libraries with a `package:` uri.

* Converts all the tests to be in packages.
  * Bonus: This removes the uri-mapping in equality checks.
  * These packages are added to the root pub workspace.
  * For the dart2js tests we need to do some juggling to keep the
    tests files in memory.
* Only support `package:` uris
  * Removed all the `relativizeUri` code for file paths.
  * Start emitting errors on non `package:` uris with a `@RecordUse()`
    annotation.

Unrelated cleanups:

* We no longer support recording const instances in annotations, this
  PR cleans up code in `kernel` and `type_flow`.

TEST=pkg/compiler/test/record_use/record_use_test.dart
TEST=pkg/dart2wasm/test/record_use_test.dart
TEST=pkg/vm/test/transformations/record_use_test.dart

Closes: https://github.com/dart-lang/native/issues/2891
Change-Id: I1bc6905291230375e185930d2c000700ac778f85
Cq-Include-Trybots: luci.dart.try:dart2wasm-asserts-linux-chrome-try,dart2wasm-asserts-minified-linux-d8-try,dart2wasm-linux-chrome-try,dart2wasm-linux-d8-try,dart2wasm-linux-firefox-try,dart2wasm-linux-jscm-chrome-try,dart2wasm-linux-optimized-jsc-try,pkg-linux-debug-try,pkg-linux-release-arm64-try,pkg-linux-release-try,pkg-mac-release-arm64-try,pkg-mac-release-try,pkg-win-release-arm64-try,pkg-win-release-try,dart2js-canary-linux-try,dart2js-hostasserts-linux-d8-try,dart2js-linux-chrome-try,dart2js-linux-firefox-try,dart2js-mac-chrome-try,dart2js-mac-safari-try,dart2js-minified-csp-linux-chrome-try,dart2js-minified-linux-d8-try,dart2js-unit-linux-x64-release-try,dart2js-win-chrome-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/478920
Reviewed-by: Nate Biggs <natebiggs@google.com>
Commit-Queue: Daco Harkes <dacoharkes@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
This commit is contained in:
Daco Harkes
2026-02-06 09:27:13 -08:00
committed by Commit Queue
parent 277119b883
commit 1cab2a8617
124 changed files with 465 additions and 191 deletions
@@ -10,11 +10,7 @@
/// null, List, Map, or constant objects.
library;
import 'dart:io';
import 'package:compiler/src/elements/entities.dart';
// ignore: implementation_imports
import 'package:front_end/src/api_unstable/dart2js.dart' show relativizeUri;
import 'package:compiler/src/deferred_load/output_unit.dart';
import 'package:compiler/src/js_model/js_world.dart';
import 'package:record_use/record_use_internal.dart';
@@ -50,6 +46,9 @@ class RecordUseCollector {
}
void _register(String loadingUnit, RecordedUse recordedUse) {
if (!recordedUse.function.library.canonicalUri.isScheme('package')) {
return;
}
final callReference = switch (recordedUse) {
RecordedCallWithArguments() => CallWithArguments(
loadingUnit: loadingUnit,
@@ -76,11 +75,7 @@ class RecordUseCollector {
identifier: Identifier(
name: key.name!,
scope: key.enclosingClass?.name,
importUri: relativizeUri(
Uri.base,
key.library.canonicalUri,
Platform.isWindows,
),
importUri: key.library.canonicalUri.toString(),
),
loadingUnit:
outputUnitToName[_closedWorld.outputUnitData.outputUnitForMember(
@@ -4,6 +4,7 @@
import 'dart:js_interop';
// ignore: experimental_member_use
import 'package:meta/meta.dart' show RecordUse;
void main() {
@@ -11,5 +12,6 @@ void main() {
}
@JS()
// ignore: experimental_member_use
@RecordUse()
external int someExternalFunction(int k);
@@ -0,0 +1,14 @@
# Copyright (c) 2026, 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.
name: record_use_js_test
publish_to: none
resolution: workspace
environment:
sdk: ^3.12.0-0
dependencies:
meta: any
+123 -31
View File
@@ -3,10 +3,9 @@
// BSD-style license that can be found in the LICENSE file.
import 'dart:convert';
import 'dart:io' show Directory, File, Platform;
import 'dart:io' show Directory, File;
import 'package:compiler/compiler_api.dart' as api show OutputType;
import 'package:compiler/compiler_api.dart';
import 'package:compiler/compiler_api.dart' as api;
import 'package:compiler/src/commandline_options.dart' show Flags;
import 'package:compiler/src/util/memory_compiler.dart';
import 'package:expect/expect.dart' show Expect;
@@ -23,37 +22,82 @@ const List<String> compilerOptions = [Flags.writeRecordedUses, Flags.testMode];
/// Run `dart -DupdateExpectations=true pkg/vm/test/transformations/record_use_test.dart`
/// to update the shared expectations to the VM output.
Future<void> main() async {
final vmTestCases = Directory('pkg/vm/testcases/transformations/record_use');
final jsTestCases = Directory.fromUri(Platform.script.resolve('data'));
final testFiles = [...jsTestCases.listSync(), ...vmTestCases.listSync()]
.whereType<File>()
.where((file) => file.path.endsWith('.dart'))
.map(
(file) => TestFile(
file: file,
basename: path.basename(file.path),
contents: file.readAsStringSync(),
uri: _createUri(path.basename(file.path)),
),
);
final vmFiles = _getTestFiles(
'pkg/vm/testcases/transformations/record_use/lib',
'record_use_test',
);
final jsFiles = _getTestFiles(
'pkg/compiler/test/record_use/data/lib',
'record_use_js_test',
);
final allFiles = {for (final file in testFiles) file.uri.path: file.contents};
final testFiles = [...vmFiles, ...jsFiles];
final allFiles = {
for (final file in vmFiles)
'/record_use_test/lib/${file.basename}': file.contents,
for (final file in jsFiles)
'/record_use_js_test/lib/${file.basename}': file.contents,
'/.dart_tool/package_config.json': jsonEncode({
"configVersion": 2,
"packages": [
{
"name": "record_use_test",
"rootUri": "/record_use_test/",
"packageUri": "lib/",
"languageVersion": "3.9",
},
{
"name": "record_use_js_test",
"rootUri": "/record_use_js_test/",
"packageUri": "lib/",
"languageVersion": "3.9",
},
{
"name": "meta",
"rootUri": Directory.current.uri.resolve('pkg/meta/').toString(),
"packageUri": "lib/",
"languageVersion": "3.9",
},
],
}),
};
for (final testFile in testFiles.where((element) => element.hasMain)) {
final bool isThrowsTest = testFile.basename.contains('throws');
test(
'${testFile.file.path}',
skip: dart2jsNotSupported.contains(testFile.basename),
() async {
final diagnosticCollector = DiagnosticCollector();
final recordedUsages = await compileWithUsages(
entryPoint: testFile.uri,
memorySourceFiles: allFiles,
diagnosticHandler: diagnosticCollector,
expectSuccess: !isThrowsTest,
);
if (isThrowsTest) {
Expect.isTrue(recordedUsages == null);
final errors = diagnosticCollector.errors
.map((e) => e.text)
.join('\n');
if (testFile.basename.contains('invalid_location')) {
Expect.contains('RecordUse', errors);
Expect.contains(
'annotation cannot be placed on this element',
errors,
);
}
return;
}
final goldenFile = File(testFile.file.path + '.json.expect');
const update = bool.fromEnvironment('updateExpectations');
if (!goldenFile.existsSync() || update) {
await goldenFile.create();
await goldenFile.writeAsString(recordedUsages);
await goldenFile.writeAsString(recordedUsages!);
} else {
final actual = Recordings.fromJson(jsonDecode(recordedUsages));
final actual = Recordings.fromJson(jsonDecode(recordedUsages!));
final goldenContents = await goldenFile.readAsString();
final golden = Recordings.fromJson(jsonDecode(goldenContents));
final semanticEquals = actual.semanticEquals(
@@ -63,8 +107,6 @@ Future<void> main() async {
// Ensure test coverage of tear offs, add pragmas to prevent
// optimiations if necessary.
allowTearoffToStaticPromotion: false,
uriMapping: (String uri) =>
uri.replaceFirst('memory:sdk/tests/web/native/', ''),
loadingUnitMapping: (String unit) =>
const <String, String>{'out': '1', 'out_1': '2'}[unit] ?? unit,
);
@@ -80,6 +122,49 @@ Future<void> main() async {
},
);
}
test('outside_package_throws', () async {
final entryPoint = Uri.parse('memory:/outside.dart');
final memorySourceFiles = {
...allFiles,
'/outside.dart': '''
import 'package:meta/meta.dart' show RecordUse;
class SomeClass {
@RecordUse()
static String someStaticMethod(int a) => a.toString();
}
void main() {
print(SomeClass.someStaticMethod(42));
}
''',
};
final diagnosticCollector = DiagnosticCollector();
await compileWithUsages(
entryPoint: entryPoint,
memorySourceFiles: memorySourceFiles,
diagnosticHandler: diagnosticCollector,
expectSuccess: false,
);
final errors = diagnosticCollector.errors.map((e) => e.text).join('\n');
Expect.contains('RecordUse', errors);
Expect.contains('package:', errors);
});
}
Iterable<TestFile> _getTestFiles(String dirPath, String packageName) {
return Directory(dirPath)
.listSync()
.whereType<File>()
.where((file) => file.path.endsWith('.dart'))
.map(
(file) => TestFile(
file: file,
basename: path.basename(file.path),
contents: file.readAsStringSync(),
uri: Uri.parse('package:$packageName/${path.basename(file.path)}'),
packageName: packageName,
),
);
}
class TestFile {
@@ -87,12 +172,14 @@ class TestFile {
final String basename;
final String contents;
final Uri uri;
final String packageName;
const TestFile({
required this.file,
required this.basename,
required this.contents,
required this.uri,
required this.packageName,
});
bool get hasMain => contents.contains('main()');
@@ -100,30 +187,35 @@ class TestFile {
typedef CompiledOutput = Map<api.OutputType, Map<String, String>>;
Future<String> compileWithUsages({
Future<String?> compileWithUsages({
Uri? entryPoint,
required Map<String, dynamic> memorySourceFiles,
api.CompilerDiagnostics? diagnosticHandler,
bool expectSuccess = true,
}) async {
final outputProvider = OutputCollector();
CompilationResult result = await runCompiler(
api.CompilationResult result = await runCompiler(
entryPoint: entryPoint,
memorySourceFiles: memorySourceFiles,
outputProvider: outputProvider,
diagnosticHandler: diagnosticHandler,
options: [Flags.writeRecordedUses],
packageConfig: Uri.parse('memory:/.dart_tool/package_config.json'),
);
Expect.isTrue(result.isSuccess);
if (expectSuccess) {
Expect.isTrue(result.isSuccess);
} else {
if (result.isSuccess) {
throw 'Compilation succeeded but was expected to fail.';
}
return null;
}
return outputProvider.outputMap[OutputType.recordedUses]!.values.first
return outputProvider.outputMap[api.OutputType.recordedUses]!.values.first
.toString();
}
// Pretend this is a dart2js_native test to allow use of 'native' keyword
// and import of private libraries.
Uri _createUri(String fileName) {
return Uri.parse('memory:sdk/tests/web/native/$fileName');
}
const dart2jsNotSupported = {
// No support for instance constants.
// https://github.com/dart-lang/native/issues/2893
+1 -2
View File
@@ -725,8 +725,7 @@ Future<CompilationResult> _runCodegenPhase(
return moduleOutput.moduleImportName;
}
record_use.transformComponent(
component, options.recordedUsesFile!, options.mainUri,
record_use.transformComponent(component, options.recordedUsesFile!,
loadingUnitLookup: loadingUnitForNode);
}
+103 -27
View File
@@ -12,49 +12,83 @@ import 'util.dart';
final Uri _pkgVmDir = Platform.script.resolve('../../vm/');
Future<void> runTestCase(Uri source) async {
Future<void> runTestCase(
Uri sourceFileUri,
Uri sourcePackageUri,
Uri packagesFileUri,
) async {
final bool isThrowsTest = sourceFileUri.path.contains('throws');
await withTempDir((String tempDir) async {
final recordedUsesFile = path.join(tempDir, 'recorded_usages.json');
await run([
final List<String> args = [
Platform.executable,
'compile',
'wasm',
'-O2',
source.toFilePath(),
'--packages=${packagesFileUri.toFilePath()}',
sourceFileUri.toFilePath(),
'-o',
path.join(tempDir, 'out.wasm'),
'--enable-deferred-loading',
'--extra-compiler-option=--recorded-uses=$recordedUsesFile',
]);
];
if (isThrowsTest) {
final result = await Process.run(args.first, args.skip(1).toList());
if (result.exitCode == 0) {
throw 'Compilation succeeded for $sourceFileUri but was expected to fail.';
}
final errors = '${result.stdout}\n${result.stderr}';
if (sourceFileUri.path.contains('invalid_location')) {
if (!errors.contains('RecordUse') ||
!errors.contains('cannot be placed on this element')) {
throw 'Wrong error message for $sourceFileUri:\n$errors';
}
}
return;
}
await run(args);
final actualSemantic = Recordings.fromJson(
jsonDecode(File(recordedUsesFile).readAsStringSync()),
);
final goldenFile = File('${source.toFilePath()}.json.expect');
final goldenContents = await goldenFile.readAsString();
final golden = Recordings.fromJson(jsonDecode(goldenContents));
final semanticEquals =
actualSemantic.semanticEquals(golden, loadingUnitMapping: (unit) {
final codeUnits = unit.codeUnits;
int result = 0;
int power = 1;
for (final codeUnit in codeUnits) {
result += (codeUnit - 35) * power;
power *= 92;
}
return '$result';
});
final goldenFile = File('${sourceFileUri.toFilePath()}.json.expect');
const update = bool.fromEnvironment('updateExpectations');
bool semanticEquals = false;
if (goldenFile.existsSync()) {
final goldenContents = await goldenFile.readAsString();
final golden = Recordings.fromJson(jsonDecode(goldenContents));
semanticEquals =
actualSemantic.semanticEquals(golden, loadingUnitMapping: (unit) {
final codeUnits = unit.codeUnits;
int result = 0;
int power = 1;
for (final codeUnit in codeUnits) {
result += (codeUnit - 35) * power;
power *= 92;
}
return '$result';
});
}
if (update && !semanticEquals) {
goldenFile.writeAsStringSync(jsonEncode(actualSemantic));
print('Updated expectations for $source');
goldenFile.writeAsStringSync(
JsonEncoder.withIndent(' ').convert(actualSemantic.toJson()),
);
print('Updated expectations for $sourceFileUri');
} else if (!semanticEquals) {
print('Actual: ${actualSemantic.toJson()}');
print('Expected: ${golden.toJson()}');
final encoder = JsonEncoder.withIndent(' ');
print('Actual:\n${encoder.convert(actualSemantic.toJson())}');
if (goldenFile.existsSync()) {
final goldenContents = await goldenFile.readAsString();
print('Expected:\n$goldenContents');
}
print('To update expectations, run: dart -DupdateExpectations=true '
'pkg/dart2wasm/test/record_use_test.dart '
'${path.basename(source.toFilePath())}');
throw 'Expectations for $source do not match';
'${path.basename(sourceFileUri.toFilePath())}');
throw 'Expectations for $sourceFileUri do not match';
}
});
}
@@ -62,15 +96,57 @@ Future<void> runTestCase(Uri source) async {
Future<void> main(List<String> args) async {
assert(args.isEmpty || args.length == 1);
final filter = args.firstOrNull;
final testCasesDir = Directory.fromUri(
_pkgVmDir.resolve('testcases/transformations/record_use/'),
final recordUseTestDir = _pkgVmDir.resolve(
'testcases/transformations/record_use/',
);
final testCasesDir = Directory.fromUri(recordUseTestDir.resolve('lib/'));
final packagesFileUri = _pkgVmDir.resolve(
'../../.dart_tool/package_config.json',
);
for (var fse in testCasesDir.listSync(recursive: true, followLinks: false)) {
if (fse is! File) continue;
if (fse.path.endsWith('.dart') &&
!fse.path.contains('helper') &&
(filter == null || fse.path.contains(filter))) {
await runTestCase(fse.uri);
final name = path.basename(fse.path);
final packageUri = Uri.parse('package:record_use_test/$name');
await runTestCase(fse.uri, packageUri, packagesFileUri);
}
}
await runOutsidePackageThrows(packagesFileUri);
}
Future<void> runOutsidePackageThrows(Uri packagesFileUri) async {
await withTempDir((String tempDir) async {
final sourceFile = File(path.join(tempDir, 'outside.dart'));
sourceFile.writeAsStringSync('''
import 'package:meta/meta.dart' show RecordUse;
class SomeClass {
@RecordUse()
static String someStaticMethod(int a) => a.toString();
}
void main() {
print(SomeClass.someStaticMethod(42));
}
''');
final result = await Process.run(Platform.executable, [
'compile',
'wasm',
'-O2',
'--packages=${packagesFileUri.toFilePath()}',
sourceFile.path,
'-o',
path.join(tempDir, 'out.wasm'),
]);
if (result.exitCode == 0) {
throw 'Compilation succeeded but was expected to fail.';
}
final errors = '${result.stdout}\n${result.stderr}';
if (!errors.contains('RecordUse') || !errors.contains('package:')) {
throw 'Wrong error message for outside package test:\n$errors';
}
});
}
@@ -14996,6 +14996,13 @@ const MessageCode recordUseCannotBePlacedHere = const MessageCode(
"""`RecordUse` annotation cannot be placed on this element.""",
);
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const MessageCode recordUseOutsideOfPackage = const MessageCode(
"RecordUseOutsideOfPackage",
problemMessage:
"""`RecordUse` annotations are only supported in libraries with a `package:` URI.""",
);
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const MessageCode recordUsedAsCallable = const MessageCode(
"RecordUsedAsCallable",
+36 -8
View File
@@ -45,15 +45,32 @@ bool isRecordUse(Class cls) =>
cls.enclosingLibrary.importUri == _metaLibraryUri;
// Coverage-ignore(suite): Not run.
bool isBeingRecorded(Class cls) => isRecordUse(cls) || hasRecordUse(cls);
bool _enclosedInLibraryWithPackageUri(Annotatable node) {
final Library? library = switch (node) {
Library l => l,
Class c => c.enclosingLibrary,
Member m => m.enclosingLibrary,
_ => null,
};
return library?.importUri.isScheme('package') ?? false;
}
// Coverage-ignore(suite): Not run.
/// If [cls] annotation is in turn annotated by a recording annotation.
bool hasRecordUse(Class cls) => cls.annotations
.whereType<ConstantExpression>()
.map((e) => e.constant)
.whereType<InstanceConstant>()
.any((annotation) => isRecordUse(annotation.classNode));
bool isBeingRecorded(Annotatable node) {
final bool hasAnnotation = hasRecordUseAnnotation(node);
if (!hasAnnotation) return false;
return _enclosedInLibraryWithPackageUri(node);
}
// Coverage-ignore(suite): Not run.
Uri? _getFileUri(Annotatable node) {
if (node is Library) return node.fileUri;
if (node is Class) return node.fileUri;
if (node is Member) return node.fileUri;
return node.location?.file;
}
// Coverage-ignore(suite): Not run.
/// Report if the resource annotations is placed on anything but a static
@@ -63,6 +80,17 @@ void validateRecordUseDeclaration(
ErrorReporter errorReporter,
Iterable<InstanceConstant> resourceAnnotations,
) {
if (resourceAnnotations.isEmpty) return;
final Uri? fileUri = _getFileUri(node);
if (fileUri == null) return;
if (!_enclosedInLibraryWithPackageUri(node)) {
errorReporter.report(
diag.recordUseOutsideOfPackage.withLocation(fileUri, node.fileOffset, 1),
);
}
final bool onNonStaticMethod =
node is! Procedure || !node.isStatic || node.kind != ProcedureKind.Method;
@@ -72,7 +100,7 @@ void validateRecordUseDeclaration(
if (onNonStaticMethod && onClassWithoutConstConstructor) {
errorReporter.report(
diag.recordUseCannotBePlacedHere.withLocation(
node.location!.file,
fileUri,
node.fileOffset,
1,
),
+1
View File
@@ -140,6 +140,7 @@ front_end/PatchInjectionFailed/example: missingExample # Patching.
front_end/PatternMatchingError/example: missingExample # Seemingly this is not issued as an error, but ends up in the kernel AST.
front_end/PositionalAfterNamedArgument/example: missingExample # Seemingly only issued in Analyzer
front_end/RecordUseCannotBePlacedHere/example: missingExample # No coverage.
front_end/RecordUseOutsideOfPackage/example: missingExample # No coverage.
front_end/SdkRootNotFound/example: missingExample # Issued on what is essentially a wrong setup.
front_end/SdkSpecificationNotFound/example: missingExample # Issued on what is essentially a wrong setup.
front_end/SdkSummaryNotFound/example: missingExample # Issued on what is essentially a wrong setup.
+4
View File
@@ -7175,6 +7175,10 @@ recordUseCannotBePlacedHere:
parameters: none
problemMessage: "`RecordUse` annotation cannot be placed on this element."
recordUseOutsideOfPackage:
parameters: none
problemMessage: "`RecordUse` annotations are only supported in libraries with a `package:` URI."
wasmImportOrExportInUserCode:
parameters: none
problemMessage: "Pragmas `wasm:import` and `wasm:export` are for internal use only and cannot be used by user code."
+1 -1
View File
@@ -871,7 +871,7 @@ Future runGlobalTransformations(
final recordedUsagesFile = args.recordedUsages;
if (recordedUsagesFile != null) {
assert(args.source != null);
record_use.transformComponent(component, recordedUsagesFile, args.source!);
record_use.transformComponent(component, recordedUsagesFile);
}
}
@@ -3,7 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
import 'package:kernel/ast.dart';
import 'package:front_end/src/kernel/record_use.dart' as recordUse;
import 'package:front_end/src/kernel/record_use.dart' as record_use;
/// Expose only the [collect] method of a [_ConstantCollector] to outside use.
extension type ConstantCollector(_ConstantCollector _collector) {
@@ -79,7 +79,7 @@ class _ConstantCollector implements ConstantVisitor {
void visitInstanceConstant(InstanceConstant constant) {
assert(_expression != null);
final classNode = constant.classNode;
if (_hasRecordUseAnnotation[classNode] ??= recordUse.hasRecordUseAnnotation(
if (_hasRecordUseAnnotation[classNode] ??= record_use.isBeingRecorded(
classNode,
)) {
collector(_expression!, constant);
@@ -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:front_end/src/kernel/record_use.dart' as recordUse;
import 'package:front_end/src/kernel/record_use.dart' show isBeingRecorded;
import 'package:kernel/ast.dart' as ast;
import 'package:record_use/record_use_internal.dart';
import 'package:vm/transformations/record_use/record_use.dart';
@@ -24,18 +24,15 @@ class CallRecorder {
/// A function to look up the loading unit for a reference.
final LoadingUnitLookup _loadingUnitLookup;
/// The source uri to base relative URIs off of.
final Uri _source;
/// Whether to save line and column info as well as the URI.
//TODO(mosum): add verbose mode to enable this
bool exactLocation = false;
CallRecorder(this._source, this._loadingUnitLookup);
CallRecorder(this._loadingUnitLookup);
/// Will record a static invocation if it is annotated with `@RecordUse`.
void recordStaticInvocation(ast.StaticInvocation node) {
if (recordUse.hasRecordUseAnnotation(node.target)) {
if (isBeingRecorded(node.target)) {
// Collect the (int, bool, double, or String) arguments passed in the call.
final createCallReference = _createCallReference(node);
_addToUsage(node.target, createCallReference);
@@ -46,10 +43,7 @@ class CallRecorder {
void recordConstantExpression(ast.ConstantExpression node) {
final constant = node.constant;
if (constant is ast.StaticTearOffConstant) {
final hasRecordUseAnnotation = recordUse.hasRecordUseAnnotation(
constant.target,
);
if (hasRecordUseAnnotation) {
if (isBeingRecorded(constant.target)) {
_addToUsage(
constant.target,
CallTearoff(loadingUnit: _loadingUnitLookup(node)),
@@ -139,11 +133,11 @@ class CallRecorder {
ast.Member target,
) {
final enclosingLibrary = target.enclosingLibrary;
String file = getImportUri(enclosingLibrary, _source);
final importUri = enclosingLibrary.importUri.toString();
return (
identifier: Identifier(
importUri: file,
importUri: importUri,
scope: target.enclosingClass?.name,
name: target.name.text,
),
@@ -22,9 +22,6 @@ class InstanceRecorder {
/// A function to look up the loading unit for a reference.
final LoadingUnitLookup _loadingUnitLookup;
/// The source uri to base relative URIs off of.
final Uri _source;
/// A visitor traversing and collecting constants.
late final ConstantCollector collector;
@@ -32,7 +29,7 @@ class InstanceRecorder {
//TODO(mosum): add verbose mode to enable this
bool exactLocation = false;
InstanceRecorder(this._source, this._loadingUnitLookup) {
InstanceRecorder(this._loadingUnitLookup) {
collector = ConstantCollector.collectWith(_collectInstance);
}
@@ -66,10 +63,10 @@ class InstanceRecorder {
ast.Class cls,
) {
final enclosingLibrary = cls.enclosingLibrary;
final file = getImportUri(enclosingLibrary, _source);
final importUri = enclosingLibrary.importUri.toString();
return (
identifier: Identifier(importUri: file, name: cls.name),
identifier: Identifier(importUri: importUri, name: cls.name),
loadingUnit: _loadingUnitLookup(cls),
);
}
@@ -5,8 +5,6 @@
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:record_use/record_use_internal.dart';
@@ -44,14 +42,13 @@ LoadingUnitLookup _getDefaultLoadingUnitLookup(ast.Component component) {
/// application.
ast.Component transformComponent(
ast.Component component,
Uri recordedUsagesFile,
Uri source, {
Uri recordedUsagesFile, {
LoadingUnitLookup? loadingUnitLookup,
}) {
loadingUnitLookup ??= _getDefaultLoadingUnitLookup(component);
final callRecorder = CallRecorder(source, loadingUnitLookup);
final instanceRecorder = InstanceRecorder(source, loadingUnitLookup);
final callRecorder = CallRecorder(loadingUnitLookup);
final instanceRecorder = InstanceRecorder(loadingUnitLookup);
component.accept(_RecordUseVisitor(callRecorder, instanceRecorder));
final usages = _usages(
@@ -196,17 +193,6 @@ InstanceConstant evaluateInstanceConstant(ast.InstanceConstant constant) =>
Never _unsupported(String constantType) =>
throw UnsupportedError('$constantType is not supported for recording.');
String getImportUri(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;
@@ -7,7 +7,7 @@ library;
import 'dart:core' hide Type;
import 'package:front_end/src/api_prototype/record_use.dart' as recordUse;
import 'package:front_end/src/api_prototype/record_use.dart' as record_use;
import 'package:kernel/ast.dart';
import 'package:kernel/library_index.dart' show LibraryIndex;
@@ -226,7 +226,7 @@ class PragmaEntryPointsVisitor extends RecursiveVisitor {
visitField(Field field) {
if (field.isInstanceMember &&
field.enclosingClass!.hasConstConstructor &&
recordUse.hasRecordUse(field.enclosingClass!)) {
record_use.isBeingRecorded(field.enclosingClass!)) {
// If a class has a `@RecordUse` annotation then a user-defined linker
// script may want to inspect instance constants of the class, so we have
// to preserve all fields.
@@ -9,7 +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/record_use.dart' as RecordUse;
import 'package:front_end/src/api_prototype/record_use.dart' as record_use;
import 'package:kernel/ast.dart' hide Statement, StatementVisitor;
import 'package:kernel/ast.dart' as ast show Statement;
import 'package:kernel/class_hierarchy.dart'
@@ -344,7 +344,7 @@ class CleanupAnnotations extends RecursiveVisitor {
protobufHandler?.usesAnnotationClass(cls) ?? false;
return cls == pragmaClass ||
usesProtobufAnnotation ||
RecordUse.isBeingRecorded(cls);
record_use.isRecordUse(cls);
}
}
return false;
-1
View File
@@ -10,7 +10,6 @@ resolution: workspace
# Use 'any' constraints here; we get our versions from the DEPS file.
dependencies:
_fe_analyzer_shared: any
args: any
build_integration: any
collection: any
@@ -21,12 +21,21 @@ import 'package:path/path.dart' as path;
final Uri _pkgVmDir = Platform.script.resolve('../..');
void runTestCaseAot(Uri source, bool throws) async {
void runTestCaseAot(
Uri sourceFileUri,
Uri sourcePackageUri,
Uri packagesFileUri,
bool throws,
) async {
final target = VmTarget(TargetFlags(supportMirrors: false));
Component component;
try {
component = await compileTestCaseToKernelProgram(source, target: target);
component = await compileTestCaseToKernelProgram(
sourcePackageUri,
target: target,
packagesFileUri: packagesFileUri,
);
} catch (e) {
if (throws) {
return;
@@ -52,7 +61,7 @@ void runTestCaseAot(Uri source, bool throws) async {
useProtobufTreeShakerV2: true,
treeShakeWriteOnlyFields: true,
recordedUsages: recordedUsagesFile,
source: source,
source: sourcePackageUri,
),
);
@@ -66,12 +75,16 @@ void runTestCaseAot(Uri source, bool throws) async {
component.mainMethod!.enclosingLibrary,
).replaceAll(_pkgVmDir.toString(), 'org-dartlang-test:///');
compareResultWithExpectationsFile(source, actual, expectFilePostfix: '.aot');
compareResultWithExpectationsFile(
sourceFileUri,
actual,
expectFilePostfix: '.aot',
);
final actualSemantic = Recordings.fromJson(
jsonDecode(File.fromUri(recordedUsagesFile).readAsStringSync()),
);
final goldenFile = File('${source.toFilePath()}.json.expect');
final goldenFile = File('${sourceFileUri.toFilePath()}.json.expect');
final update = bool.fromEnvironment('updateExpectations');
bool semanticEquals = false;
@@ -83,7 +96,7 @@ void runTestCaseAot(Uri source, bool throws) async {
if (!semanticEquals || update) {
compareResultWithExpectationsFile(
source,
sourceFileUri,
File.fromUri(recordedUsagesFile).readAsStringSync(),
expectFilePostfix: '.json',
);
@@ -94,9 +107,14 @@ void main(List<String> args) {
assert(args.isEmpty || args.length == 1);
final filter = args.firstOrNull;
group('record-use-transformations', () {
final testCasesDir = Directory.fromUri(
_pkgVmDir.resolve('testcases/transformations/record_use/'),
final recordUseTestDir = _pkgVmDir.resolve(
'testcases/transformations/record_use/',
);
final testCasesDir = Directory.fromUri(recordUseTestDir.resolve('lib/'));
final packagesFileUri = _pkgVmDir.resolve(
'../../.dart_tool/package_config.json',
);
for (var file
in testCasesDir
.listSync(recursive: true, followLinks: false)
@@ -104,11 +122,42 @@ void main(List<String> args) {
if (file.path.endsWith('.dart') &&
!file.path.contains('helper') &&
(filter == null || file.path.contains(filter))) {
final name = path.basename(file.path);
final packageUri = Uri.parse('package:record_use_test/$name');
test(
'${file.path} aot',
() => runTestCaseAot(file.uri, file.path.contains('throws')),
() => runTestCaseAot(
file.uri,
packageUri,
packagesFileUri,
file.path.contains('throws'),
),
);
}
}
test('outside_package_throws', () async {
final sourceFileUri = recordUseTestDir.resolve(
'outside_package_throws.dart',
);
final target = VmTarget(TargetFlags(supportMirrors: false));
bool failed = false;
try {
await compileTestCaseToKernelProgram(
sourceFileUri,
target: target,
packagesFileUri: packagesFileUri,
);
} catch (e) {
failed = true;
final message = e.toString();
expect(message, contains('RecordUse'));
expect(message, contains('package:'));
}
if (!failed) {
fail('Should have failed with a diagnostic error');
}
});
});
}
@@ -24,7 +24,7 @@
"identifier": {
"name": "someStaticMethod2",
"scope": "SomeClass",
"uri": "basic.dart"
"uri": "package:record_use_test/basic.dart"
},
"loading_unit": "1"
}
@@ -28,7 +28,7 @@
"identifier": {
"name": "generate",
"scope": "OtherClass",
"uri": "complex.dart"
"uri": "package:record_use_test/complex.dart"
},
"loading_unit": "1"
}
@@ -29,7 +29,7 @@
"definition": {
"identifier": {
"name": "someStaticMethod2",
"uri": "const_argument_instance.dart"
"uri": "package:record_use_test/const_argument_instance.dart"
},
"loading_unit": "1"
}
@@ -34,7 +34,7 @@
"definition": {
"identifier": {
"name": "doSomething",
"uri": "enum_const_arg.dart"
"uri": "package:record_use_test/enum_const_arg.dart"
},
"loading_unit": "1"
}
@@ -23,7 +23,7 @@
"definition": {
"identifier": {
"name": "_extension#0|callWithArgs",
"uri": "extension.dart"
"uri": "package:record_use_test/extension.dart"
},
"loading_unit": "1"
}
@@ -20,7 +20,7 @@
"definition": {
"identifier": {
"name": "MyClass",
"uri": "instance_class.dart"
"uri": "package:record_use_test/instance_class.dart"
},
"loading_unit": "1"
},
@@ -76,7 +76,7 @@
"definition": {
"identifier": {
"name": "MyClass",
"uri": "instance_complex.dart"
"uri": "package:record_use_test/instance_complex.dart"
},
"loading_unit": "1"
},
@@ -30,7 +30,7 @@
"definition": {
"identifier": {
"name": "MyClass",
"uri": "instance_duplicates.dart"
"uri": "package:record_use_test/instance_duplicates.dart"
},
"loading_unit": "1"
},
@@ -1,16 +1,11 @@
library #lib;
import self as self;
import "package:meta/meta.dart" as meta;
import "dart:core" as core;
import "package:meta/meta.dart" show RecordUse;
abstract class MyClass extends core::Object /*hasConstConstructor*/ {
@#C1
class MyClass extends core::Object /*hasConstConstructor*/ {
[@vm.inferred-type.metadata=dart.core::_Smi (value: 42)]
[@vm.unreachable.metadata=]
[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,getterSelectorId:1]
[@vm.unboxing-info.metadata=[!regcc]]
final field core::int i;
}
@@ -20,12 +15,6 @@ static method main() → void {
}
[@vm.inferred-return-type.metadata=dart.core::Null? (value: null)]
@#C3
static method doSomething() → void {
core::print("a");
}
constants {
#C1 = meta::RecordUse {}
#C2 = 42
#C3 = self::MyClass {i:#C2}
}
@@ -13,7 +13,7 @@
"definition": {
"identifier": {
"name": "MyClass",
"uri": "instance_not_annotation.dart"
"uri": "package:record_use_test/instance_not_annotation.dart"
},
"loading_unit": "1"
},
@@ -0,0 +1,15 @@
library #lib;
import self as self;
import "package:record_use_test/loading_units_multiple_helper_shared.dart" as loa;
import "package:record_use_test/loading_units_multiple_helper.dart" as loa2;
import "package:record_use_test/loading_units_multiple_helper_shared.dart";
import "package:record_use_test/loading_units_multiple_helper.dart" deferred as helper;
[@vm.inferred-return-type.metadata=dart.async::_Future]
static method main() → void async /* emittedValueType= void */ {
loa::SomeClass::someStaticMethod(42);
await LoadLibrary(helper);
let final dynamic #t1 = CheckLibraryIsLoaded(helper) in loa2::invokeDeferred();
}
@@ -31,7 +31,7 @@
"identifier": {
"name": "someStaticMethod",
"scope": "SomeClass",
"uri": "loading_units_multiple_helper_shared.dart"
"uri": "package:record_use_test/loading_units_multiple_helper_shared.dart"
},
"loading_unit": "1"
}
@@ -2,10 +2,10 @@ library #lib;
import self as self;
import "dart:core" as core;
import "package:meta/meta.dart" as meta;
import "loading_units_simple_helper.dart" as loa;
import "package:record_use_test/loading_units_simple_helper.dart" as loa;
import "package:meta/meta.dart" show RecordUse;
import "org-dartlang-test:///testcases/transformations/record_use/loading_units_simple_helper.dart" deferred as helper;
import "package:record_use_test/loading_units_simple_helper.dart" deferred as helper;
abstract class SomeClass extends core::Object {
@@ -24,7 +24,7 @@
"identifier": {
"name": "someStaticMethod",
"scope": "SomeClass",
"uri": "loading_units_simple.dart"
"uri": "package:record_use_test/loading_units_simple.dart"
},
"loading_unit": "1"
}
@@ -43,7 +43,7 @@
"identifier": {
"name": "someStaticMethod",
"scope": "SomeClass",
"uri": "loading_units_simple_helper.dart"
"uri": "package:record_use_test/loading_units_simple_helper.dart"
},
"loading_unit": "2"
}
@@ -49,7 +49,7 @@
"identifier": {
"name": "someStaticMethod",
"scope": "SomeClass",
"uri": "map_complex_keys.dart"
"uri": "package:record_use_test/map_complex_keys.dart"
},
"loading_unit": "1"
}
@@ -80,7 +80,7 @@
"identifier": {
"name": "someStaticMethod",
"scope": "SomeClass",
"uri": "named_and_positional.dart"
"uri": "package:record_use_test/named_and_positional.dart"
},
"loading_unit": "1"
}
@@ -72,7 +72,7 @@
"identifier": {
"name": "someStaticMethod",
"scope": "SomeClass",
"uri": "named_both.dart"
"uri": "package:record_use_test/named_both.dart"
},
"loading_unit": "1"
}
@@ -35,7 +35,7 @@
"identifier": {
"name": "someStaticMethod",
"scope": "SomeClass",
"uri": "named_optional.dart"
"uri": "package:record_use_test/named_optional.dart"
},
"loading_unit": "1"
}
@@ -35,7 +35,7 @@
"identifier": {
"name": "someStaticMethod",
"scope": "SomeClass",
"uri": "named_required.dart"
"uri": "package:record_use_test/named_required.dart"
},
"loading_unit": "1"
}
@@ -26,7 +26,7 @@
"definition": {
"identifier": {
"name": "Ext|foo",
"uri": "named_with_function_arg.dart"
"uri": "package:record_use_test/named_with_function_arg.dart"
},
"loading_unit": "1"
}
@@ -33,7 +33,7 @@
"definition": {
"identifier": {
"name": "MyClass",
"uri": "nested.dart"
"uri": "package:record_use_test/nested.dart"
},
"loading_unit": "1"
},
@@ -54,7 +54,7 @@
"definition": {
"identifier": {
"name": "MyOtherClass",
"uri": "nested.dart"
"uri": "package:record_use_test/nested.dart"
},
"loading_unit": "1"
},
@@ -20,7 +20,7 @@
"definition": {
"identifier": {
"name": "Recorded",
"uri": "nested_instance_constant.dart"
"uri": "package:record_use_test/nested_instance_constant.dart"
},
"loading_unit": "1"
},
@@ -6,7 +6,7 @@ import "package:meta/meta.dart" as meta;
import "package:meta/meta.dart" show RecordUse;
part partfile_helper.dart;
abstract class SomeClass extends core::Object { // from org-dartlang-test:///testcases/transformations/record_use/partfile_helper.dart
abstract class SomeClass extends core::Object { // from org-dartlang-test:///testcases/transformations/record_use/lib/partfile_helper.dart
[@vm.inferred-return-type.metadata=int]
[@vm.unboxing-info.metadata=(i)->i]
@@ -24,7 +24,7 @@
"identifier": {
"name": "someStaticMethod",
"scope": "SomeClass",
"uri": "partfile_main.dart"
"uri": "package:record_use_test/partfile_main.dart"
},
"loading_unit": "1"
}
@@ -45,7 +45,7 @@
"identifier": {
"name": "someStaticMethod",
"scope": "SomeClass",
"uri": "positional_both.dart"
"uri": "package:record_use_test/positional_both.dart"
},
"loading_unit": "1"
}
@@ -45,7 +45,7 @@
"identifier": {
"name": "someStaticMethod",
"scope": "SomeClass",
"uri": "positional_both_with_type_argument.dart"
"uri": "package:record_use_test/positional_both_with_type_argument.dart"
},
"loading_unit": "1"
}
@@ -35,7 +35,7 @@
"identifier": {
"name": "someStaticMethod",
"scope": "SomeClass",
"uri": "positional_optional.dart"
"uri": "package:record_use_test/positional_optional.dart"
},
"loading_unit": "1"
}

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