From 9e8fdef17c0a8a346e233188ba8170e9dc95bcc4 Mon Sep 17 00:00:00 2001 From: Kilian Schulte Date: Tue, 28 Apr 2026 03:06:03 -0700 Subject: [PATCH] Add annotation-based creation location tracking Closes https://github.com/dart-lang/sdk/pull/63011 GitOrigin-RevId: 926534c2f5d3cd1e9629cf25f6b028ab34c00e64 Change-Id: I88460a060faabeae8f611e88f23b2eb5fc99f21a Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/491702 Reviewed-by: Daco Harkes Reviewed-by: Jens Johansen Reviewed-by: Slava Egorov Commit-Queue: Daco Harkes --- pkg/dart2bytecode/lib/dart2bytecode.dart | 19 +- pkg/dev_compiler/lib/src/command/command.dart | 12 +- pkg/dev_compiler/lib/src/command/options.dart | 11 +- .../kernel/expression_compiler_worker.dart | 16 +- pkg/dev_compiler/lib/src/kernel/target.dart | 31 +- pkg/dev_compiler/test/memory_compiler.dart | 4 +- .../lib/src/base/incremental_compiler.dart | 7 +- .../test/constant_evaluator_benchmark.dart | 4 +- .../test/crashing_test_case_minimizer.dart | 4 +- .../crashing_test_case_minimizer_impl.dart | 12 +- .../test/incremental_flutter_tester.dart | 4 +- pkg/front_end/test/incremental_suite.dart | 16 +- .../test/outline_extractor_tester.dart | 2 +- .../test/spell_checking_list_common.txt | 1 + .../testcases/incremental/crash_01.yaml | 2 +- .../testcases/incremental/crash_02.yaml | 2 +- .../testcases/incremental/crash_03.yaml | 2 +- .../testcases/incremental/crash_04.yaml | 2 +- .../incremental/flutter_issue_66122.yaml | 2 +- .../incremental/flutter_widget_factory.yaml | 2 +- .../incremental/flutter_widget_transform.yaml | 2 +- .../flutter_widget_transform_const.yaml | 2 +- .../flutter_widget_transform_nnbd.yaml | 2 +- .../flutter_widget_transform_pattern.yaml | 2 +- .../flutter_widget_transformer_43371.yaml | 2 +- .../flutter_widget_transformer_non_const.yaml | 2 +- .../general_location_transformer.yaml | 58 ++++ ...l_location_transformer.yaml.world.1.expect | 80 +++++ ...l_location_transformer.yaml.world.2.expect | 89 +++++ .../general_location_transformer_flutter.yaml | 42 +++ ...on_transformer_flutter.yaml.world.1.expect | 49 +++ ...on_transformer_flutter.yaml.world.2.expect | 52 +++ ...al_location_transformer_flutter_const.yaml | 100 ++++++ ...nsformer_flutter_const.yaml.world.1.expect | 161 +++++++++ ...nsformer_flutter_const.yaml.world.2.expect | 135 ++++++++ ...nsformer_flutter_const.yaml.world.3.expect | 161 +++++++++ .../general_location_transformer_jaspr.yaml | 44 +++ ...tion_transformer_jaspr.yaml.world.1.expect | 89 +++++ .../general_location_transformer_outline.yaml | 64 ++++ ...on_transformer_outline.yaml.world.1.expect | 40 +++ ...on_transformer_outline.yaml.world.2.expect | 42 +++ .../no_outline_change_44_flutter.yaml | 2 +- .../no_outline_change_45_flutter.yaml | 2 +- .../no_outline_change_45_flutter_prime_1.yaml | 2 +- .../testcases/incremental/super_key.yaml | 2 +- pkg/frontend_server/lib/compute_kernel.dart | 18 +- pkg/frontend_server/lib/frontend_server.dart | 13 +- pkg/kernel/lib/target/targets.dart | 34 +- .../track_widget_constructor_locations.dart | 318 +++++++++++++----- .../test/generalized_tracking_test.dart | 131 ++++++++ .../lib/modular_aot_compiler.dart | 11 +- pkg/vm/lib/kernel_front_end.dart | 13 +- pkg/vm/lib/modular/target/flutter.dart | 28 -- pkg/vm/lib/modular/target/vm.dart | 11 + runtime/docs/pragmas.md | 1 + sdk/lib/developer/creation_tracking.dart | 95 ++++++ sdk/lib/developer/developer.dart | 1 + sdk/lib/developer/developer_sources.gni | 1 + 58 files changed, 1851 insertions(+), 205 deletions(-) create mode 100644 pkg/front_end/testcases/incremental/general_location_transformer.yaml create mode 100644 pkg/front_end/testcases/incremental/general_location_transformer.yaml.world.1.expect create mode 100644 pkg/front_end/testcases/incremental/general_location_transformer.yaml.world.2.expect create mode 100644 pkg/front_end/testcases/incremental/general_location_transformer_flutter.yaml create mode 100644 pkg/front_end/testcases/incremental/general_location_transformer_flutter.yaml.world.1.expect create mode 100644 pkg/front_end/testcases/incremental/general_location_transformer_flutter.yaml.world.2.expect create mode 100644 pkg/front_end/testcases/incremental/general_location_transformer_flutter_const.yaml create mode 100644 pkg/front_end/testcases/incremental/general_location_transformer_flutter_const.yaml.world.1.expect create mode 100644 pkg/front_end/testcases/incremental/general_location_transformer_flutter_const.yaml.world.2.expect create mode 100644 pkg/front_end/testcases/incremental/general_location_transformer_flutter_const.yaml.world.3.expect create mode 100644 pkg/front_end/testcases/incremental/general_location_transformer_jaspr.yaml create mode 100644 pkg/front_end/testcases/incremental/general_location_transformer_jaspr.yaml.world.1.expect create mode 100644 pkg/front_end/testcases/incremental/general_location_transformer_outline.yaml create mode 100644 pkg/front_end/testcases/incremental/general_location_transformer_outline.yaml.world.1.expect create mode 100644 pkg/front_end/testcases/incremental/general_location_transformer_outline.yaml.world.2.expect create mode 100644 pkg/kernel/test/generalized_tracking_test.dart create mode 100644 sdk/lib/developer/creation_tracking.dart diff --git a/pkg/dart2bytecode/lib/dart2bytecode.dart b/pkg/dart2bytecode/lib/dart2bytecode.dart index 3cc4d6d3459..5ba179ead6b 100644 --- a/pkg/dart2bytecode/lib/dart2bytecode.dart +++ b/pkg/dart2bytecode/lib/dart2bytecode.dart @@ -109,9 +109,16 @@ final ArgParser _argParser = ArgParser(allowTrailingOptions: true) help: 'Print this help message.', ) ..addFlag( - 'track-widget-creation', - help: 'Run a kernel transformer to track creation locations for widgets.', + 'track-creation-locations', + help: + 'Run a kernel transformer to track creation locations for' + ' classes annotated with @pragma(\'track-creation-locations\').', defaultsTo: false, + aliases: [ + // TODO(http://dartbug.com/63225): Remove this once flutter is migrated + // to the new flag. + 'track-widget-creation', + ], ) ..addOption( 'invocation-modes', @@ -186,7 +193,7 @@ Future runCompilerWithCommandLineArguments(List arguments) async { final String? validateDynamicInterface = options['validate']; final String messageVerbosity = options['verbosity']; final String cfeInvocationModes = options['invocation-modes']; - final bool trackWidgetCreation = options['track-widget-creation']; + final bool trackCreationLocations = options['track-creation-locations']; final List? bytecodeGeneratorOptions = options['bytecode-options']; final String libraryUrisPrefix = options['prefix-library-uris']!; @@ -205,7 +212,7 @@ Future runCompilerWithCommandLineArguments(List arguments) async { fileSystemRoots: fileSystemRoots, messageVerbosity: messageVerbosity, cfeInvocationModes: cfeInvocationModes, - trackWidgetCreation: trackWidgetCreation, + trackCreationLocations: trackCreationLocations, bytecodeGeneratorOptions: bytecodeGeneratorOptions, depfile: depfile, depfileTarget: depfileTarget, @@ -231,7 +238,7 @@ Future runCompilerWithOptions({ String messageVerbosity = Verbosity.defaultValue, void Function(String) printMessage = print, String cfeInvocationModes = '', - bool trackWidgetCreation = false, + bool trackCreationLocations = false, List? bytecodeGeneratorOptions, String? depfile, String? depfileTarget, @@ -287,7 +294,7 @@ Future runCompilerWithOptions({ ..verbosity = verbosity ..target = createFrontEndTarget( targetName, - trackWidgetCreation: trackWidgetCreation, + trackCreationLocations: trackCreationLocations, supportMirrors: false, isClosureContextLoweringEnabled: bytecodeOptions.isClosureContextLoweringEnabled, diff --git a/pkg/dev_compiler/lib/src/command/command.dart b/pkg/dev_compiler/lib/src/command/command.dart index 4dd9d7903d9..ff7b2af117c 100644 --- a/pkg/dev_compiler/lib/src/command/command.dart +++ b/pkg/dev_compiler/lib/src/command/command.dart @@ -270,7 +270,7 @@ Future _compile( onWarning: print, ); - var trackWidgetCreation = argResults.flag('track-widget-creation'); + var trackCreationLocations = argResults.flag('track-creation-locations'); var oldCompilerState = compilerState; var recordUsedInputs = argResults.option('used-inputs-file') != null; var additionalDills = summaryModules.keys.toList(); @@ -290,7 +290,9 @@ Future _compile( packageFile != null ? sourcePathToUri(packageFile) : null, sourcePathToUri(librarySpecPath), additionalDills, - DevCompilerTarget(TargetFlags(trackWidgetCreation: trackWidgetCreation)), + DevCompilerTarget( + TargetFlags(trackCreationLocations: trackCreationLocations), + ), fileSystem: fileSystem, explicitExperimentalFlags: explicitExperimentalFlags, environmentDefines: declaredVariables, @@ -318,7 +320,7 @@ Future _compile( compilerState = await fe.initializeIncrementalCompiler( oldCompilerState, { - 'trackWidgetCreation=$trackWidgetCreation', + 'trackCreationLocations=$trackCreationLocations', 'multiRootScheme=${fileSystem.markerScheme}', 'multiRootRoots=${fileSystem.roots}', }, @@ -330,7 +332,9 @@ Future _compile( sourcePathToUri(librarySpecPath), additionalDills, inputDigests, - DevCompilerTarget(TargetFlags(trackWidgetCreation: trackWidgetCreation)), + DevCompilerTarget( + TargetFlags(trackCreationLocations: trackCreationLocations), + ), fileSystem: fileSystem, explicitExperimentalFlags: explicitExperimentalFlags, environmentDefines: declaredVariables, diff --git a/pkg/dev_compiler/lib/src/command/options.dart b/pkg/dev_compiler/lib/src/command/options.dart index 030ea0fabf5..a2e57b31b18 100644 --- a/pkg/dev_compiler/lib/src/command/options.dart +++ b/pkg/dev_compiler/lib/src/command/options.dart @@ -368,10 +368,17 @@ class Options { hide: true, ) ..addFlag( - 'track-widget-creation', - help: 'Enable inspecting of Flutter widgets.', + 'track-creation-locations', + help: + 'Run a kernel transformer to track creation locations for' + ' classes annotated with @pragma(\'track-creation-locations\').', defaultsTo: false, hide: true, + aliases: [ + // TODO(http://dartbug.com/63225): Remove this once flutter is + // migrated to the new flag. + 'track-widget-creation', + ], ) // TODO(jmesserly): add verbose help to show hidden options ..addOption( diff --git a/pkg/dev_compiler/lib/src/kernel/expression_compiler_worker.dart b/pkg/dev_compiler/lib/src/kernel/expression_compiler_worker.dart index 12edea14ce8..2a43f0d3d61 100644 --- a/pkg/dev_compiler/lib/src/kernel/expression_compiler_worker.dart +++ b/pkg/dev_compiler/lib/src/kernel/expression_compiler_worker.dart @@ -201,7 +201,7 @@ class ExpressionCompilerWorker { environmentDefines: environmentDefines, explicitExperimentalFlags: explicitExperimentalFlags, sdkRoot: _argToUri(parsedArgs.option('sdk-root')), - trackWidgetCreation: parsedArgs.flag('track-widget-creation'), + trackCreationLocations: parsedArgs.flag('track-creation-locations'), moduleFormat: moduleFormat, canaryFeatures: parsedArgs.flag('canary'), enableAsserts: parsedArgs.flag('enable-asserts'), @@ -227,7 +227,7 @@ class ExpressionCompilerWorker { Map? environmentDefines, Map explicitExperimentalFlags = const {}, Uri? sdkRoot, - bool trackWidgetCreation = false, + bool trackCreationLocations = false, ModuleFormat moduleFormat = ModuleFormat.amd, bool canaryFeatures = false, bool enableAsserts = true, @@ -244,7 +244,7 @@ class ExpressionCompilerWorker { ..packagesFileUri = packagesFile ..librariesSpecificationUri = librariesSpecificationUri ..target = DevCompilerTarget( - TargetFlags(trackWidgetCreation: trackWidgetCreation), + TargetFlags(trackCreationLocations: trackCreationLocations), ) ..fileSystem = fileSystem ..omitPlatform = true @@ -889,7 +889,15 @@ final argParser = ArgParser() ..addOption('asset-server-address') ..addOption('asset-server-port') ..addOption('module-format', defaultsTo: 'amd') - ..addFlag('track-widget-creation', defaultsTo: false) + ..addFlag( + 'track-creation-locations', + defaultsTo: false, + aliases: [ + // TODO(http://dartbug.com/63225): Remove this once flutter is + // migrated to the new flag. + 'track-widget-creation', + ], + ) ..addFlag('sound-null-safety', negatable: true, defaultsTo: true) ..addFlag('canary', negatable: true, defaultsTo: false) // Disable asserts in compiled code by default, which is different diff --git a/pkg/dev_compiler/lib/src/kernel/target.dart b/pkg/dev_compiler/lib/src/kernel/target.dart index 8f674af12b3..078257ba7cf 100644 --- a/pkg/dev_compiler/lib/src/kernel/target.dart +++ b/pkg/dev_compiler/lib/src/kernel/target.dart @@ -240,6 +240,28 @@ class DevCompilerTarget extends Target { } } + @override + void performOutlineTransformations( + Component component, { + List? libraries, + ChangedStructureNotifier? changedStructureNotifier, + }) { + super.performOutlineTransformations( + component, + libraries: libraries, + changedStructureNotifier: changedStructureNotifier, + ); + // In addition to [performPreConstantEvaluationTransformations] this makes + // sure summaries are also transformed. + if (flags.trackCreationLocations) { + (_widgetTracker ??= WidgetCreatorTracker()).transform( + libraries ?? component.libraries, + component.libraries, + changedStructureNotifier, + ); + } + } + @override void performPreConstantEvaluationTransformations( Component component, @@ -249,9 +271,12 @@ class DevCompilerTarget extends Target { void Function(String msg)? logger, ChangedStructureNotifier? changedStructureNotifier, }) { - if (flags.trackWidgetCreation) { - _widgetTracker ??= WidgetCreatorTracker(); - _widgetTracker!.transform(component, libraries, changedStructureNotifier); + if (flags.trackCreationLocations) { + (_widgetTracker ??= WidgetCreatorTracker()).transform( + libraries, + component.libraries, + changedStructureNotifier, + ); } } diff --git a/pkg/dev_compiler/test/memory_compiler.dart b/pkg/dev_compiler/test/memory_compiler.dart index 8d5a4283fc0..7a3d8ecf000 100644 --- a/pkg/dev_compiler/test/memory_compiler.dart +++ b/pkg/dev_compiler/test/memory_compiler.dart @@ -77,7 +77,7 @@ Future componentFromMemory( null, sourcePathToUri(defaultLibrarySpecPath), [], - DevCompilerTarget(TargetFlags(trackWidgetCreation: false)), + DevCompilerTarget(TargetFlags(trackCreationLocations: false)), fileSystem: fe.HybridFileSystem(memoryFileSystem), environmentDefines: {}, explicitExperimentalFlags: explicitExperimentalFlags, @@ -134,7 +134,7 @@ Future incrementalComponentFromMemory( sourcePathToUri(defaultLibrarySpecPath), [], inputDigests, - DevCompilerTarget(TargetFlags(trackWidgetCreation: false)), + DevCompilerTarget(TargetFlags(trackCreationLocations: false)), fileSystem: fe.HybridFileSystem(memoryFileSystem), environmentDefines: {}, explicitExperimentalFlags: explicitExperimentalFlags, diff --git a/pkg/front_end/lib/src/base/incremental_compiler.dart b/pkg/front_end/lib/src/base/incremental_compiler.dart index 3588a98ad91..5acb7c5f34c 100644 --- a/pkg/front_end/lib/src/base/incremental_compiler.dart +++ b/pkg/front_end/lib/src/base/incremental_compiler.dart @@ -443,7 +443,12 @@ class IncrementalCompiler implements IncrementalKernelGenerator { } // Coverage-ignore(suite): Not run. else if (componentWithDill != null) { - context.options.target.performOutlineTransformations(componentWithDill); + context.options.target.performOutlineTransformations( + componentWithDill, + libraries: currentKernelTarget.loader.libraries, + changedStructureNotifier: + currentKernelTarget.changedStructureNotifier, + ); } _benchmarker diff --git a/pkg/front_end/test/constant_evaluator_benchmark.dart b/pkg/front_end/test/constant_evaluator_benchmark.dart index fe7651094b9..e6560093f51 100644 --- a/pkg/front_end/test/constant_evaluator_benchmark.dart +++ b/pkg/front_end/test/constant_evaluator_benchmark.dart @@ -190,14 +190,14 @@ Future main(List arguments) async { CompilerContext setupCompilerContext( String targetString, - bool widgetTransformation, + bool trackCreationLocations, Uri platformUri, Uri mainUri, ) { CompilerOptions options = getOptions(); TargetFlags targetFlags = new TargetFlags( - trackWidgetCreation: widgetTransformation, + trackCreationLocations: trackCreationLocations, ); Target target; switch (targetString) { diff --git a/pkg/front_end/test/crashing_test_case_minimizer.dart b/pkg/front_end/test/crashing_test_case_minimizer.dart index 12d662c4eac..cc102d0cb03 100644 --- a/pkg/front_end/test/crashing_test_case_minimizer.dart +++ b/pkg/front_end/test/crashing_test_case_minimizer.dart @@ -56,8 +56,8 @@ Future main(List arguments) async { for (String s in arg.substring("--invalidate=".length).split(",")) { settings.invalidate.add(Uri.base.resolve(s)); } - } else if (arg.startsWith("--widgetTransformation")) { - settings.widgetTransformation = true; + } else if (arg.startsWith("--track-creation-locations")) { + settings.trackCreationLocations = true; } else if (arg.startsWith("--target=vm")) { settings.targetString = "vm"; } else if (arg.startsWith("--target=flutter")) { diff --git a/pkg/front_end/test/crashing_test_case_minimizer_impl.dart b/pkg/front_end/test/crashing_test_case_minimizer_impl.dart index 8b3e1db2626..e4c165c1cdc 100644 --- a/pkg/front_end/test/crashing_test_case_minimizer_impl.dart +++ b/pkg/front_end/test/crashing_test_case_minimizer_impl.dart @@ -68,7 +68,7 @@ class TestMinimizerSettings { bool invalidateAllAtOnce = false; bool experimentalInvalidation = false; bool serialize = false; - bool widgetTransformation = false; + bool trackCreationLocations = false; final List invalidate = []; String targetString = "vm"; bool noTryToDeleteEmptyFilesUpFront = false; @@ -103,7 +103,7 @@ class TestMinimizerSettings { 'initialOnlyOutline': initialOnlyOutline, 'experimentalInvalidation': experimentalInvalidation, 'serialize': serialize, - 'widgetTransformation': widgetTransformation, + 'trackCreationLocations': trackCreationLocations, 'invalidate': invalidate.map((uri) => uri.toString()).toList(), 'targetString': targetString, 'noTryToDeleteEmptyFilesUpFront': noTryToDeleteEmptyFilesUpFront, @@ -134,7 +134,7 @@ class TestMinimizerSettings { initialOnlyOutline = json["initialOnlyOutline"]; experimentalInvalidation = json["experimentalInvalidation"]; serialize = json["serialize"]; - widgetTransformation = json["widgetTransformation"]; + trackCreationLocations = json["trackCreationLocations"]; invalidate.clear(); invalidate.addAll( (json["invalidate"] as List).map((uriString) => Uri.parse(uriString)), @@ -802,8 +802,8 @@ class TestMinimizer { # Reproduce a crash. type: newworld"""); - if (_settings.widgetTransformation) { - print("trackWidgetCreation: true"); + if (_settings.trackCreationLocations) { + print("trackCreationLocations: true"); print("target: dartdevc # needed for widget creation to be run"); } print(""" @@ -2259,7 +2259,7 @@ worlds: } TargetFlags targetFlags = new TargetFlags( - trackWidgetCreation: _settings.widgetTransformation, + trackCreationLocations: _settings.trackCreationLocations, ); Target target; switch (_settings.targetString) { diff --git a/pkg/front_end/test/incremental_flutter_tester.dart b/pkg/front_end/test/incremental_flutter_tester.dart index 1c7db8a3ed3..6c5294670dd 100644 --- a/pkg/front_end/test/incremental_flutter_tester.dart +++ b/pkg/front_end/test/incremental_flutter_tester.dart @@ -279,7 +279,9 @@ bool isEqualBitForBit(List a, List b) { } CompilerOptions getOptions(Uri sdkRoot) { - Target target = new FlutterTarget(new TargetFlags(trackWidgetCreation: true)); + Target target = new FlutterTarget( + new TargetFlags(trackCreationLocations: true), + ); CompilerOptions options = new CompilerOptions() ..sdkRoot = sdkRoot ..target = target diff --git a/pkg/front_end/test/incremental_suite.dart b/pkg/front_end/test/incremental_suite.dart index 95413dce305..630ac96db17 100644 --- a/pkg/front_end/test/incremental_suite.dart +++ b/pkg/front_end/test/incremental_suite.dart @@ -148,8 +148,8 @@ class NewWorldTestProperties { defaultValue: false, ); - static const Property trackWidgetCreation = Property.optional( - 'trackWidgetCreation', + static const Property trackCreationLocations = Property.optional( + 'trackCreationLocations', BoolValue(), defaultValue: false, ); @@ -724,10 +724,8 @@ class RunCompilations extends Step { forceLateLoweringForTesting: NewWorldTestProperties .forceLateLoweringForTesting .read(map, keys), - trackWidgetCreation: NewWorldTestProperties.trackWidgetCreation.read( - map, - keys, - ), + trackCreationLocations: NewWorldTestProperties.trackCreationLocations + .read(map, keys), incrementalSerialization: NewWorldTestProperties .incrementalSerialization .read(map, keys), @@ -1260,7 +1258,7 @@ class NewWorldTest { final Map>? modules; final bool omitPlatform; final bool forceLateLoweringForTesting; - final bool trackWidgetCreation; + final bool trackCreationLocations; final bool incrementalSerialization; final String? targetName; @@ -1279,7 +1277,7 @@ class NewWorldTest { required this.modules, required this.omitPlatform, required this.forceLateLoweringForTesting, - required this.trackWidgetCreation, + required this.trackCreationLocations, required this.incrementalSerialization, required this.targetName, }); @@ -1296,7 +1294,7 @@ class NewWorldTest { forceLateLoweringsForTesting: forceLateLoweringForTesting ? LateLowering.all : null, - trackWidgetCreation: trackWidgetCreation, + trackCreationLocations: trackCreationLocations, ); Target target = new VmTarget(targetFlags); if (targetName != null) { diff --git a/pkg/front_end/test/outline_extractor_tester.dart b/pkg/front_end/test/outline_extractor_tester.dart index 767384d0821..fe115916c49 100644 --- a/pkg/front_end/test/outline_extractor_tester.dart +++ b/pkg/front_end/test/outline_extractor_tester.dart @@ -83,7 +83,7 @@ Future processUri( Component? fullComponent, Uri packageUri, ) async { - TargetFlags targetFlags = new TargetFlags(trackWidgetCreation: false); + TargetFlags targetFlags = new TargetFlags(trackCreationLocations: false); Target? target = new Dart2jsTarget("dart2js", targetFlags); Uri sdkSummary = Uri.base.resolve("out/ReleaseX64/dart2js_outline.dill"); Stopwatch stopwatch = new Stopwatch()..start(); diff --git a/pkg/front_end/test/spell_checking_list_common.txt b/pkg/front_end/test/spell_checking_list_common.txt index 311bf35ed9e..14b670d13bd 100644 --- a/pkg/front_end/test/spell_checking_list_common.txt +++ b/pkg/front_end/test/spell_checking_list_common.txt @@ -1452,6 +1452,7 @@ happen happened happens hard +hardcoded hare has hash diff --git a/pkg/front_end/testcases/incremental/crash_01.yaml b/pkg/front_end/testcases/incremental/crash_01.yaml index d641aeb58c3..e9f2377cbfe 100644 --- a/pkg/front_end/testcases/incremental/crash_01.yaml +++ b/pkg/front_end/testcases/incremental/crash_01.yaml @@ -5,7 +5,7 @@ # Reproduce a crash. type: newworld -trackWidgetCreation: true +trackCreationLocations: true target: dartdevc # basically needed for widget creation to be run worlds: - entry: app/main.dart diff --git a/pkg/front_end/testcases/incremental/crash_02.yaml b/pkg/front_end/testcases/incremental/crash_02.yaml index 1bfd0e9f314..df8adc86595 100644 --- a/pkg/front_end/testcases/incremental/crash_02.yaml +++ b/pkg/front_end/testcases/incremental/crash_02.yaml @@ -5,7 +5,7 @@ # Reproduce a crash. type: newworld -trackWidgetCreation: true +trackCreationLocations: true target: dartdevc # basically needed for widget creation to be run worlds: - entry: app/main.dart diff --git a/pkg/front_end/testcases/incremental/crash_03.yaml b/pkg/front_end/testcases/incremental/crash_03.yaml index 00adbe63420..1eb5267e944 100644 --- a/pkg/front_end/testcases/incremental/crash_03.yaml +++ b/pkg/front_end/testcases/incremental/crash_03.yaml @@ -5,7 +5,7 @@ # Reproduce a crash. type: newworld -trackWidgetCreation: true +trackCreationLocations: true target: dartdevc # basically needed for widget creation to be run worlds: - entry: app/main.dart diff --git a/pkg/front_end/testcases/incremental/crash_04.yaml b/pkg/front_end/testcases/incremental/crash_04.yaml index 1bfd0e9f314..df8adc86595 100644 --- a/pkg/front_end/testcases/incremental/crash_04.yaml +++ b/pkg/front_end/testcases/incremental/crash_04.yaml @@ -5,7 +5,7 @@ # Reproduce a crash. type: newworld -trackWidgetCreation: true +trackCreationLocations: true target: dartdevc # basically needed for widget creation to be run worlds: - entry: app/main.dart diff --git a/pkg/front_end/testcases/incremental/flutter_issue_66122.yaml b/pkg/front_end/testcases/incremental/flutter_issue_66122.yaml index 76129d2cec2..7d5ff46d64d 100644 --- a/pkg/front_end/testcases/incremental/flutter_issue_66122.yaml +++ b/pkg/front_end/testcases/incremental/flutter_issue_66122.yaml @@ -7,7 +7,7 @@ type: newworld target: dartdevc -trackWidgetCreation: true +trackCreationLocations: true worlds: - entry: main.dart skipClassHierarchyTest: true diff --git a/pkg/front_end/testcases/incremental/flutter_widget_factory.yaml b/pkg/front_end/testcases/incremental/flutter_widget_factory.yaml index a2a32e7cb05..bb950b36e4c 100644 --- a/pkg/front_end/testcases/incremental/flutter_widget_factory.yaml +++ b/pkg/front_end/testcases/incremental/flutter_widget_factory.yaml @@ -4,7 +4,7 @@ type: newworld target: dartdevc -trackWidgetCreation: true +trackCreationLocations: true worlds: - entry: main.dart sources: diff --git a/pkg/front_end/testcases/incremental/flutter_widget_transform.yaml b/pkg/front_end/testcases/incremental/flutter_widget_transform.yaml index 8701e91875b..ab4b43deef3 100644 --- a/pkg/front_end/testcases/incremental/flutter_widget_transform.yaml +++ b/pkg/front_end/testcases/incremental/flutter_widget_transform.yaml @@ -4,7 +4,7 @@ type: newworld target: dartdevc -trackWidgetCreation: true +trackCreationLocations: true worlds: - entry: main.dart sources: diff --git a/pkg/front_end/testcases/incremental/flutter_widget_transform_const.yaml b/pkg/front_end/testcases/incremental/flutter_widget_transform_const.yaml index 24d7a3681cc..952a4f216c8 100644 --- a/pkg/front_end/testcases/incremental/flutter_widget_transform_const.yaml +++ b/pkg/front_end/testcases/incremental/flutter_widget_transform_const.yaml @@ -4,7 +4,7 @@ type: newworld target: dartdevc -trackWidgetCreation: true +trackCreationLocations: true worlds: - entry: main.dart sources: diff --git a/pkg/front_end/testcases/incremental/flutter_widget_transform_nnbd.yaml b/pkg/front_end/testcases/incremental/flutter_widget_transform_nnbd.yaml index 4f4cd79d9bd..699ef5e0808 100644 --- a/pkg/front_end/testcases/incremental/flutter_widget_transform_nnbd.yaml +++ b/pkg/front_end/testcases/incremental/flutter_widget_transform_nnbd.yaml @@ -4,7 +4,7 @@ type: newworld target: dartdevc -trackWidgetCreation: true +trackCreationLocations: true worlds: - entry: main.dart sources: diff --git a/pkg/front_end/testcases/incremental/flutter_widget_transform_pattern.yaml b/pkg/front_end/testcases/incremental/flutter_widget_transform_pattern.yaml index 5267cd248ed..e531b78e295 100644 --- a/pkg/front_end/testcases/incremental/flutter_widget_transform_pattern.yaml +++ b/pkg/front_end/testcases/incremental/flutter_widget_transform_pattern.yaml @@ -4,7 +4,7 @@ type: newworld target: dartdevc -trackWidgetCreation: true +trackCreationLocations: true worlds: - entry: main.dart sources: diff --git a/pkg/front_end/testcases/incremental/flutter_widget_transformer_43371.yaml b/pkg/front_end/testcases/incremental/flutter_widget_transformer_43371.yaml index 77c89dee685..8e9d728c582 100644 --- a/pkg/front_end/testcases/incremental/flutter_widget_transformer_43371.yaml +++ b/pkg/front_end/testcases/incremental/flutter_widget_transformer_43371.yaml @@ -4,7 +4,7 @@ type: newworld target: dartdevc -trackWidgetCreation: true +trackCreationLocations: true worlds: - entry: main.dart sources: diff --git a/pkg/front_end/testcases/incremental/flutter_widget_transformer_non_const.yaml b/pkg/front_end/testcases/incremental/flutter_widget_transformer_non_const.yaml index a7c407d186c..f4fce962ded 100644 --- a/pkg/front_end/testcases/incremental/flutter_widget_transformer_non_const.yaml +++ b/pkg/front_end/testcases/incremental/flutter_widget_transformer_non_const.yaml @@ -4,7 +4,7 @@ type: newworld target: dartdevc -trackWidgetCreation: true +trackCreationLocations: true worlds: - entry: main.dart sources: diff --git a/pkg/front_end/testcases/incremental/general_location_transformer.yaml b/pkg/front_end/testcases/incremental/general_location_transformer.yaml new file mode 100644 index 00000000000..a699e58972a --- /dev/null +++ b/pkg/front_end/testcases/incremental/general_location_transformer.yaml @@ -0,0 +1,58 @@ +# 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. + +type: newworld +target: dartdevc +trackCreationLocations: true +worlds: + - entry: main.dart + sources: + widget.dart: | + @pragma('track-creation-locations') + abstract class Widget { + const Widget(); + } + + class StandardWidget extends Widget { + const StandardWidget(); + const StandardWidget.named(String name); + } + main.dart: | + import 'widget.dart'; + + class MyWidget extends Widget { + const MyWidget({this.child}); + + final Widget? child; + } + + void main() { + var w1 = MyWidget(); + var w2 = StandardWidget.named('w2'); + const w3 = MyWidget(); + } + expectedLibraryCount: 2 + + - entry: main.dart + worldType: updated + expectInitializeFromDill: false + invalidate: + - main.dart + sources: + main.dart: | + import 'widget.dart'; + + class MyWidget extends Widget { + const MyWidget({this.child}); + + final Widget? child; + } + + void main() { + var w1 = MyWidget(child: StandardWidget()); + const w2 = MyWidget(child: StandardWidget.named('w2')); + const w3 = MyWidget(child: w2); + } + expectedLibraryCount: 2 + advancedInvalidation: bodiesOnly diff --git a/pkg/front_end/testcases/incremental/general_location_transformer.yaml.world.1.expect b/pkg/front_end/testcases/incremental/general_location_transformer.yaml.world.1.expect new file mode 100644 index 00000000000..3317ef9cdf3 --- /dev/null +++ b/pkg/front_end/testcases/incremental/general_location_transformer.yaml.world.1.expect @@ -0,0 +1,80 @@ +main = main::main; +library from "org-dartlang-test:///main.dart" as main { + + import "org-dartlang-test:///widget.dart"; + + class MyWidget extends wid::Widget /*hasConstConstructor*/ { + final field wid::Widget? child; + const constructor •({initializing-formal wid::Widget? child = #C1, dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C1}) → main::MyWidget + : main::MyWidget::child = child, super wid::Widget::•($creationLocationd_0dea112b090073317d4: $creationLocationd_0dea112b090073317d4) + ; + static synthetic method _#new#tearOff({wid::Widget? child = #C1}) → main::MyWidget + return new main::MyWidget::•(child: child, $creationLocationd_0dea112b090073317d4: #C6); + } + static method main() → void { + main::MyWidget w1 = new main::MyWidget::•($creationLocationd_0dea112b090073317d4: #C9); + wid::StandardWidget w2 = new wid::StandardWidget::named("w2", $creationLocationd_0dea112b090073317d4: #C13); + const main::MyWidget w3 = #C16; + } +} +library from "org-dartlang-test:///widget.dart" as wid { + + @#C18 + abstract class Widget extends dart.core::Object implements dart.developer::_HasCreationLocation /*hasConstConstructor*/ { + final field dart.developer::CreationLocation? dart.developer::_location; + const constructor •({dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C1}) → wid::Widget + : super dart.core::Object::•(), wid::Widget::_location = $creationLocationd_0dea112b090073317d4 + ; + } + class StandardWidget extends wid::Widget /*hasConstConstructor*/ { + const constructor •({dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C1}) → wid::StandardWidget + : super wid::Widget::•($creationLocationd_0dea112b090073317d4: $creationLocationd_0dea112b090073317d4) + ; + const constructor named(dart.core::String name, {dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C1}) → wid::StandardWidget + : super wid::Widget::•($creationLocationd_0dea112b090073317d4: $creationLocationd_0dea112b090073317d4) + ; + static synthetic method _#new#tearOff() → wid::StandardWidget + return new wid::StandardWidget::•($creationLocationd_0dea112b090073317d4: #C21); + static synthetic method _#named#tearOff(dart.core::String name) → wid::StandardWidget + return new wid::StandardWidget::named(name, $creationLocationd_0dea112b090073317d4: #C23); + } +} +constants { + #C1 = null + #C2 = "org-dartlang-test:///main.dart" + #C3 = 4.0 + #C4 = 9.0 + #C5 = "MyWidget" + #C6 = dart.developer::CreationLocation {file:#C2, line:#C3, column:#C4, name:#C5} + #C7 = 10.0 + #C8 = 12.0 + #C9 = dart.developer::CreationLocation {file:#C2, line:#C7, column:#C8, name:#C5} + #C10 = 11.0 + #C11 = 27.0 + #C12 = "StandardWidget" + #C13 = dart.developer::CreationLocation {file:#C2, line:#C10, column:#C11, name:#C12} + #C14 = 14.0 + #C15 = dart.developer::CreationLocation {file:#C2, line:#C8, column:#C14, name:#C5} + #C16 = main::MyWidget {child:#C1, _location:#C15} + #C17 = "track-creation-locations" + #C18 = dart.core::pragma {name:#C17, options:#C1} + #C19 = "org-dartlang-test:///widget.dart" + #C20 = 7.0 + #C21 = dart.developer::CreationLocation {file:#C19, line:#C20, column:#C4, name:#C12} + #C22 = 8.0 + #C23 = dart.developer::CreationLocation {file:#C19, line:#C22, column:#C4, name:#C12} +} + + +Constructor coverage from constants: +org-dartlang-test:///main.dart: +- CreationLocation._ (from org-dartlang-sdk:///lib/developer/creation_tracking.dart) +- Object. (from org-dartlang-sdk:///lib/core/object.dart) +- MyWidget. (from org-dartlang-test:///main.dart:4:9) +- Widget. (from org-dartlang-test:///widget.dart:3:9) + +org-dartlang-test:///widget.dart: +- pragma._ (from org-dartlang-sdk:///lib/core/annotations.dart) +- Object. (from org-dartlang-sdk:///lib/core/object.dart) +- CreationLocation._ (from org-dartlang-sdk:///lib/developer/creation_tracking.dart) + diff --git a/pkg/front_end/testcases/incremental/general_location_transformer.yaml.world.2.expect b/pkg/front_end/testcases/incremental/general_location_transformer.yaml.world.2.expect new file mode 100644 index 00000000000..1bb84beea78 --- /dev/null +++ b/pkg/front_end/testcases/incremental/general_location_transformer.yaml.world.2.expect @@ -0,0 +1,89 @@ +main = main::main; +library from "org-dartlang-test:///main.dart" as main { + + import "org-dartlang-test:///widget.dart"; + + class MyWidget extends wid::Widget /*hasConstConstructor*/ { + final field wid::Widget? child; + const constructor •({initializing-formal wid::Widget? child = #C1, dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C1}) → main::MyWidget + : main::MyWidget::child = child, super wid::Widget::•($creationLocationd_0dea112b090073317d4: $creationLocationd_0dea112b090073317d4) + ; + static synthetic method _#new#tearOff({wid::Widget? child = #C1}) → main::MyWidget + return new main::MyWidget::•(child: child, $creationLocationd_0dea112b090073317d4: #C6); + synthetic no-such-method-forwarder set dart.developer::_location(dart.developer::CreationLocation? value) → void + return throw{for-error-handling} dart.core::NoSuchMethodError::withInvocation(this, new dart.core::_Invocation::setter(#C7, value)); + } + static method main() → void { + main::MyWidget w1 = new main::MyWidget::•(child: new wid::StandardWidget::•($creationLocationd_0dea112b090073317d4: #C11), $creationLocationd_0dea112b090073317d4: #C13); + const main::MyWidget w2 = #C20; + const main::MyWidget w3 = #C22; + } +} +library from "org-dartlang-test:///widget.dart" as wid { + + @#C24 + abstract class Widget extends dart.core::Object implements dart.developer::_HasCreationLocation /*hasConstConstructor*/ { + final field dart.developer::CreationLocation? dart.developer::_location; + const constructor •({dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C1}) → wid::Widget + : super dart.core::Object::•(), wid::Widget::_location = $creationLocationd_0dea112b090073317d4 + ; + } + class StandardWidget extends wid::Widget /*hasConstConstructor*/ { + const constructor •({dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C1}) → wid::StandardWidget + : super wid::Widget::•($creationLocationd_0dea112b090073317d4: $creationLocationd_0dea112b090073317d4) + ; + const constructor named(dart.core::String name, {dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C1}) → wid::StandardWidget + : super wid::Widget::•($creationLocationd_0dea112b090073317d4: $creationLocationd_0dea112b090073317d4) + ; + static synthetic method _#new#tearOff() → wid::StandardWidget + return new wid::StandardWidget::•($creationLocationd_0dea112b090073317d4: #C27); + static synthetic method _#named#tearOff(dart.core::String name) → wid::StandardWidget + return new wid::StandardWidget::named(name, $creationLocationd_0dea112b090073317d4: #C29); + } +} +constants { + #C1 = null + #C2 = "org-dartlang-test:///main.dart" + #C3 = 4.0 + #C4 = 9.0 + #C5 = "MyWidget" + #C6 = dart.developer::CreationLocation {file:#C2, line:#C3, column:#C4, name:#C5} + #C7 = #org-dartlang-test:///main.dart::_location= + #C8 = 10.0 + #C9 = 28.0 + #C10 = "StandardWidget" + #C11 = dart.developer::CreationLocation {file:#C2, line:#C8, column:#C9, name:#C10} + #C12 = 12.0 + #C13 = dart.developer::CreationLocation {file:#C2, line:#C8, column:#C12, name:#C5} + #C14 = 11.0 + #C15 = 45.0 + #C16 = dart.developer::CreationLocation {file:#C2, line:#C14, column:#C15, name:#C10} + #C17 = wid::StandardWidget {_location:#C16} + #C18 = 14.0 + #C19 = dart.developer::CreationLocation {file:#C2, line:#C14, column:#C18, name:#C5} + #C20 = main::MyWidget {child:#C17, _location:#C19} + #C21 = dart.developer::CreationLocation {file:#C2, line:#C12, column:#C18, name:#C5} + #C22 = main::MyWidget {child:#C20, _location:#C21} + #C23 = "track-creation-locations" + #C24 = dart.core::pragma {name:#C23, options:#C1} + #C25 = "org-dartlang-test:///widget.dart" + #C26 = 7.0 + #C27 = dart.developer::CreationLocation {file:#C25, line:#C26, column:#C4, name:#C10} + #C28 = 8.0 + #C29 = dart.developer::CreationLocation {file:#C25, line:#C28, column:#C4, name:#C10} +} + + +Constructor coverage from constants: +org-dartlang-test:///widget.dart: +- pragma._ (from org-dartlang-sdk:///lib/core/annotations.dart) +- Object. (from org-dartlang-sdk:///lib/core/object.dart) +- CreationLocation._ (from org-dartlang-sdk:///lib/developer/creation_tracking.dart) + +org-dartlang-test:///main.dart: +- CreationLocation._ (from org-dartlang-sdk:///lib/developer/creation_tracking.dart) +- Object. (from org-dartlang-sdk:///lib/core/object.dart) +- StandardWidget.named (from org-dartlang-test:///widget.dart:8:9) +- Widget. (from org-dartlang-test:///widget.dart:3:9) +- MyWidget. (from org-dartlang-test:///main.dart:4:9) + diff --git a/pkg/front_end/testcases/incremental/general_location_transformer_flutter.yaml b/pkg/front_end/testcases/incremental/general_location_transformer_flutter.yaml new file mode 100644 index 00000000000..10350389313 --- /dev/null +++ b/pkg/front_end/testcases/incremental/general_location_transformer_flutter.yaml @@ -0,0 +1,42 @@ +# 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. + +type: newworld +target: dartdevc +trackCreationLocations: true +worlds: + - entry: main.dart + sources: + main.dart: | + import 'package:flutter/src/widgets/framework.dart'; + + class Foo extends Widget {} + flutter/lib/src/widgets/framework.dart: | + abstract class Bar {} + + @pragma('track-creation-locations') + abstract class Widget extends Bar {} + .dart_tool/package_config.json: | + { + "configVersion": 2, + "packages": [ + { + "name": "flutter", + "rootUri": "../flutter/lib" + } + ] + } + expectedLibraryCount: 2 + - entry: main.dart + worldType: updated + invalidate: + - main.dart + expectInitializeFromDill: false + sources: + main.dart: | + import 'package:flutter/src/widgets/framework.dart'; + + class Foo extends Widget {} + expectedLibraryCount: 2 + advancedInvalidation: bodiesOnly diff --git a/pkg/front_end/testcases/incremental/general_location_transformer_flutter.yaml.world.1.expect b/pkg/front_end/testcases/incremental/general_location_transformer_flutter.yaml.world.1.expect new file mode 100644 index 00000000000..5d7f834db20 --- /dev/null +++ b/pkg/front_end/testcases/incremental/general_location_transformer_flutter.yaml.world.1.expect @@ -0,0 +1,49 @@ +main = ; +library from "package:flutter/src/widgets/framework.dart" as fra { + + abstract class Bar extends dart.core::Object { + synthetic constructor •() → fra::Bar + : super dart.core::Object::•() + ; + } + @#C3 + abstract class Widget extends fra::Bar implements dart.developer::_HasCreationLocation { + final field dart.developer::CreationLocation? dart.developer::_location; + synthetic constructor •({dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C2}) → fra::Widget + : super fra::Bar::•(), fra::Widget::_location = $creationLocationd_0dea112b090073317d4 + ; + } +} +library from "org-dartlang-test:///main.dart" as main { + + import "package:flutter/src/widgets/framework.dart"; + + class Foo extends fra::Widget { + synthetic constructor •({dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C2}) → main::Foo + : super fra::Widget::•($creationLocationd_0dea112b090073317d4: $creationLocationd_0dea112b090073317d4) + ; + static synthetic method _#new#tearOff() → main::Foo + return new main::Foo::•($creationLocationd_0dea112b090073317d4: #C8); + } +} +constants { + #C1 = "track-creation-locations" + #C2 = null + #C3 = dart.core::pragma {name:#C1, options:#C2} + #C4 = "org-dartlang-test:///main.dart" + #C5 = 3.0 + #C6 = 7.0 + #C7 = "Foo" + #C8 = dart.developer::CreationLocation {file:#C4, line:#C5, column:#C6, name:#C7} +} + + +Constructor coverage from constants: +org-dartlang-test:///main.dart: +- CreationLocation._ (from org-dartlang-sdk:///lib/developer/creation_tracking.dart) +- Object. (from org-dartlang-sdk:///lib/core/object.dart) + +org-dartlang-test:///flutter/lib/src/widgets/framework.dart: +- pragma._ (from org-dartlang-sdk:///lib/core/annotations.dart) +- Object. (from org-dartlang-sdk:///lib/core/object.dart) + diff --git a/pkg/front_end/testcases/incremental/general_location_transformer_flutter.yaml.world.2.expect b/pkg/front_end/testcases/incremental/general_location_transformer_flutter.yaml.world.2.expect new file mode 100644 index 00000000000..c3fe513533d --- /dev/null +++ b/pkg/front_end/testcases/incremental/general_location_transformer_flutter.yaml.world.2.expect @@ -0,0 +1,52 @@ +main = ; +library from "package:flutter/src/widgets/framework.dart" as fra { + + abstract class Bar extends dart.core::Object { + synthetic constructor •() → fra::Bar + : super dart.core::Object::•() + ; + } + @#C3 + abstract class Widget extends fra::Bar implements dart.developer::_HasCreationLocation { + final field dart.developer::CreationLocation? dart.developer::_location; + synthetic constructor •({dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C2}) → fra::Widget + : super fra::Bar::•(), fra::Widget::_location = $creationLocationd_0dea112b090073317d4 + ; + } +} +library from "org-dartlang-test:///main.dart" as main { + + import "package:flutter/src/widgets/framework.dart"; + + class Foo extends fra::Widget { + synthetic constructor •({dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C2}) → main::Foo + : super fra::Widget::•($creationLocationd_0dea112b090073317d4: $creationLocationd_0dea112b090073317d4) + ; + static synthetic method _#new#tearOff() → main::Foo + return new main::Foo::•($creationLocationd_0dea112b090073317d4: #C8); + synthetic no-such-method-forwarder set dart.developer::_location(dart.developer::CreationLocation? value) → void + return throw{for-error-handling} dart.core::NoSuchMethodError::withInvocation(this, new dart.core::_Invocation::setter(#C9, value)); + } +} +constants { + #C1 = "track-creation-locations" + #C2 = null + #C3 = dart.core::pragma {name:#C1, options:#C2} + #C4 = "org-dartlang-test:///main.dart" + #C5 = 3.0 + #C6 = 7.0 + #C7 = "Foo" + #C8 = dart.developer::CreationLocation {file:#C4, line:#C5, column:#C6, name:#C7} + #C9 = #org-dartlang-test:///main.dart::_location= +} + + +Constructor coverage from constants: +org-dartlang-test:///flutter/lib/src/widgets/framework.dart: +- pragma._ (from org-dartlang-sdk:///lib/core/annotations.dart) +- Object. (from org-dartlang-sdk:///lib/core/object.dart) + +org-dartlang-test:///main.dart: +- CreationLocation._ (from org-dartlang-sdk:///lib/developer/creation_tracking.dart) +- Object. (from org-dartlang-sdk:///lib/core/object.dart) + diff --git a/pkg/front_end/testcases/incremental/general_location_transformer_flutter_const.yaml b/pkg/front_end/testcases/incremental/general_location_transformer_flutter_const.yaml new file mode 100644 index 00000000000..b0986d66904 --- /dev/null +++ b/pkg/front_end/testcases/incremental/general_location_transformer_flutter_const.yaml @@ -0,0 +1,100 @@ +# 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. + +type: newworld +target: dartdevc +trackCreationLocations: true +worlds: + - entry: main.dart + sources: + main.dart: | + import 'foo.dart'; + Foo newFoo = new Foo(); + Bar newBar = new Bar(); + Bar constBar = const Bar(); + Baz newBaz = new Baz(); + Boz newBoz = new Boz(createNew: true); + Boz constBoz = new Boz(createNew: false); + foo.dart: | + import 'package:flutter/src/widgets/framework.dart'; + + class Foo extends Widget { + factory Foo() => const Foo._(); + + const Foo._(); + } + + class Bar extends Widget { + const factory Bar() = Bar._; + + const Bar._(); + } + + class Baz extends Widget { + factory Baz() => const Baz._(); + + const factory Baz._() = Baz.__; + + const Baz.__(); + } + + class Boz extends Widget { + factory Boz({required bool createNew}) { + if (createNew) { + return new Boz._(); + } else { + return const Boz._(); + } + } + + const Boz._(); + } + flutter/lib/src/widgets/framework.dart: | + abstract class Bar { + const Bar(); + } + @pragma('track-creation-locations') + abstract class Widget extends Bar { + const Widget(); + } + .dart_tool/package_config.json: | + { + "configVersion": 2, + "packages": [ + { + "name": "flutter", + "rootUri": "../flutter", + "packageUri": "lib/" + } + ] + } + expectedLibraryCount: 3 + + - entry: main.dart + worldType: updated + invalidate: + - main.dart + expectInitializeFromDill: false + sources: + main.dart: | + import 'foo.dart'; + expectedLibraryCount: 3 + advancedInvalidation: outlineChange + + - entry: main.dart + worldType: updated + invalidate: + - main.dart + expectInitializeFromDill: false + sources: + main.dart: | + import 'foo.dart'; + Foo newFoo = new Foo(); + Bar newBar = new Bar(); + Bar constBar = const Bar(); + Baz newBaz = new Baz(); + Boz newBoz = new Boz(createNew: true); + Boz constBoz = new Boz(createNew: false); + expectedLibraryCount: 3 + advancedInvalidation: outlineChange diff --git a/pkg/front_end/testcases/incremental/general_location_transformer_flutter_const.yaml.world.1.expect b/pkg/front_end/testcases/incremental/general_location_transformer_flutter_const.yaml.world.1.expect new file mode 100644 index 00000000000..06ade6b926a --- /dev/null +++ b/pkg/front_end/testcases/incremental/general_location_transformer_flutter_const.yaml.world.1.expect @@ -0,0 +1,161 @@ +main = ; +library from "package:flutter/src/widgets/framework.dart" as fra { + + abstract class Bar extends dart.core::Object /*hasConstConstructor*/ { + const constructor •() → fra::Bar + : super dart.core::Object::•() + ; + } + @#C3 + abstract class Widget extends fra::Bar implements dart.developer::_HasCreationLocation /*hasConstConstructor*/ { + final field dart.developer::CreationLocation? dart.developer::_location; + const constructor •({dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C2}) → fra::Widget + : super fra::Bar::•(), fra::Widget::_location = $creationLocationd_0dea112b090073317d4 + ; + } +} +library from "org-dartlang-test:///foo.dart" as foo { + + import "package:flutter/src/widgets/framework.dart"; + + class Foo extends fra::Widget /*hasConstConstructor*/ { + const constructor _({dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C2}) → foo::Foo + : super fra::Widget::•($creationLocationd_0dea112b090073317d4: $creationLocationd_0dea112b090073317d4) + ; + static factory •({dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C2}) → foo::Foo + return #C9; + static synthetic method _#new#tearOff() → foo::Foo + return foo::Foo::•($creationLocationd_0dea112b090073317d4: #C11); + static synthetic method _#_#tearOff() → foo::Foo + return new foo::Foo::_($creationLocationd_0dea112b090073317d4: #C14); + } + class Bar extends fra::Widget /*hasConstConstructor*/ { + const constructor _({dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C2}) → foo::Bar + : super fra::Widget::•($creationLocationd_0dea112b090073317d4: $creationLocationd_0dea112b090073317d4) + ; + static const factory •({dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C2}) → foo::Bar /* redirection-target: foo::Bar::_ */ + return new foo::Bar::_($creationLocationd_0dea112b090073317d4: $creationLocationd_0dea112b090073317d4); + static synthetic method _#new#tearOff() → foo::Bar + return new foo::Bar::_($creationLocationd_0dea112b090073317d4: #C18); + static synthetic method _#_#tearOff() → foo::Bar + return new foo::Bar::_($creationLocationd_0dea112b090073317d4: #C20); + } + class Baz extends fra::Widget /*hasConstConstructor*/ { + const constructor __({dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C2}) → foo::Baz + : super fra::Widget::•($creationLocationd_0dea112b090073317d4: $creationLocationd_0dea112b090073317d4) + ; + static factory •({dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C2}) → foo::Baz + return #C24; + static synthetic method _#new#tearOff() → foo::Baz + return foo::Baz::•($creationLocationd_0dea112b090073317d4: #C25); + static const factory _({dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C2}) → foo::Baz /* redirection-target: foo::Baz::__ */ + return new foo::Baz::__($creationLocationd_0dea112b090073317d4: $creationLocationd_0dea112b090073317d4); + static synthetic method _#_#tearOff() → foo::Baz + return new foo::Baz::__($creationLocationd_0dea112b090073317d4: #C27); + static synthetic method _#__#tearOff() → foo::Baz + return new foo::Baz::__($creationLocationd_0dea112b090073317d4: #C29); + } + class Boz extends fra::Widget /*hasConstConstructor*/ { + const constructor _({dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C2}) → foo::Boz + : super fra::Widget::•($creationLocationd_0dea112b090073317d4: $creationLocationd_0dea112b090073317d4) + ; + static factory •({required dart.core::bool createNew, dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C2}) → foo::Boz { + if(createNew) { + return new foo::Boz::_($creationLocationd_0dea112b090073317d4: $creationLocationd_0dea112b090073317d4); + } + else { + return #C33; + } + } + static synthetic method _#new#tearOff({required dart.core::bool createNew}) → foo::Boz + return foo::Boz::•(createNew: createNew, $creationLocationd_0dea112b090073317d4: #C35); + static synthetic method _#_#tearOff() → foo::Boz + return new foo::Boz::_($creationLocationd_0dea112b090073317d4: #C37); + } +} +library from "org-dartlang-test:///main.dart" as main { + + import "org-dartlang-test:///foo.dart"; + + static field foo::Foo newFoo = foo::Foo::•($creationLocationd_0dea112b090073317d4: #C40); + static field foo::Bar newBar = new foo::Bar::_($creationLocationd_0dea112b090073317d4: #C42); + static field foo::Bar constBar = #C45; + static field foo::Baz newBaz = foo::Baz::•($creationLocationd_0dea112b090073317d4: #C47); + static field foo::Boz newBoz = foo::Boz::•(createNew: true, $creationLocationd_0dea112b090073317d4: #C48); + static field foo::Boz constBoz = foo::Boz::•(createNew: false, $creationLocationd_0dea112b090073317d4: #C50); +} +constants { + #C1 = "track-creation-locations" + #C2 = null + #C3 = dart.core::pragma {name:#C1, options:#C2} + #C4 = "org-dartlang-test:///foo.dart" + #C5 = 4.0 + #C6 = 26.0 + #C7 = "Foo" + #C8 = dart.developer::CreationLocation {file:#C4, line:#C5, column:#C6, name:#C7} + #C9 = foo::Foo {_location:#C8} + #C10 = 11.0 + #C11 = dart.developer::CreationLocation {file:#C4, line:#C5, column:#C10, name:#C7} + #C12 = 6.0 + #C13 = 9.0 + #C14 = dart.developer::CreationLocation {file:#C4, line:#C12, column:#C13, name:#C7} + #C15 = 10.0 + #C16 = 17.0 + #C17 = "Bar" + #C18 = dart.developer::CreationLocation {file:#C4, line:#C15, column:#C16, name:#C17} + #C19 = 12.0 + #C20 = dart.developer::CreationLocation {file:#C4, line:#C19, column:#C13, name:#C17} + #C21 = 16.0 + #C22 = "Baz" + #C23 = dart.developer::CreationLocation {file:#C4, line:#C21, column:#C6, name:#C22} + #C24 = foo::Baz {_location:#C23} + #C25 = dart.developer::CreationLocation {file:#C4, line:#C21, column:#C10, name:#C22} + #C26 = 18.0 + #C27 = dart.developer::CreationLocation {file:#C4, line:#C26, column:#C16, name:#C22} + #C28 = 20.0 + #C29 = dart.developer::CreationLocation {file:#C4, line:#C28, column:#C13, name:#C22} + #C30 = 28.0 + #C31 = "Boz" + #C32 = dart.developer::CreationLocation {file:#C4, line:#C30, column:#C28, name:#C31} + #C33 = foo::Boz {_location:#C32} + #C34 = 24.0 + #C35 = dart.developer::CreationLocation {file:#C4, line:#C34, column:#C10, name:#C31} + #C36 = 32.0 + #C37 = dart.developer::CreationLocation {file:#C4, line:#C36, column:#C13, name:#C31} + #C38 = "org-dartlang-test:///main.dart" + #C39 = 2.0 + #C40 = dart.developer::CreationLocation {file:#C38, line:#C39, column:#C26, name:#C7} + #C41 = 3.0 + #C42 = dart.developer::CreationLocation {file:#C38, line:#C41, column:#C26, name:#C17} + #C43 = 22.0 + #C44 = dart.developer::CreationLocation {file:#C38, line:#C5, column:#C43, name:#C17} + #C45 = foo::Bar {_location:#C44} + #C46 = 5.0 + #C47 = dart.developer::CreationLocation {file:#C38, line:#C46, column:#C26, name:#C22} + #C48 = dart.developer::CreationLocation {file:#C38, line:#C12, column:#C26, name:#C31} + #C49 = 7.0 + #C50 = dart.developer::CreationLocation {file:#C38, line:#C49, column:#C28, name:#C31} +} + + +Constructor coverage from constants: +org-dartlang-test:///main.dart: +- CreationLocation._ (from org-dartlang-sdk:///lib/developer/creation_tracking.dart) +- Object. (from org-dartlang-sdk:///lib/core/object.dart) +- Bar._ (from org-dartlang-test:///foo.dart:12:9) +- Widget. (from org-dartlang-test:///flutter/lib/src/widgets/framework.dart:6:9) +- Bar. (from org-dartlang-test:///flutter/lib/src/widgets/framework.dart:2:9) + +org-dartlang-test:///foo.dart: +- CreationLocation._ (from org-dartlang-sdk:///lib/developer/creation_tracking.dart) +- Object. (from org-dartlang-sdk:///lib/core/object.dart) +- Foo._ (from org-dartlang-test:///foo.dart:6:9) +- Widget. (from org-dartlang-test:///flutter/lib/src/widgets/framework.dart:6:9) +- Bar. (from org-dartlang-test:///flutter/lib/src/widgets/framework.dart:2:9) +- Baz.__ (from org-dartlang-test:///foo.dart:20:9) +- Boz._ (from org-dartlang-test:///foo.dart:32:9) + +org-dartlang-test:///flutter/lib/src/widgets/framework.dart: +- pragma._ (from org-dartlang-sdk:///lib/core/annotations.dart) +- Object. (from org-dartlang-sdk:///lib/core/object.dart) + diff --git a/pkg/front_end/testcases/incremental/general_location_transformer_flutter_const.yaml.world.2.expect b/pkg/front_end/testcases/incremental/general_location_transformer_flutter_const.yaml.world.2.expect new file mode 100644 index 00000000000..f1d71e0b3b7 --- /dev/null +++ b/pkg/front_end/testcases/incremental/general_location_transformer_flutter_const.yaml.world.2.expect @@ -0,0 +1,135 @@ +main = ; +library from "package:flutter/src/widgets/framework.dart" as fra { + + abstract class Bar extends dart.core::Object /*hasConstConstructor*/ { + const constructor •() → fra::Bar + : super dart.core::Object::•() + ; + } + @#C3 + abstract class Widget extends fra::Bar implements dart.developer::_HasCreationLocation /*hasConstConstructor*/ { + final field dart.developer::CreationLocation? dart.developer::_location; + const constructor •({dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C2}) → fra::Widget + : super fra::Bar::•(), fra::Widget::_location = $creationLocationd_0dea112b090073317d4 + ; + } +} +library from "org-dartlang-test:///foo.dart" as foo { + + import "package:flutter/src/widgets/framework.dart"; + + class Foo extends fra::Widget /*hasConstConstructor*/ { + const constructor _({dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C2}) → foo::Foo + : super fra::Widget::•($creationLocationd_0dea112b090073317d4: $creationLocationd_0dea112b090073317d4) + ; + static factory •({dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C2}) → foo::Foo + return #C9; + static synthetic method _#new#tearOff() → foo::Foo + return foo::Foo::•($creationLocationd_0dea112b090073317d4: #C11); + static synthetic method _#_#tearOff() → foo::Foo + return new foo::Foo::_($creationLocationd_0dea112b090073317d4: #C14); + } + class Bar extends fra::Widget /*hasConstConstructor*/ { + const constructor _({dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C2}) → foo::Bar + : super fra::Widget::•($creationLocationd_0dea112b090073317d4: $creationLocationd_0dea112b090073317d4) + ; + static const factory •({dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C2}) → foo::Bar /* redirection-target: foo::Bar::_ */ + return new foo::Bar::_($creationLocationd_0dea112b090073317d4: $creationLocationd_0dea112b090073317d4); + static synthetic method _#new#tearOff() → foo::Bar + return new foo::Bar::_($creationLocationd_0dea112b090073317d4: #C18); + static synthetic method _#_#tearOff() → foo::Bar + return new foo::Bar::_($creationLocationd_0dea112b090073317d4: #C20); + } + class Baz extends fra::Widget /*hasConstConstructor*/ { + const constructor __({dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C2}) → foo::Baz + : super fra::Widget::•($creationLocationd_0dea112b090073317d4: $creationLocationd_0dea112b090073317d4) + ; + static factory •({dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C2}) → foo::Baz + return #C24; + static synthetic method _#new#tearOff() → foo::Baz + return foo::Baz::•($creationLocationd_0dea112b090073317d4: #C25); + static const factory _({dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C2}) → foo::Baz /* redirection-target: foo::Baz::__ */ + return new foo::Baz::__($creationLocationd_0dea112b090073317d4: $creationLocationd_0dea112b090073317d4); + static synthetic method _#_#tearOff() → foo::Baz + return new foo::Baz::__($creationLocationd_0dea112b090073317d4: #C27); + static synthetic method _#__#tearOff() → foo::Baz + return new foo::Baz::__($creationLocationd_0dea112b090073317d4: #C29); + } + class Boz extends fra::Widget /*hasConstConstructor*/ { + const constructor _({dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C2}) → foo::Boz + : super fra::Widget::•($creationLocationd_0dea112b090073317d4: $creationLocationd_0dea112b090073317d4) + ; + static factory •({required dart.core::bool createNew, dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C2}) → foo::Boz { + if(createNew) { + return new foo::Boz::_($creationLocationd_0dea112b090073317d4: $creationLocationd_0dea112b090073317d4); + } + else { + return #C33; + } + } + static synthetic method _#new#tearOff({required dart.core::bool createNew}) → foo::Boz + return foo::Boz::•(createNew: createNew, $creationLocationd_0dea112b090073317d4: #C35); + static synthetic method _#_#tearOff() → foo::Boz + return new foo::Boz::_($creationLocationd_0dea112b090073317d4: #C37); + } +} +library from "org-dartlang-test:///main.dart" as main { + + import "org-dartlang-test:///foo.dart"; + +} +constants { + #C1 = "track-creation-locations" + #C2 = null + #C3 = dart.core::pragma {name:#C1, options:#C2} + #C4 = "org-dartlang-test:///foo.dart" + #C5 = 4.0 + #C6 = 26.0 + #C7 = "Foo" + #C8 = dart.developer::CreationLocation {file:#C4, line:#C5, column:#C6, name:#C7} + #C9 = foo::Foo {_location:#C8} + #C10 = 11.0 + #C11 = dart.developer::CreationLocation {file:#C4, line:#C5, column:#C10, name:#C7} + #C12 = 6.0 + #C13 = 9.0 + #C14 = dart.developer::CreationLocation {file:#C4, line:#C12, column:#C13, name:#C7} + #C15 = 10.0 + #C16 = 17.0 + #C17 = "Bar" + #C18 = dart.developer::CreationLocation {file:#C4, line:#C15, column:#C16, name:#C17} + #C19 = 12.0 + #C20 = dart.developer::CreationLocation {file:#C4, line:#C19, column:#C13, name:#C17} + #C21 = 16.0 + #C22 = "Baz" + #C23 = dart.developer::CreationLocation {file:#C4, line:#C21, column:#C6, name:#C22} + #C24 = foo::Baz {_location:#C23} + #C25 = dart.developer::CreationLocation {file:#C4, line:#C21, column:#C10, name:#C22} + #C26 = 18.0 + #C27 = dart.developer::CreationLocation {file:#C4, line:#C26, column:#C16, name:#C22} + #C28 = 20.0 + #C29 = dart.developer::CreationLocation {file:#C4, line:#C28, column:#C13, name:#C22} + #C30 = 28.0 + #C31 = "Boz" + #C32 = dart.developer::CreationLocation {file:#C4, line:#C30, column:#C28, name:#C31} + #C33 = foo::Boz {_location:#C32} + #C34 = 24.0 + #C35 = dart.developer::CreationLocation {file:#C4, line:#C34, column:#C10, name:#C31} + #C36 = 32.0 + #C37 = dart.developer::CreationLocation {file:#C4, line:#C36, column:#C13, name:#C31} +} + + +Constructor coverage from constants: +org-dartlang-test:///foo.dart: +- CreationLocation._ (from org-dartlang-sdk:///lib/developer/creation_tracking.dart) +- Object. (from org-dartlang-sdk:///lib/core/object.dart) +- Foo._ (from org-dartlang-test:///foo.dart:6:9) +- Widget. (from org-dartlang-test:///flutter/lib/src/widgets/framework.dart:6:9) +- Bar. (from org-dartlang-test:///flutter/lib/src/widgets/framework.dart:2:9) +- Baz.__ (from org-dartlang-test:///foo.dart:20:9) +- Boz._ (from org-dartlang-test:///foo.dart:32:9) + +org-dartlang-test:///flutter/lib/src/widgets/framework.dart: +- pragma._ (from org-dartlang-sdk:///lib/core/annotations.dart) +- Object. (from org-dartlang-sdk:///lib/core/object.dart) + diff --git a/pkg/front_end/testcases/incremental/general_location_transformer_flutter_const.yaml.world.3.expect b/pkg/front_end/testcases/incremental/general_location_transformer_flutter_const.yaml.world.3.expect new file mode 100644 index 00000000000..8de63863eae --- /dev/null +++ b/pkg/front_end/testcases/incremental/general_location_transformer_flutter_const.yaml.world.3.expect @@ -0,0 +1,161 @@ +main = ; +library from "package:flutter/src/widgets/framework.dart" as fra { + + abstract class Bar extends dart.core::Object /*hasConstConstructor*/ { + const constructor •() → fra::Bar + : super dart.core::Object::•() + ; + } + @#C3 + abstract class Widget extends fra::Bar implements dart.developer::_HasCreationLocation /*hasConstConstructor*/ { + final field dart.developer::CreationLocation? dart.developer::_location; + const constructor •({dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C2}) → fra::Widget + : super fra::Bar::•(), fra::Widget::_location = $creationLocationd_0dea112b090073317d4 + ; + } +} +library from "org-dartlang-test:///foo.dart" as foo { + + import "package:flutter/src/widgets/framework.dart"; + + class Foo extends fra::Widget /*hasConstConstructor*/ { + const constructor _({dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C2}) → foo::Foo + : super fra::Widget::•($creationLocationd_0dea112b090073317d4: $creationLocationd_0dea112b090073317d4) + ; + static factory •({dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C2}) → foo::Foo + return #C9; + static synthetic method _#new#tearOff() → foo::Foo + return foo::Foo::•($creationLocationd_0dea112b090073317d4: #C11); + static synthetic method _#_#tearOff() → foo::Foo + return new foo::Foo::_($creationLocationd_0dea112b090073317d4: #C14); + } + class Bar extends fra::Widget /*hasConstConstructor*/ { + const constructor _({dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C2}) → foo::Bar + : super fra::Widget::•($creationLocationd_0dea112b090073317d4: $creationLocationd_0dea112b090073317d4) + ; + static const factory •({dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C2}) → foo::Bar /* redirection-target: foo::Bar::_ */ + return new foo::Bar::_($creationLocationd_0dea112b090073317d4: $creationLocationd_0dea112b090073317d4); + static synthetic method _#new#tearOff() → foo::Bar + return new foo::Bar::_($creationLocationd_0dea112b090073317d4: #C18); + static synthetic method _#_#tearOff() → foo::Bar + return new foo::Bar::_($creationLocationd_0dea112b090073317d4: #C20); + } + class Baz extends fra::Widget /*hasConstConstructor*/ { + const constructor __({dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C2}) → foo::Baz + : super fra::Widget::•($creationLocationd_0dea112b090073317d4: $creationLocationd_0dea112b090073317d4) + ; + static factory •({dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C2}) → foo::Baz + return #C24; + static synthetic method _#new#tearOff() → foo::Baz + return foo::Baz::•($creationLocationd_0dea112b090073317d4: #C25); + static const factory _({dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C2}) → foo::Baz /* redirection-target: foo::Baz::__ */ + return new foo::Baz::__($creationLocationd_0dea112b090073317d4: $creationLocationd_0dea112b090073317d4); + static synthetic method _#_#tearOff() → foo::Baz + return new foo::Baz::__($creationLocationd_0dea112b090073317d4: #C27); + static synthetic method _#__#tearOff() → foo::Baz + return new foo::Baz::__($creationLocationd_0dea112b090073317d4: #C29); + } + class Boz extends fra::Widget /*hasConstConstructor*/ { + const constructor _({dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C2}) → foo::Boz + : super fra::Widget::•($creationLocationd_0dea112b090073317d4: $creationLocationd_0dea112b090073317d4) + ; + static factory •({required dart.core::bool createNew, dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C2}) → foo::Boz { + if(createNew) { + return new foo::Boz::_($creationLocationd_0dea112b090073317d4: $creationLocationd_0dea112b090073317d4); + } + else { + return #C33; + } + } + static synthetic method _#new#tearOff({required dart.core::bool createNew}) → foo::Boz + return foo::Boz::•(createNew: createNew, $creationLocationd_0dea112b090073317d4: #C35); + static synthetic method _#_#tearOff() → foo::Boz + return new foo::Boz::_($creationLocationd_0dea112b090073317d4: #C37); + } +} +library from "org-dartlang-test:///main.dart" as main { + + import "org-dartlang-test:///foo.dart"; + + static field foo::Foo newFoo = foo::Foo::•($creationLocationd_0dea112b090073317d4: #C40); + static field foo::Bar newBar = new foo::Bar::_($creationLocationd_0dea112b090073317d4: #C42); + static field foo::Bar constBar = #C45; + static field foo::Baz newBaz = foo::Baz::•($creationLocationd_0dea112b090073317d4: #C47); + static field foo::Boz newBoz = foo::Boz::•(createNew: true, $creationLocationd_0dea112b090073317d4: #C48); + static field foo::Boz constBoz = foo::Boz::•(createNew: false, $creationLocationd_0dea112b090073317d4: #C50); +} +constants { + #C1 = "track-creation-locations" + #C2 = null + #C3 = dart.core::pragma {name:#C1, options:#C2} + #C4 = "org-dartlang-test:///foo.dart" + #C5 = 4.0 + #C6 = 26.0 + #C7 = "Foo" + #C8 = dart.developer::CreationLocation {file:#C4, line:#C5, column:#C6, name:#C7} + #C9 = foo::Foo {_location:#C8} + #C10 = 11.0 + #C11 = dart.developer::CreationLocation {file:#C4, line:#C5, column:#C10, name:#C7} + #C12 = 6.0 + #C13 = 9.0 + #C14 = dart.developer::CreationLocation {file:#C4, line:#C12, column:#C13, name:#C7} + #C15 = 10.0 + #C16 = 17.0 + #C17 = "Bar" + #C18 = dart.developer::CreationLocation {file:#C4, line:#C15, column:#C16, name:#C17} + #C19 = 12.0 + #C20 = dart.developer::CreationLocation {file:#C4, line:#C19, column:#C13, name:#C17} + #C21 = 16.0 + #C22 = "Baz" + #C23 = dart.developer::CreationLocation {file:#C4, line:#C21, column:#C6, name:#C22} + #C24 = foo::Baz {_location:#C23} + #C25 = dart.developer::CreationLocation {file:#C4, line:#C21, column:#C10, name:#C22} + #C26 = 18.0 + #C27 = dart.developer::CreationLocation {file:#C4, line:#C26, column:#C16, name:#C22} + #C28 = 20.0 + #C29 = dart.developer::CreationLocation {file:#C4, line:#C28, column:#C13, name:#C22} + #C30 = 28.0 + #C31 = "Boz" + #C32 = dart.developer::CreationLocation {file:#C4, line:#C30, column:#C28, name:#C31} + #C33 = foo::Boz {_location:#C32} + #C34 = 24.0 + #C35 = dart.developer::CreationLocation {file:#C4, line:#C34, column:#C10, name:#C31} + #C36 = 32.0 + #C37 = dart.developer::CreationLocation {file:#C4, line:#C36, column:#C13, name:#C31} + #C38 = "org-dartlang-test:///main.dart" + #C39 = 2.0 + #C40 = dart.developer::CreationLocation {file:#C38, line:#C39, column:#C26, name:#C7} + #C41 = 3.0 + #C42 = dart.developer::CreationLocation {file:#C38, line:#C41, column:#C26, name:#C17} + #C43 = 22.0 + #C44 = dart.developer::CreationLocation {file:#C38, line:#C5, column:#C43, name:#C17} + #C45 = foo::Bar {_location:#C44} + #C46 = 5.0 + #C47 = dart.developer::CreationLocation {file:#C38, line:#C46, column:#C26, name:#C22} + #C48 = dart.developer::CreationLocation {file:#C38, line:#C12, column:#C26, name:#C31} + #C49 = 7.0 + #C50 = dart.developer::CreationLocation {file:#C38, line:#C49, column:#C28, name:#C31} +} + + +Constructor coverage from constants: +org-dartlang-test:///foo.dart: +- CreationLocation._ (from org-dartlang-sdk:///lib/developer/creation_tracking.dart) +- Object. (from org-dartlang-sdk:///lib/core/object.dart) +- Foo._ (from org-dartlang-test:///foo.dart:6:9) +- Widget. (from org-dartlang-test:///flutter/lib/src/widgets/framework.dart:6:9) +- Bar. (from org-dartlang-test:///flutter/lib/src/widgets/framework.dart:2:9) +- Baz.__ (from org-dartlang-test:///foo.dart:20:9) +- Boz._ (from org-dartlang-test:///foo.dart:32:9) + +org-dartlang-test:///flutter/lib/src/widgets/framework.dart: +- pragma._ (from org-dartlang-sdk:///lib/core/annotations.dart) +- Object. (from org-dartlang-sdk:///lib/core/object.dart) + +org-dartlang-test:///main.dart: +- CreationLocation._ (from org-dartlang-sdk:///lib/developer/creation_tracking.dart) +- Object. (from org-dartlang-sdk:///lib/core/object.dart) +- Bar._ (from org-dartlang-test:///foo.dart:12:9) +- Widget. (from org-dartlang-test:///flutter/lib/src/widgets/framework.dart:6:9) +- Bar. (from org-dartlang-test:///flutter/lib/src/widgets/framework.dart:2:9) + diff --git a/pkg/front_end/testcases/incremental/general_location_transformer_jaspr.yaml b/pkg/front_end/testcases/incremental/general_location_transformer_jaspr.yaml new file mode 100644 index 00000000000..776624736dc --- /dev/null +++ b/pkg/front_end/testcases/incremental/general_location_transformer_jaspr.yaml @@ -0,0 +1,44 @@ +# 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. + +# This tests that Jaspr-style components, including redirecting factory +# constructors, are correctly transformed to include tracking information. + +type: newworld +target: dartdevc +trackCreationLocations: true +worlds: + - entry: main.dart + sources: + main.dart: | + @pragma('track-creation-locations') + abstract class Component { + const Component({this.key}); + + const factory Component.text(String text, {String? key}) = Text._; + + final String? key; + } + + class Text extends Component { + const Text._(this.text, {super.key}); + + final String text; + } + + class MyComponent extends Component { + const MyComponent(this.children, {this.id, super.key}); + + final List children; + final String? id; + } + + void main() { + var c1 = MyComponent([]); + var c2 = MyComponent([c1], id: '2'); + + const c3 = MyComponent([Component.text('Hello')]); + const c4 = MyComponent([c3, MyComponent([], id: 'x')], id: '4'); + } + expectedLibraryCount: 1 diff --git a/pkg/front_end/testcases/incremental/general_location_transformer_jaspr.yaml.world.1.expect b/pkg/front_end/testcases/incremental/general_location_transformer_jaspr.yaml.world.1.expect new file mode 100644 index 00000000000..de33d3ef5d7 --- /dev/null +++ b/pkg/front_end/testcases/incremental/general_location_transformer_jaspr.yaml.world.1.expect @@ -0,0 +1,89 @@ +main = main::main; +library from "org-dartlang-test:///main.dart" as main { + + @#C3 + abstract class Component extends dart.core::Object implements dart.developer::_HasCreationLocation /*hasConstConstructor*/ { + final field dart.core::String? key; + final field dart.developer::CreationLocation? dart.developer::_location; + const constructor •({initializing-formal dart.core::String? key = #C2, dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C2}) → main::Component + : main::Component::key = key, super dart.core::Object::•(), main::Component::_location = $creationLocationd_0dea112b090073317d4 + ; + static const factory text(dart.core::String text, {dart.core::String? key = #C2, dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C2}) → main::Component /* redirection-target: main::Text::_ */ + return new main::Text::_(text, key: key, $creationLocationd_0dea112b090073317d4: $creationLocationd_0dea112b090073317d4); + static synthetic method _#text#tearOff(dart.core::String text, {dart.core::String? key = #C2}) → main::Component + return new main::Text::_(text, key: key, $creationLocationd_0dea112b090073317d4: #C8); + } + class Text extends main::Component /*hasConstConstructor*/ { + final field dart.core::String text; + const constructor _(initializing-formal dart.core::String text, {super-initializing-formal dart.core::String? key = #C2, dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C2}) → main::Text + : main::Text::text = text, super main::Component::•(key: key, $creationLocationd_0dea112b090073317d4: $creationLocationd_0dea112b090073317d4) + ; + static synthetic method _#_#tearOff(dart.core::String text, {dart.core::String? key = #C2}) → main::Text + return new main::Text::_(text, key: key, $creationLocationd_0dea112b090073317d4: #C11); + } + class MyComponent extends main::Component /*hasConstConstructor*/ { + final field dart.core::List children; + final field dart.core::String? id; + const constructor •(initializing-formal dart.core::List children, {initializing-formal dart.core::String? id = #C2, super-initializing-formal dart.core::String? key = #C2, dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C2}) → main::MyComponent + : main::MyComponent::children = children, main::MyComponent::id = id, super main::Component::•(key: key, $creationLocationd_0dea112b090073317d4: $creationLocationd_0dea112b090073317d4) + ; + static synthetic method _#new#tearOff(dart.core::List children, {dart.core::String? id = #C2, dart.core::String? key = #C2}) → main::MyComponent + return new main::MyComponent::•(children, id: id, key: key, $creationLocationd_0dea112b090073317d4: #C13); + } + static method main() → void { + main::MyComponent c1 = new main::MyComponent::•([], $creationLocationd_0dea112b090073317d4: #C16); + main::MyComponent c2 = new main::MyComponent::•([c1], id: "2", $creationLocationd_0dea112b090073317d4: #C18); + const main::MyComponent c3 = #C27; + const main::MyComponent c4 = #C37; + } +} +constants { + #C1 = "track-creation-locations" + #C2 = null + #C3 = dart.core::pragma {name:#C1, options:#C2} + #C4 = "org-dartlang-test:///main.dart" + #C5 = 5.0 + #C6 = 17.0 + #C7 = "Text" + #C8 = dart.developer::CreationLocation {file:#C4, line:#C5, column:#C6, name:#C7} + #C9 = 11.0 + #C10 = 9.0 + #C11 = dart.developer::CreationLocation {file:#C4, line:#C9, column:#C10, name:#C7} + #C12 = "MyComponent" + #C13 = dart.developer::CreationLocation {file:#C4, line:#C6, column:#C10, name:#C12} + #C14 = 24.0 + #C15 = 12.0 + #C16 = dart.developer::CreationLocation {file:#C4, line:#C14, column:#C15, name:#C12} + #C17 = 25.0 + #C18 = dart.developer::CreationLocation {file:#C4, line:#C17, column:#C15, name:#C12} + #C19 = "Hello" + #C20 = 27.0 + #C21 = 37.0 + #C22 = dart.developer::CreationLocation {file:#C4, line:#C20, column:#C21, name:#C7} + #C23 = main::Text {text:#C19, key:#C2, _location:#C22} + #C24 = [#C23] + #C25 = 14.0 + #C26 = dart.developer::CreationLocation {file:#C4, line:#C20, column:#C25, name:#C12} + #C27 = main::MyComponent {children:#C24, id:#C2, key:#C2, _location:#C26} + #C28 = [] + #C29 = "x" + #C30 = 28.0 + #C31 = 31.0 + #C32 = dart.developer::CreationLocation {file:#C4, line:#C30, column:#C31, name:#C12} + #C33 = main::MyComponent {children:#C28, id:#C29, key:#C2, _location:#C32} + #C34 = [#C27, #C33] + #C35 = "4" + #C36 = dart.developer::CreationLocation {file:#C4, line:#C30, column:#C25, name:#C12} + #C37 = main::MyComponent {children:#C34, id:#C35, key:#C2, _location:#C36} +} + + +Constructor coverage from constants: +org-dartlang-test:///main.dart: +- pragma._ (from org-dartlang-sdk:///lib/core/annotations.dart) +- Object. (from org-dartlang-sdk:///lib/core/object.dart) +- CreationLocation._ (from org-dartlang-sdk:///lib/developer/creation_tracking.dart) +- Text._ (from org-dartlang-test:///main.dart:11:9) +- Component. (from org-dartlang-test:///main.dart:3:9) +- MyComponent. (from org-dartlang-test:///main.dart:17:9) + diff --git a/pkg/front_end/testcases/incremental/general_location_transformer_outline.yaml b/pkg/front_end/testcases/incremental/general_location_transformer_outline.yaml new file mode 100644 index 00000000000..04eb3f9fcc7 --- /dev/null +++ b/pkg/front_end/testcases/incremental/general_location_transformer_outline.yaml @@ -0,0 +1,64 @@ +# 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. + +type: newworld +target: dartdevc +trackCreationLocations: true +worlds: + - entry: main.dart + outlineOnly: true + skipOutlineBodyCheck: true + sources: + widget.dart: | + import 'dart:developer'; + + @pragma('track-creation-locations') + abstract class Widget { + const Widget(); + } + + class StandardWidget extends Widget { + const StandardWidget(); + const StandardWidget.named(String name); + } + main.dart: | + import 'widget.dart'; + + class MyWidget extends Widget { + const MyWidget({this.child}); + + final Widget? child; + } + + void main() { + var w1 = MyWidget(); + var w2 = StandardWidget.named('w2'); + const w3 = MyWidget(); + } + expectedLibraryCount: 2 + + - entry: main.dart + worldType: updated + outlineOnly: true + skipOutlineBodyCheck: true + expectInitializeFromDill: false + invalidate: + - main.dart + sources: + main.dart: | + import 'widget.dart'; + + class MyWidget extends Widget { + const MyWidget({this.child}); + + final Widget? child; + } + + void main() { + var w1 = MyWidget(child: StandardWidget()); + const w2 = MyWidget(child: StandardWidget.named('w2')); + const w3 = MyWidget(child: w2); + } + expectedLibraryCount: 2 + advancedInvalidation: bodiesOnly diff --git a/pkg/front_end/testcases/incremental/general_location_transformer_outline.yaml.world.1.expect b/pkg/front_end/testcases/incremental/general_location_transformer_outline.yaml.world.1.expect new file mode 100644 index 00000000000..a856510234d --- /dev/null +++ b/pkg/front_end/testcases/incremental/general_location_transformer_outline.yaml.world.1.expect @@ -0,0 +1,40 @@ +main = main::main; +library from "org-dartlang-test:///main.dart" as main { + + import "org-dartlang-test:///widget.dart"; + + class MyWidget extends wid::Widget /*hasConstConstructor*/ { + final field wid::Widget? child; + const constructor •({initializing-formal wid::Widget? child = null, dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = null}) → main::MyWidget + : main::MyWidget::child = child, super wid::Widget::•($creationLocationd_0dea112b090073317d4: $creationLocationd_0dea112b090073317d4) + ; + static synthetic method _#new#tearOff({wid::Widget? child}) → main::MyWidget + return new main::MyWidget::•(child: child, $creationLocationd_0dea112b090073317d4: const dart.developer::CreationLocation::_(file: "org-dartlang-test:///main.dart", line: 4, column: 9, name: "MyWidget")); + } + static method main() → void + ; +} +library from "org-dartlang-test:///widget.dart" as wid { + + import "dart:developer"; + + @/*original=dart.core::pragma::•*/ const dart.core::pragma::_("track-creation-locations") + abstract class Widget extends dart.core::Object implements dart.developer::_HasCreationLocation /*hasConstConstructor*/ { + final field dart.developer::CreationLocation? dart.developer::_location; + const constructor •({dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = null}) → wid::Widget + : super dart.core::Object::•(), wid::Widget::_location = $creationLocationd_0dea112b090073317d4 + ; + } + class StandardWidget extends wid::Widget /*hasConstConstructor*/ { + const constructor •({dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = null}) → wid::StandardWidget + : super wid::Widget::•($creationLocationd_0dea112b090073317d4: $creationLocationd_0dea112b090073317d4) + ; + const constructor named(dart.core::String name, {dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = null}) → wid::StandardWidget + : super wid::Widget::•($creationLocationd_0dea112b090073317d4: $creationLocationd_0dea112b090073317d4) + ; + static synthetic method _#new#tearOff() → wid::StandardWidget + return new wid::StandardWidget::•($creationLocationd_0dea112b090073317d4: const dart.developer::CreationLocation::_(file: "org-dartlang-test:///widget.dart", line: 9, column: 9, name: "StandardWidget")); + static synthetic method _#named#tearOff(dart.core::String name) → wid::StandardWidget + return new wid::StandardWidget::named(name, $creationLocationd_0dea112b090073317d4: const dart.developer::CreationLocation::_(file: "org-dartlang-test:///widget.dart", line: 10, column: 9, name: "StandardWidget")); + } +} diff --git a/pkg/front_end/testcases/incremental/general_location_transformer_outline.yaml.world.2.expect b/pkg/front_end/testcases/incremental/general_location_transformer_outline.yaml.world.2.expect new file mode 100644 index 00000000000..7ecd62e670f --- /dev/null +++ b/pkg/front_end/testcases/incremental/general_location_transformer_outline.yaml.world.2.expect @@ -0,0 +1,42 @@ +main = main::main; +library from "org-dartlang-test:///main.dart" as main { + + import "org-dartlang-test:///widget.dart"; + + class MyWidget extends wid::Widget /*hasConstConstructor*/ { + final field wid::Widget? child; + const constructor •({initializing-formal wid::Widget? child = null, dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = null}) → main::MyWidget + : main::MyWidget::child = child, super wid::Widget::•($creationLocationd_0dea112b090073317d4: $creationLocationd_0dea112b090073317d4) + ; + static synthetic method _#new#tearOff({wid::Widget? child}) → main::MyWidget + return new main::MyWidget::•(child: child, $creationLocationd_0dea112b090073317d4: const dart.developer::CreationLocation::_(file: "org-dartlang-test:///main.dart", line: 4, column: 9, name: "MyWidget")); + synthetic no-such-method-forwarder set dart.developer::_location(dart.developer::CreationLocation? value) → void + return throw{for-error-handling} dart.core::NoSuchMethodError::withInvocation(this, new dart.core::_Invocation::setter(#_location=, value)); + } + static method main() → void + ; +} +library from "org-dartlang-test:///widget.dart" as wid { + + import "dart:developer"; + + @/*original=dart.core::pragma::•*/ const dart.core::pragma::_("track-creation-locations") + abstract class Widget extends dart.core::Object implements dart.developer::_HasCreationLocation /*hasConstConstructor*/ { + final field dart.developer::CreationLocation? dart.developer::_location; + const constructor •({dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = null}) → wid::Widget + : super dart.core::Object::•(), wid::Widget::_location = $creationLocationd_0dea112b090073317d4 + ; + } + class StandardWidget extends wid::Widget /*hasConstConstructor*/ { + const constructor •({dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = null}) → wid::StandardWidget + : super wid::Widget::•($creationLocationd_0dea112b090073317d4: $creationLocationd_0dea112b090073317d4) + ; + const constructor named(dart.core::String name, {dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = null}) → wid::StandardWidget + : super wid::Widget::•($creationLocationd_0dea112b090073317d4: $creationLocationd_0dea112b090073317d4) + ; + static synthetic method _#new#tearOff() → wid::StandardWidget + return new wid::StandardWidget::•($creationLocationd_0dea112b090073317d4: const dart.developer::CreationLocation::_(file: "org-dartlang-test:///widget.dart", line: 9, column: 9, name: "StandardWidget")); + static synthetic method _#named#tearOff(dart.core::String name) → wid::StandardWidget + return new wid::StandardWidget::named(name, $creationLocationd_0dea112b090073317d4: const dart.developer::CreationLocation::_(file: "org-dartlang-test:///widget.dart", line: 10, column: 9, name: "StandardWidget")); + } +} diff --git a/pkg/front_end/testcases/incremental/no_outline_change_44_flutter.yaml b/pkg/front_end/testcases/incremental/no_outline_change_44_flutter.yaml index 9fd6e265648..3e7225fdff5 100644 --- a/pkg/front_end/testcases/incremental/no_outline_change_44_flutter.yaml +++ b/pkg/front_end/testcases/incremental/no_outline_change_44_flutter.yaml @@ -5,7 +5,7 @@ # Reproduce a crash. type: newworld -trackWidgetCreation: true +trackCreationLocations: true target: dartdevc # basically needed for widget creation to be run worlds: - entry: flutter_gallery/lib/main.dart diff --git a/pkg/front_end/testcases/incremental/no_outline_change_45_flutter.yaml b/pkg/front_end/testcases/incremental/no_outline_change_45_flutter.yaml index a8c9075098a..f1971086687 100644 --- a/pkg/front_end/testcases/incremental/no_outline_change_45_flutter.yaml +++ b/pkg/front_end/testcases/incremental/no_outline_change_45_flutter.yaml @@ -5,7 +5,7 @@ # Reproduce a crash. type: newworld -trackWidgetCreation: true +trackCreationLocations: true target: vm worlds: - entry: main.dart diff --git a/pkg/front_end/testcases/incremental/no_outline_change_45_flutter_prime_1.yaml b/pkg/front_end/testcases/incremental/no_outline_change_45_flutter_prime_1.yaml index 81fb091167a..48fb441ff81 100644 --- a/pkg/front_end/testcases/incremental/no_outline_change_45_flutter_prime_1.yaml +++ b/pkg/front_end/testcases/incremental/no_outline_change_45_flutter_prime_1.yaml @@ -5,7 +5,7 @@ # Reproduce a crash. type: newworld -trackWidgetCreation: true +trackCreationLocations: true target: vm worlds: - entry: main.dart diff --git a/pkg/front_end/testcases/incremental/super_key.yaml b/pkg/front_end/testcases/incremental/super_key.yaml index fbab4becec7..9feb224476e 100644 --- a/pkg/front_end/testcases/incremental/super_key.yaml +++ b/pkg/front_end/testcases/incremental/super_key.yaml @@ -4,7 +4,7 @@ type: newworld target: dartdevc -trackWidgetCreation: true +trackCreationLocations: true worlds: - entry: main.dart sources: diff --git a/pkg/frontend_server/lib/compute_kernel.dart b/pkg/frontend_server/lib/compute_kernel.dart index 02db28c4da7..eaaa05efe43 100644 --- a/pkg/frontend_server/lib/compute_kernel.dart +++ b/pkg/frontend_server/lib/compute_kernel.dart @@ -102,7 +102,15 @@ final ArgParser summaryArgsParser = new ArgParser() ..addFlag('reuse-compiler-result', defaultsTo: false) ..addFlag('use-incremental-compiler', defaultsTo: false) ..addOption('used-inputs') - ..addFlag('track-widget-creation', defaultsTo: false) + ..addFlag( + 'track-creation-locations', + defaultsTo: false, + aliases: [ + // TODO(http://dartbug.com/63225): Remove this once flutter is migrated + // to the new flag. + 'track-widget-creation', + ], + ) ..addFlag('include-unsupported-platform-library-stubs', defaultsTo: false) ..addMultiOption( 'enable-experiment', @@ -205,7 +213,7 @@ Future computeKernel( if (summaryOnly && !summary) { throw new ArgumentError('--summary-only conflicts with --no-summary'); } - bool trackWidgetCreation = parsedArgs['track-widget-creation'] as bool; + bool trackCreationLocations = parsedArgs['track-creation-locations'] as bool; bool includeUnsupportedPlatformLibraryStubs = parsedArgs['include-unsupported-platform-library-stubs'] as bool; @@ -214,7 +222,7 @@ Future computeKernel( String targetName = (parsedArgs['target'] as String?) ?? (summaryOnly ? 'ddc' : 'vm'); TargetFlags targetFlags = new TargetFlags( - trackWidgetCreation: trackWidgetCreation, + trackCreationLocations: trackCreationLocations, includeUnsupportedPlatformLibraryStubs: includeUnsupportedPlatformLibraryStubs, ); @@ -392,8 +400,8 @@ Future computeKernel( previousState, { "target=$targetName", - // trackWidgetCreation is in TargetFlags. - "trackWidgetCreation=$trackWidgetCreation", + // trackCreationLocations is in TargetFlags. + "trackCreationLocations=$trackCreationLocations", // includeUnsupportedPlatformLibraryStubs is in TargetFlags. "includeUnsupportedPlatformLibraryStubs=" "$includeUnsupportedPlatformLibraryStubs", diff --git a/pkg/frontend_server/lib/frontend_server.dart b/pkg/frontend_server/lib/frontend_server.dart index 0fc7707b7be..1885b4b6741 100644 --- a/pkg/frontend_server/lib/frontend_server.dart +++ b/pkg/frontend_server/lib/frontend_server.dart @@ -261,9 +261,16 @@ ArgParser argParser = new ArgParser(allowTrailingOptions: true) hide: true, ) ..addFlag( - 'track-widget-creation', - help: 'Run a kernel transformer to track creation locations for widgets.', + 'track-creation-locations', + help: + 'Run a kernel transformer to track creation locations for' + ' classes annotated with @pragma(\'track-creation-locations\').', defaultsTo: false, + aliases: [ + // TODO(http://dartbug.com/63225): Remove this once flutter is migrated + // to the new flag. + 'track-widget-creation', + ], ) ..addMultiOption( 'delete-tostring-package-uri', @@ -800,7 +807,7 @@ class FrontendCompiler implements CompilerInterface { final bool minimalKernel = options['minimal-kernel']; compilerOptions.target = createFrontEndTarget( options['target'], - trackWidgetCreation: options['track-widget-creation'], + trackCreationLocations: options['track-creation-locations'], supportMirrors: options['support-mirrors'] ?? !(aot || minimalKernel), includeUnsupportedPlatformLibraryStubs: options['include-unsupported-platform-library-stubs'], diff --git a/pkg/kernel/lib/target/targets.dart b/pkg/kernel/lib/target/targets.dart index 2e0b21af905..ea452ac49fe 100644 --- a/pkg/kernel/lib/target/targets.dart +++ b/pkg/kernel/lib/target/targets.dart @@ -12,7 +12,7 @@ import '../verifier.dart'; import 'changed_structure_notifier.dart'; class TargetFlags { - final bool trackWidgetCreation; + final bool trackCreationLocations; final bool supportMirrors; /// Whether the backend expects closure contexts to be present in the AST. @@ -32,7 +32,7 @@ class TargetFlags { final bool includeUnsupportedPlatformLibraryStubs; const TargetFlags({ - this.trackWidgetCreation = false, + this.trackCreationLocations = false, this.supportMirrors = true, this.isClosureContextLoweringEnabled = false, this.constKeepLocalsIndicator, @@ -43,7 +43,7 @@ class TargetFlags { bool operator ==(other) { if (identical(this, other)) return true; return other is TargetFlags && - trackWidgetCreation == other.trackWidgetCreation && + trackCreationLocations == other.trackCreationLocations && supportMirrors == other.supportMirrors && includeUnsupportedPlatformLibraryStubs == other.includeUnsupportedPlatformLibraryStubs && @@ -53,7 +53,7 @@ class TargetFlags { @override int get hashCode { int hash = 485786; - hash = 0x3fffffff & (hash * 31 + (hash ^ trackWidgetCreation.hashCode)); + hash = 0x3fffffff & (hash * 31 + (hash ^ trackCreationLocations.hashCode)); hash = 0x3fffffff & (hash * 31 + (hash ^ supportMirrors.hashCode)); hash = 0x3fffffff & @@ -340,7 +340,11 @@ abstract class Target { /// This transformation is not applied when compiling full kernel programs to /// prevent affecting the internal invariants of the compiler and accidentally /// slowing down compilation. - void performOutlineTransformations(Component component) {} + void performOutlineTransformations( + Component component, { + List? libraries, + ChangedStructureNotifier? changedStructureNotifier, + }) {} /// Perform target-specific operations on the [Component] storing the outlines /// when generating summaries. @@ -821,7 +825,7 @@ class TestTargetFlags extends TargetFlags { final Set unsupportedDartLibraries; const TestTargetFlags({ - bool trackWidgetCreation = false, + bool trackCreationLocations = false, this.forceLateLoweringsForTesting, this.forceLateLoweringSentinelForTesting, this.forceStaticFieldLoweringForTesting, @@ -831,7 +835,7 @@ class TestTargetFlags extends TargetFlags { this.unsupportedDartLibraries = const {}, bool isClosureContextLoweringEnabled = false, }) : super( - trackWidgetCreation: trackWidgetCreation, + trackCreationLocations: trackCreationLocations, isClosureContextLoweringEnabled: isClosureContextLoweringEnabled, ); } @@ -1040,8 +1044,16 @@ class TargetWrapper extends Target { } @override - void performOutlineTransformations(Component component) { - _target.performOutlineTransformations(component); + void performOutlineTransformations( + Component component, { + List? libraries, + ChangedStructureNotifier? changedStructureNotifier, + }) { + _target.performOutlineTransformations( + component, + libraries: libraries, + changedStructureNotifier: changedStructureNotifier, + ); } @override @@ -1131,10 +1143,10 @@ mixin SummaryMixin on Target { super.performOutlineComponentOperations(component); if (!excludeNonSources) return; - List libraries = new List.of(component.libraries); + List componentLibraries = new List.of(component.libraries); component.libraries.clear(); Set include = sources.toSet(); - for (Library library in libraries) { + for (Library library in componentLibraries) { if (include.contains(library.importUri)) { component.libraries.add(library); } else { diff --git a/pkg/kernel/lib/transformations/track_widget_constructor_locations.dart b/pkg/kernel/lib/transformations/track_widget_constructor_locations.dart index 10c2dadc3c4..1f10718409f 100644 --- a/pkg/kernel/lib/transformations/track_widget_constructor_locations.dart +++ b/pkg/kernel/lib/transformations/track_widget_constructor_locations.dart @@ -61,7 +61,6 @@ void _maybeAddCreationLocationArgument( Arguments arguments, FunctionNode function, Expression creationLocation, - Class locationClass, ) { if (_hasNamedArgument(arguments, _creationLocationParameterName)) { return; @@ -120,15 +119,6 @@ bool _maybeAddNamedParameter( /// and this is used as the location value for all Widget constructor /// invocations within the method. class _WidgetCallSiteTransformer extends Transformer { - /// The [Widget] class defined in the `package:flutter` library. - /// - /// Used to perform is-tests to determine whether Dart constructor calls are - /// creating [Widget] objects. - final Class _widgetClass; - - /// The _Location class defined in the `package:flutter` library. - final Class _locationClass; - final WidgetCreatorTracker _tracker; /// The creation location parameter of the extension factory method enclosing @@ -151,13 +141,8 @@ class _WidgetCallSiteTransformer extends Transformer { /// of the library. Library? _currentLibrary; - _WidgetCallSiteTransformer({ - required Class widgetClass, - required Class locationClass, - required WidgetCreatorTracker tracker, - }) : _widgetClass = widgetClass, - _locationClass = locationClass, - _tracker = tracker; + _WidgetCallSiteTransformer({required WidgetCreatorTracker tracker}) + : _tracker = tracker; /// Builds a call to the const constructor of the _Location /// object specifying the location where a constructor call was made and @@ -169,7 +154,12 @@ class _WidgetCallSiteTransformer extends Transformer { /// constructor call but it is convenient to bundle the location and names /// of the parameters passed in so that tools can show parameter locations /// without re-parsing the source code. - ConstructorInvocation _constructLocation(Location location, {String? name}) { + + ConstructorInvocation _constructLocation( + Location location, { + required Class locationClass, + String? name, + }) { final List arguments = [ new NamedExpression('file', new StringLiteral(location.file.toString())), new NamedExpression('line', new IntLiteral(location.line)), @@ -178,7 +168,7 @@ class _WidgetCallSiteTransformer extends Transformer { ]; return new ConstructorInvocation( - _locationClass.constructors.first, + locationClass.constructors.first, new Arguments([], named: arguments), isConst: true, ); @@ -210,10 +200,6 @@ class _WidgetCallSiteTransformer extends Transformer { return node; } - bool _isSubclassOfWidget(Class clazz) { - return _tracker._isSubclassOf(clazz, _widgetClass); - } - bool _isWidgetFactory(Procedure node) { return node.isExtensionMember && _hasNamedParameter(node.function, _creationLocationParameterName); @@ -225,7 +211,10 @@ class _WidgetCallSiteTransformer extends Transformer { final Procedure target = node.target; if (target.isFactory) { final Class constructedClass = target.enclosingClass!; - if (!_isSubclassOfWidget(constructedClass)) { + final _TrackingClasses? tracking = _tracker._getTrackingClasses( + constructedClass, + ); + if (tracking == null) { return node; } @@ -234,11 +223,16 @@ class _WidgetCallSiteTransformer extends Transformer { target.function, constructedClass: constructedClass, isConst: node.isConst, + locationClass: tracking.locationClass, ); return node; } - if (_isWidgetFactory(target)) { - _addLocationArgument(node, target.function); + if (_isWidgetFactory(target) && _tracker._locationClass != null) { + _addLocationArgument( + node, + target.function, + locationClass: _tracker._locationClass!, + ); return node; } return node; @@ -249,6 +243,7 @@ class _WidgetCallSiteTransformer extends Transformer { FunctionNode function, { Class? constructedClass, bool isConst = false, + required Class locationClass, }) { Expression? location = _currentExtensionFactoryLocationParameter; if (location == null || @@ -261,14 +256,10 @@ class _WidgetCallSiteTransformer extends Transformer { function, constructedClass, isConst: isConst, + locationClass: locationClass, ); } - _maybeAddCreationLocationArgument( - node.arguments, - function, - location, - _locationClass, - ); + _maybeAddCreationLocationArgument(node.arguments, function, location); } @override @@ -277,7 +268,10 @@ class _WidgetCallSiteTransformer extends Transformer { final Constructor constructor = node.target; final Class constructedClass = constructor.enclosingClass; - if (!_isSubclassOfWidget(constructedClass)) { + final _TrackingClasses? tracking = _tracker._getTrackingClasses( + constructedClass, + ); + if (tracking == null) { return node; } @@ -286,6 +280,7 @@ class _WidgetCallSiteTransformer extends Transformer { constructor.function, constructedClass: constructedClass, isConst: node.isConst, + locationClass: tracking.locationClass, ); return node; } @@ -295,6 +290,7 @@ class _WidgetCallSiteTransformer extends Transformer { FunctionNode function, Class? constructedClass, { bool isConst = false, + required Class locationClass, }) { assert(constructedClass != null || !isConst); @@ -302,9 +298,9 @@ class _WidgetCallSiteTransformer extends Transformer { // argument to the factory constructor rather than the location if (constructedClass != null && _currentFactory != null && - _tracker._isSubclassOf( + _tracker._isSubclassWhere( constructedClass, - _currentFactory!.enclosingClass!, + (Class c) => c == _currentFactory!.enclosingClass!, ) && // If the constructor invocation is constant we cannot refer to the // location parameter of the surrounding factory since it isn't a @@ -319,8 +315,13 @@ class _WidgetCallSiteTransformer extends Transformer { } } + if (node.location == null) { + return new NullLiteral(); + } + return _constructLocation( node.location!, + locationClass: locationClass, name: constructedClass?.name ?? // For extension factory methods we use the name of the method. @@ -360,12 +361,20 @@ class _WidgetCallSiteTransformer extends Transformer { /// invocations within the method. class WidgetCreatorTracker { bool _foundClasses = false; - late Class _widgetClass; - late Class _locationClass; - /// Marker interface indicating that a private _location field is - /// available. - late Class _hasCreationLocationClass; + /// The [Widget] class defined in the `package:flutter` library. + /// + /// Used to perform is-tests to determine whether Dart constructor calls are + /// creating [Widget] objects. + Class? _widgetClass; + + /// The _Location class defined in the `package:flutter` library. + Class? _locationClass; + + /// The _HasCreationLocation class defined in the `package:flutter` library. + /// + /// Marker interface indicating that a private _location field is available. + Class? _hasCreationLocationClass; /// Annotation class used to mark an extension method as a "Widget factory". /// @@ -374,20 +383,27 @@ class WidgetCreatorTracker { /// method. Class? _widgetFactoryClass; - void _resolveFlutterClasses(Iterable libraries) { + /// The _HasCreationLocation class defined in the `dart:developer` library. + Class? _developerHasCreationLocationClass; + + /// The CreationLocation class defined in the `dart:developer` library. + Class? _developerCreationLocationClass; + + void _resolveWellKnownClasses(Iterable libraries) { // If the Widget or Debug location classes have been updated we need to get // the latest version - bool foundWidgetClass = false; - bool foundHasCreationLocationClass = false; - bool foundLocationClass = false; + for (Library library in libraries) { final Uri importUri = library.importUri; + + // Legacy Case: Search for hardcoded Flutter classes. + // TODO(http://dartbug.com/63225): Remove this once Flutter is migrated + // to the new API. if (importUri.isScheme('package')) { if (importUri.path == 'flutter/src/widgets/framework.dart') { for (Class class_ in library.classes) { if (class_.name == 'Widget') { _widgetClass = class_; - foundWidgetClass = true; } } } else { @@ -395,10 +411,8 @@ class WidgetCreatorTracker { for (Class class_ in library.classes) { if (class_.name == '_HasCreationLocation') { _hasCreationLocationClass = class_; - foundHasCreationLocationClass = true; } else if (class_.name == '_Location') { _locationClass = class_; - foundLocationClass = true; } else if (class_.name == '_WidgetFactory') { _widgetFactoryClass = class_; } @@ -406,43 +420,62 @@ class WidgetCreatorTracker { } } } + + // New Case: Search for classes in `dart:developer` + if (importUri.isScheme('dart') && importUri.path == 'developer') { + for (Class class_ in library.classes) { + if (class_.name == '_HasCreationLocation') { + _developerHasCreationLocationClass = class_; + } else if (class_.name == 'CreationLocation') { + _developerCreationLocationClass = class_; + } + } + } } // TODO(johnniwinther): Require the [_widgetFactoryClass] once the // `widgetFactory` is stable in flutter. _foundClasses = - foundWidgetClass && foundHasCreationLocationClass && foundLocationClass; + (_widgetClass != null && + _hasCreationLocationClass != null && + _locationClass != null) || + (_developerHasCreationLocationClass != null && + _developerCreationLocationClass != null); } - /// Modify [clazz] to add a field named [_locationFieldName] that is the - /// first parameter of all constructors of the class. + /// Modify [clazz] to add the location field that is + /// the first parameter of all constructors of the class. /// /// This method should only be called for classes that implement but do not - /// extend [Widget]. - void _transformClassImplementingWidget( + /// extend the tracking base class. + void _transformClassImplementingTrackingClass( Class clazz, + _TrackingClasses tracking, ChangedStructureNotifier? changedStructureNotifier, ) { if (clazz.fields.any( - (Field field) => field.name.text == _locationFieldName, + (Field field) => field.name.text == tracking.locationFieldName, )) { // This class has already been transformed. Skip return; } clazz.implementedTypes.add( - new Supertype(_hasCreationLocationClass, []), + new Supertype(tracking.hasCreationLocationClass, []), ); changedStructureNotifier?.registerClassHierarchyChange(clazz); // We intentionally use the library context of the _HasCreationLocation // class for the private field even if [clazz] is in a different library - // so that all classes implementing Widget behave consistently. + // so that all classes implementing the tracking class behave consistently. final Name fieldName = new Name( - _locationFieldName, - _hasCreationLocationClass.enclosingLibrary, + tracking.locationFieldName, + tracking.hasCreationLocationClass.enclosingLibrary, ); final Field locationField = new Field.immutable( fieldName, - type: new InterfaceType(_locationClass, clazz.enclosingLibrary.nullable), + type: new InterfaceType( + tracking.locationClass, + clazz.enclosingLibrary.nullable, + ), isFinal: true, fieldReference: clazz.reference.canonicalName ?.getChildFromFieldWithName(fieldName) @@ -470,7 +503,7 @@ class WidgetCreatorTracker { final VariableDeclaration variable = new VariableDeclaration( _creationLocationParameterName, type: new InterfaceType( - _locationClass, + tracking.locationClass, clazz.enclosingLibrary.nullable, ), initializer: new NullLiteral(), @@ -492,7 +525,6 @@ class WidgetCreatorTracker { initializer.arguments, initializer.target.function, new VariableGet(variable), - _locationClass, ); hasRedirectingInitializer = true; break; @@ -512,7 +544,7 @@ class WidgetCreatorTracker { // constructor.initializers.add(new AssertInitializer( // new AssertStatement( // new IsExpression( - // new VariableGet(variable), _locationClass.thisType), + // new VariableGet(variable), tracking.locationClass.thisType), // conditionStartOffset: constructor.fileOffset, // conditionEndOffset: constructor.fileOffset, // ))); @@ -525,7 +557,7 @@ class WidgetCreatorTracker { /// Transform the given [libraries]. /// - /// The libraries from [module] is searched for the Widget class, + /// The [moduleLibraries] are searched for the Widget class, /// the _Location class, the _HasCreationLocation class and the /// _WidgetFactory class. /// If the component does not contain them, the ones from a previous run is @@ -537,18 +569,18 @@ class WidgetCreatorTracker { /// compilation where the class hierarchy is kept between compiles and thus /// has to be kept up to date. void transform( - Component module, List libraries, + List moduleLibraries, ChangedStructureNotifier? changedStructureNotifier, ) { if (libraries.isEmpty) { return; } - _resolveFlutterClasses(module.libraries); + _resolveWellKnownClasses(moduleLibraries); if (!_foundClasses) { - // This application doesn't actually use the package:flutter library. + // Neither package:flutter nor dart:developer tracking classes found. return; } @@ -579,11 +611,7 @@ class WidgetCreatorTracker { // Transform call sites to pass the location parameter. final _WidgetCallSiteTransformer callsiteTransformer = - new _WidgetCallSiteTransformer( - widgetClass: _widgetClass, - locationClass: _locationClass, - tracker: this, - ); + new _WidgetCallSiteTransformer(tracker: this); for (Library library in libraries) { callsiteTransformer.enterLibrary(library); @@ -592,26 +620,110 @@ class WidgetCreatorTracker { } } - bool _isSubclassOfWidget(Class clazz) => _isSubclassOf(clazz, _widgetClass); + _TrackingClasses? _getTrackingClasses(Class clazz) { + // Legacy Case: Check for widget class. + if (_isSubclassOfWidget(clazz)) { + if (_hasCreationLocationClass != null && _locationClass != null) { + return new _TrackingClasses( + hasCreationLocationClass: _hasCreationLocationClass!, + locationClass: _locationClass!, + locationFieldName: _locationFieldName, + ); + } + } - bool _isSubclassOf(Class a, Class b) { + // New Case: Check for 'pragma('track-creation-locations')' annotation. + if (_hasTrackCreationLocationsPragmaAnnotation(clazz)) { + if (_developerHasCreationLocationClass != null && + _developerCreationLocationClass != null) { + return new _TrackingClasses( + hasCreationLocationClass: _developerHasCreationLocationClass!, + locationClass: _developerCreationLocationClass!, + locationFieldName: _locationFieldName, + ); + } + } + + return null; + } + + bool _isSubclassOfWidget(Class clazz) { + if (_widgetClass == null) return false; + return _isSubclassWhere(clazz, (Class c) => c == _widgetClass); + } + + bool _hasTrackCreationLocationsPragmaAnnotation(Class clazz) { + if (_developerHasCreationLocationClass == null) return false; + return _isSubclassWhere(clazz, (Class c) { + for (Expression annotation in c.annotations) { + // Case before constant evaluation (newly compiled modules). + if (annotation is RedirectingFactoryInvocation) { + final expression = annotation.expression; + + if (expression is ConstructorInvocation) { + final Class enclosingClass = expression.target.enclosingClass; + + if (enclosingClass.name == 'pragma' && + enclosingClass.enclosingLibrary.importUri.toString() == + 'dart:core') { + if (expression.arguments.positional.isNotEmpty) { + final Expression firstArg = + expression.arguments.positional.first; + if (firstArg is StringLiteral && + firstArg.value == 'track-creation-locations') { + return true; + } + } + } + } + } + // Case after constant evaluation (incremental or modular compilation). + if (annotation case ConstantExpression( + constant: final InstanceConstant constant, + )) { + final Class enclosingClass = constant.classNode; + + if (enclosingClass.name == 'pragma' && + enclosingClass.enclosingLibrary.importUri.toString() == + 'dart:core') { + for (final Constant value in constant.fieldValues.values) { + if (value case StringConstant( + value: 'track-creation-locations', + )) { + return true; + } + } + } + } + } + return false; + }); + } + + bool _isSubclassWhere(Class a, bool Function(Class b) predicate) { // TODO(askesc): Cache results. // TODO(askesc): Test for subtype rather than subclass. Class? current = a; while (current != null) { - if (current == b) return true; + if (predicate(current)) return true; current = current.superclass; } return false; } bool _hasWidgetFactoryAnnotation(Procedure node) => - _isAnnotatedWithNamedValueOfType(node, _widgetFactoryClass!); + _isAnnotatedWithNamedValueOfType( + node, + _widgetFactoryClass, + _widgetFactoryClass, + ); bool _isAnnotatedWithNamedValueOfType( Annotatable node, - Class annotationClass, + Class? annotationClass, + Class? typeClass, ) { + if (annotationClass == null || typeClass == null) return false; return node.annotations.any((annotation) { if (annotation is! StaticGet) { return false; @@ -627,7 +739,7 @@ class WidgetCreatorTracker { if (type.nullability == Nullability.nullable) { return false; } - return type.classNode == _widgetFactoryClass; + return type.classNode == typeClass; }); } @@ -637,7 +749,8 @@ class WidgetCreatorTracker { Class clazz, ChangedStructureNotifier? changedStructureNotifier, ) { - if (!_isSubclassOfWidget(clazz) || + final _TrackingClasses? tracking = _getTrackingClasses(clazz); + if (tracking == null || !librariesToBeTransformed.contains(clazz.enclosingLibrary) || !transformedClasses.add(clazz)) { return; @@ -661,7 +774,7 @@ class WidgetCreatorTracker { new VariableDeclaration( _creationLocationParameterName, type: new InterfaceType( - _locationClass, + tracking.locationClass, clazz.enclosingLibrary.nullable, ), initializer: new NullLiteral(), @@ -672,8 +785,14 @@ class WidgetCreatorTracker { // Handle the widget class and classes that implement but do not extend the // widget class. - if (!_isSubclassOfWidget(clazz.superclass!)) { - _transformClassImplementingWidget(clazz, changedStructureNotifier); + if (clazz.superclass == null || + _getTrackingClasses(clazz.superclass!)?.hasCreationLocationClass != + tracking.hasCreationLocationClass) { + _transformClassImplementingTrackingClass( + clazz, + tracking, + changedStructureNotifier, + ); return; } @@ -688,7 +807,7 @@ class WidgetCreatorTracker { final VariableDeclaration variable = new VariableDeclaration( _creationLocationParameterName, type: new InterfaceType( - _locationClass, + tracking.locationClass, clazz.enclosingLibrary.nullable, ), initializer: new NullLiteral(), @@ -717,16 +836,17 @@ class WidgetCreatorTracker { initializer.arguments, initializer.target.function, new VariableGet(variable), - _locationClass, - ); - } else if (initializer is SuperInitializer && - _isSubclassOfWidget(initializer.target.enclosingClass)) { - _maybeAddCreationLocationArgument( - initializer.arguments, - initializer.target.function, - new VariableGet(variable), - _locationClass, ); + } else if (initializer is SuperInitializer) { + final Class superclass = initializer.target.enclosingClass; + if (_getTrackingClasses(superclass)?.hasCreationLocationClass == + tracking.hasCreationLocationClass) { + _maybeAddCreationLocationArgument( + initializer.arguments, + initializer.target.function, + new VariableGet(variable), + ); + } } } } @@ -761,7 +881,7 @@ class WidgetCreatorTracker { new VariableDeclaration( _creationLocationParameterName, type: new InterfaceType( - _locationClass, + _locationClass!, extension.enclosingLibrary.nullable, ), initializer: new NullLiteral(), @@ -775,7 +895,7 @@ class WidgetCreatorTracker { new VariableDeclaration( _creationLocationParameterName, type: new InterfaceType( - _locationClass, + _locationClass!, extension.enclosingLibrary.nullable, ), initializer: new NullLiteral(), @@ -785,3 +905,17 @@ class WidgetCreatorTracker { } } } + +/// Holds the set of creation location classes, either +/// from `package:flutter` or `dart:developer`. +class _TrackingClasses { + final Class hasCreationLocationClass; + final Class locationClass; + final String locationFieldName; + + _TrackingClasses({ + required this.hasCreationLocationClass, + required this.locationClass, + required this.locationFieldName, + }); +} diff --git a/pkg/kernel/test/generalized_tracking_test.dart b/pkg/kernel/test/generalized_tracking_test.dart new file mode 100644 index 00000000000..fa15633c3cc --- /dev/null +++ b/pkg/kernel/test/generalized_tracking_test.dart @@ -0,0 +1,131 @@ +// 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:expect/expect.dart'; +import 'package:kernel/ast.dart'; +import 'package:kernel/transformations/track_widget_constructor_locations.dart'; + +void main() { + final Uri developerUri = Uri.parse('dart:developer'); + final Library developerLib = new Library(developerUri, fileUri: developerUri); + + final Class hasCreationLocationClass = new Class( + name: '_HasCreationLocation', + isAbstract: true, + fileUri: developerUri, + ); + developerLib.addClass(hasCreationLocationClass); + + final Class creationLocationClass = new Class( + name: 'CreationLocation', + fileUri: developerUri, + ); + final Constructor creationLocationConstructor = new Constructor( + new FunctionNode( + null, + namedParameters: [ + new VariableDeclaration('file', type: const DynamicType()), + new VariableDeclaration('line', type: const DynamicType()), + new VariableDeclaration('column', type: const DynamicType()), + new VariableDeclaration('name', type: const DynamicType()), + ], + ), + name: new Name('_', developerLib), + fileUri: developerUri, + ); + creationLocationClass.addConstructor(creationLocationConstructor); + developerLib.addClass(creationLocationClass); + + final Uri coreUri = Uri.parse('dart:core'); + final Library coreLib = new Library(coreUri, fileUri: coreUri); + final Class pragmaClass = new Class(name: 'pragma', fileUri: coreUri); + coreLib.addClass(pragmaClass); + final Field pragmaNameField = new Field.immutable( + new Name('name'), + fileUri: coreUri, + ); + pragmaClass.addField(pragmaNameField); + + final Uri testUri = Uri.parse('package:test/test.dart'); + final Library testLib = new Library(testUri, fileUri: testUri); + + final Class myWidgetClass = new Class(name: 'MyWidget', fileUri: testUri); + myWidgetClass.addAnnotation( + new ConstantExpression( + new InstanceConstant( + pragmaClass.reference, + [], + { + pragmaNameField.fieldReference: new StringConstant( + 'track-creation-locations', + ), + }, + ), + ), + ); + myWidgetClass.addConstructor( + new Constructor( + new FunctionNode(new Block([])), + name: new Name(''), + fileUri: testUri, + ), + ); + testLib.addClass(myWidgetClass); + + const int fileOffset = 100; + final Procedure mainProcedure = new Procedure( + new Name('main'), + ProcedureKind.Method, + new FunctionNode( + new Block([ + new ExpressionStatement( + new ConstructorInvocation( + myWidgetClass.constructors.first, + new Arguments([]), + )..fileOffset = fileOffset, + ), + ]), + ), + isStatic: true, + fileUri: testUri, + ); + testLib.addProcedure(mainProcedure); + + final WidgetCreatorTracker tracker = new WidgetCreatorTracker(); + tracker.transform([testLib], [developerLib, testLib], null); + + // Verification + Expect.isTrue( + myWidgetClass.implementedTypes.any( + (s) => s.classNode == hasCreationLocationClass, + ), + ); + Expect.isTrue(myWidgetClass.fields.any((f) => f.name.text == '_location')); + + final Constructor constructor = myWidgetClass.constructors.first; + const String creationLocationPrefix = r'$creationLocation'; + Expect.isTrue( + constructor.function.namedParameters.any( + (p) => p.name!.startsWith(creationLocationPrefix), + ), + ); + + final Block body = mainProcedure.function.body as Block; + final ExpressionStatement stmt = body.statements.first as ExpressionStatement; + final ConstructorInvocation invocation = + stmt.expression as ConstructorInvocation; + Expect.isTrue( + invocation.arguments.named.any( + (n) => n.name.startsWith(creationLocationPrefix), + ), + ); + + final NamedExpression namedArg = invocation.arguments.named.firstWhere( + (n) => n.name.startsWith(creationLocationPrefix), + ); + Expect.isTrue(namedArg.value is ConstructorInvocation); + final ConstructorInvocation locInvocation = + namedArg.value as ConstructorInvocation; + Expect.equals(creationLocationClass, locInvocation.target.enclosingClass); +} diff --git a/pkg/native_compiler/lib/modular_aot_compiler.dart b/pkg/native_compiler/lib/modular_aot_compiler.dart index ddceb45e187..fc79cf72d05 100644 --- a/pkg/native_compiler/lib/modular_aot_compiler.dart +++ b/pkg/native_compiler/lib/modular_aot_compiler.dart @@ -114,9 +114,14 @@ final ArgParser _argParser = ArgParser(allowTrailingOptions: true) help: 'Print this help message.', ) ..addFlag( - 'track-widget-creation', + 'track-creation-locations', help: 'Run a kernel transformer to track creation locations for widgets.', defaultsTo: false, + aliases: [ + // TODO(http://dartbug.com/63225): Remove this once flutter is migrated + // to the new flag. + 'track-widget-creation', + ], ) ..addOption( 'invocation-modes', @@ -180,7 +185,7 @@ Future runCompilerWithCommandLineArguments(List arguments) async { final String? importDill = options['import-dill']; final String messageVerbosity = options['verbosity']; final String cfeInvocationModes = options['invocation-modes']; - final bool trackWidgetCreation = options['track-widget-creation']; + final bool trackCreationLocations = options['track-creation-locations']; final TargetCPU targetCPU = TargetCPU.fromName(options['target-arch']); final ImageFormat imageFormat = ImageFormat.fromName(options['image-format']); @@ -225,7 +230,7 @@ Future runCompilerWithCommandLineArguments(List arguments) async { ..verbosity = verbosity ..target = createFrontEndTarget( targetName, - trackWidgetCreation: trackWidgetCreation, + trackCreationLocations: trackCreationLocations, supportMirrors: false, isClosureContextLoweringEnabled: false, ); diff --git a/pkg/vm/lib/kernel_front_end.dart b/pkg/vm/lib/kernel_front_end.dart index 58a80882724..9ae6ed80af9 100644 --- a/pkg/vm/lib/kernel_front_end.dart +++ b/pkg/vm/lib/kernel_front_end.dart @@ -243,9 +243,14 @@ void declareCompilerOptions(ArgParser args) { help: 'Print this help message.', ); args.addFlag( - 'track-widget-creation', + 'track-creation-locations', help: 'Run a kernel transformer to track creation locations for widgets.', defaultsTo: false, + aliases: [ + // TODO(http://dartbug.com/63225): Remove this once flutter is migrated + // to the new flag. + 'track-widget-creation', + ], ); args.addMultiOption( 'delete-tostring-package-uri', @@ -435,7 +440,7 @@ Future runCompiler(ArgResults options, String usage) async { compilerOptions.target = createFrontEndTarget( targetName, - trackWidgetCreation: options['track-widget-creation'], + trackCreationLocations: options['track-creation-locations'], supportMirrors: supportMirrors ?? !(aot || minimalKernel), constKeepLocalsIndicator: !(aot || minimalKernel), ); @@ -1014,7 +1019,7 @@ bool parseCommandLineDefines( /// Create front-end target with given name. Target? createFrontEndTarget( String targetName, { - bool trackWidgetCreation = false, + bool trackCreationLocations = false, bool supportMirrors = true, bool includeUnsupportedPlatformLibraryStubs = false, bool? constKeepLocalsIndicator, @@ -1024,7 +1029,7 @@ Target? createFrontEndTarget( installAdditionalTargets(); final TargetFlags targetFlags = new TargetFlags( - trackWidgetCreation: trackWidgetCreation, + trackCreationLocations: trackCreationLocations, supportMirrors: supportMirrors, includeUnsupportedPlatformLibraryStubs: includeUnsupportedPlatformLibraryStubs, diff --git a/pkg/vm/lib/modular/target/flutter.dart b/pkg/vm/lib/modular/target/flutter.dart index 67aaeffde4f..7305ff7b170 100644 --- a/pkg/vm/lib/modular/target/flutter.dart +++ b/pkg/vm/lib/modular/target/flutter.dart @@ -2,18 +2,12 @@ // 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:kernel/ast.dart' show Component, Library; -import 'package:kernel/core_types.dart' show CoreTypes; -import 'package:kernel/target/changed_structure_notifier.dart'; import 'package:kernel/target/targets.dart'; -import 'package:kernel/transformations/track_widget_constructor_locations.dart'; import 'package:vm/modular/target/vm.dart' show VmTarget; class FlutterTarget extends VmTarget { FlutterTarget(TargetFlags flags) : super(flags); - late final WidgetCreatorTracker _widgetTracker = WidgetCreatorTracker(); - @override String get name => 'flutter'; @@ -51,26 +45,4 @@ class FlutterTarget extends VmTarget { @override DartLibrarySupport get dartLibrarySupport => const CustomizedDartLibrarySupport(unsupported: {'mirrors'}); - - @override - void performPreConstantEvaluationTransformations( - Component component, - CoreTypes coreTypes, - List libraries, - DiagnosticReporter diagnosticReporter, { - void Function(String msg)? logger, - ChangedStructureNotifier? changedStructureNotifier, - }) { - super.performPreConstantEvaluationTransformations( - component, - coreTypes, - libraries, - diagnosticReporter, - logger: logger, - changedStructureNotifier: changedStructureNotifier, - ); - if (flags.trackWidgetCreation) { - _widgetTracker.transform(component, libraries, changedStructureNotifier); - } - } } diff --git a/pkg/vm/lib/modular/target/vm.dart b/pkg/vm/lib/modular/target/vm.dart index c9015e9bcc4..7d91430ce60 100644 --- a/pkg/vm/lib/modular/target/vm.dart +++ b/pkg/vm/lib/modular/target/vm.dart @@ -9,6 +9,7 @@ import 'package:kernel/core_types.dart'; import 'package:kernel/reference_from_index.dart'; import 'package:kernel/target/changed_structure_notifier.dart'; import 'package:kernel/target/targets.dart'; +import 'package:kernel/transformations/track_widget_constructor_locations.dart'; import '../transformations/call_site_annotator.dart' as callSiteAnnotator; import '../transformations/deeply_immutable.dart' as deeply_immutable; @@ -135,6 +136,8 @@ class VmTarget extends Target { ..parent = host; } + late final WidgetCreatorTracker _widgetTracker = WidgetCreatorTracker(); + @override void performPreConstantEvaluationTransformations( Component component, @@ -153,6 +156,14 @@ class VmTarget extends Target { changedStructureNotifier: changedStructureNotifier, ); _patchVmConstants(coreTypes); + + if (flags.trackCreationLocations) { + _widgetTracker.transform( + libraries, + component.libraries, + changedStructureNotifier, + ); + } } @override diff --git a/runtime/docs/pragmas.md b/runtime/docs/pragmas.md index 6260b46f0e1..5b0e4d516c8 100644 --- a/runtime/docs/pragmas.md +++ b/runtime/docs/pragmas.md @@ -23,6 +23,7 @@ These pragmas are part of the VM's API and are safe for use in external code. | `vm:align-loops` | Tells compiler to align all loop headers inside the function to an architecture specific boundary: currently 32 bytes on X64 and ARM64 (except Apple Silicon, which explicitly discourages aligning branch targets) | | `vm:no-sanitize-thread` | Disable ThreadSanitizer instrumentation | | `external-effect` | Declares a static method which will be treated as live code when performing any analysis of the program. The call itself (and its arguments) are then dropped from the running program. +| `track-creation-locations` | Marks a class, instances of which should have their creation locations tracked. Tracked creation locations are stored in a private field of the class and can be accessed through the `CreationLocation.of(object)` method in `dart:developer`. | ## Unsafe pragmas for general use diff --git a/sdk/lib/developer/creation_tracking.dart b/sdk/lib/developer/creation_tracking.dart new file mode 100644 index 00000000000..93ad1af0c64 --- /dev/null +++ b/sdk/lib/developer/creation_tracking.dart @@ -0,0 +1,95 @@ +// 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. + +part of dart.developer; + +/// Holds the source code location where an object was created, when its class +/// was annotated with `@pragma('track-creation-locations')`. +/// +/// When a class definition is annotated with `@pragma('track-creation-locations')`, +/// the Dart compiler injects the call-site location into any invocation of that +/// class's or any subclass's constructors and stores it in the created object. +/// +/// The location of such an object can be read by calling [CreationLocation.of] +/// with the object as the argument. +/// +/// ## Example +/// +/// ```dart +/// import 'dart:developer'; +/// +/// // Marks this and any subclass to have their constructor call-sites tracked. +/// @pragma('track-creation-locations') +/// class TargetClass { +/// TargetClass(); +/// } +/// +/// void main() { +/// // The source-code location of this constructor call is injected into the object. +/// final instance = TargetClass(); +/// +/// final location = CreationLocation.of(instance); +/// print(location); // Will print the current file path, line 11, column 20 +/// } +/// ``` +/// +/// ## Limitations +/// +/// The compiler transformation relies on injecting a named parameter into the +/// target class's constructors. Since Dart semantics do not permit a function +/// to have both optional positional parameters and named parameters simultaneously, +/// this transformation **will silently skip** any constructor that declares optional +/// positional parameters. Calling [CreationLocation.of] on an object whose +/// constructor was skipped, will return `null`. +final class CreationLocation { + /// Returns the creation location of [object]. + /// + /// The provided object must be an instance of a class annotated with + /// `@pragma('track-creation-locations')`. + static CreationLocation? of(Object? object) { + if (object is _HasCreationLocation) { + return object._location; + } + return null; + } + + const CreationLocation._({ + required this.file, + required this.line, + required this.column, + this.name, + }); + + /// File path of the location. + final String file; + + /// 1-based line number. + final int line; + + /// 1-based column number. + final int column; + + /// Optional name of the parameter or function at this location. + final String? name; + + /// JSON representation of this location. + Map toJsonMap() { + return { + 'file': file, + 'line': line, + 'column': column, + 'name': name, + }; + } + + @override + String toString() => [?name, file, '$line', '$column'].join(':'); +} + +/// Interface for classes that track the source code location their +/// constructor was called from. +abstract interface class _HasCreationLocation { + /// The location where the constructor was called. + CreationLocation? _location; +} diff --git a/sdk/lib/developer/developer.dart b/sdk/lib/developer/developer.dart index c674480ea6d..d7fbc46bd7c 100644 --- a/sdk/lib/developer/developer.dart +++ b/sdk/lib/developer/developer.dart @@ -79,6 +79,7 @@ import 'dart:collection'; import 'dart:convert'; import 'dart:isolate' show Isolate, RawReceivePort, SendPort; +part 'creation_tracking.dart'; part 'extension.dart'; part 'http_profiling.dart'; part 'profiler.dart'; diff --git a/sdk/lib/developer/developer_sources.gni b/sdk/lib/developer/developer_sources.gni index df0dbc2e4db..648cffcdff5 100644 --- a/sdk/lib/developer/developer_sources.gni +++ b/sdk/lib/developer/developer_sources.gni @@ -6,6 +6,7 @@ developer_sdk_sources = [ "developer.dart", # The above file needs to be first if additional parts are added to the lib. + "creation_tracking.dart", "extension.dart", "profiler.dart", "service.dart",