From dd4f983cee1b444299ac04eb41e734f99db4a7d8 Mon Sep 17 00:00:00 2001 From: Kilian Schulte Date: Wed, 10 Jun 2026 04:15:37 -0700 Subject: [PATCH] fix location tracking transformer for use in flutter Closes https://github.com/dart-lang/sdk/pull/63551 GitOrigin-RevId: a14cbbd88c605d193e2a1b744727765c45d38278 Change-Id: Ied6c2fc4ee3b5343c6ab8e38080542d667175f4d Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/510160 Reviewed-by: Daco Harkes Commit-Queue: Daco Harkes Reviewed-by: Jens Johansen --- ...ation_tracking_flutter_widget_factory.yaml | 117 ++++++++++ ...flutter_widget_factory.yaml.world.1.expect | 194 +++++++++++++++++ ...flutter_widget_factory.yaml.world.2.expect | 194 +++++++++++++++++ .../track_widget_constructor_locations.dart | 206 ++++++++++-------- .../test/generalized_tracking_test.dart | 90 ++++++++ 5 files changed, 714 insertions(+), 87 deletions(-) create mode 100644 pkg/front_end/testcases/incremental/general_location_tracking_flutter_widget_factory.yaml create mode 100644 pkg/front_end/testcases/incremental/general_location_tracking_flutter_widget_factory.yaml.world.1.expect create mode 100644 pkg/front_end/testcases/incremental/general_location_tracking_flutter_widget_factory.yaml.world.2.expect diff --git a/pkg/front_end/testcases/incremental/general_location_tracking_flutter_widget_factory.yaml b/pkg/front_end/testcases/incremental/general_location_tracking_flutter_widget_factory.yaml new file mode 100644 index 00000000000..65bd14f2bef --- /dev/null +++ b/pkg/front_end/testcases/incremental/general_location_tracking_flutter_widget_factory.yaml @@ -0,0 +1,117 @@ +# 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(); + var newFooFunction = Foo.new; + var newFooFunctionCall = newFooFunction(); + Foo extensionFoo = null.foo(); + var extensionFooFunction = null.foo; + var extensionFooFunctionCall = extensionFooFunction(); + Foo extensionBar = null.bar(); + Foo extensionBaz = null.baz(); + Foo extensionBoz = null.boz(); + Foo extensionConstFoo = null.constFoo(); + Foo extensionGetterFoo = null.getterFoo; + var extensionSetterFoo = () => null.setterFoo = null; + Foo extensionOperatorFoo = -null; + Foo extensionStaticFoo = FooExtension.staticFoo(); + foo.dart: | + import 'package:flutter/src/widgets/framework.dart'; + + const alias = widgetFactory; + const nullableAlias = false ? widgetFactory : null; + const nonNullableAlias = false ? widgetFactory : alias; + + class Foo extends Widget { + factory Foo() => const Foo._(); + + const Foo._(); + } + + extension FooExtension on Object? { + @widgetFactory + Foo foo() => Foo._(); + + @alias + Foo bar() => Foo._(); + + @nullableAlias + Foo baz() => Foo._(); + + @nonNullableAlias + Foo boz() => Foo._(); + + @widgetFactory + Foo constFoo() => const Foo._(); + + @widgetFactory + Foo get getterFoo => new Foo._(); + + @widgetFactory + void set setterFoo(_) => new Foo._(); + + @widgetFactory + Foo operator -() => new Foo._(); + + @widgetFactory + static Foo staticFoo() => new Foo._(); + } + flutter/lib/src/widgets/framework.dart: | + export 'widget_inspector.dart' show widgetFactory; + + abstract class Bar { + const Bar(); + } + + @pragma('track-creation-locations') + abstract class Widget extends Bar { + const Widget(); + } + flutter/lib/src/widgets/widget_inspector.dart: | + const widgetFactory = pragma('track-creation-locations'); + .dart_tool/package_config.json: | + { + "configVersion": 2, + "packages": [ + { + "name": "flutter", + "rootUri": "../flutter", + "packageUri": "lib/" + } + ] + } + expectedLibraryCount: 4 + + - entry: main.dart + worldType: updated + invalidate: + - main.dart + expectInitializeFromDill: false + sources: + main.dart: | + import 'foo.dart'; + Foo extensionConstFoo = null.constFoo(); + Foo newFoo = new Foo(); + var newFooFunction = Foo.new; + var newFooFunctionCall = newFooFunction(); + Foo extensionFoo = null.foo(); + var extensionFooFunction = null.foo; + var extensionFooFunctionCall = extensionFooFunction(); + Foo extensionBar = null.bar(); + Foo extensionBaz = null.baz(); + Foo extensionBoz = null.boz(); + Foo extensionGetterFoo = null.getterFoo; + var extensionSetterFoo = () => null.setterFoo = null; + Foo extensionOperatorFoo = -null; + Foo extensionStaticFoo = FooExtension.staticFoo(); + expectedLibraryCount: 4 + advancedInvalidation: bodiesOnly diff --git a/pkg/front_end/testcases/incremental/general_location_tracking_flutter_widget_factory.yaml.world.1.expect b/pkg/front_end/testcases/incremental/general_location_tracking_flutter_widget_factory.yaml.world.1.expect new file mode 100644 index 00000000000..1b3ab1bf8a5 --- /dev/null +++ b/pkg/front_end/testcases/incremental/general_location_tracking_flutter_widget_factory.yaml.world.1.expect @@ -0,0 +1,194 @@ +main = ; +library from "package:flutter/src/widgets/framework.dart" as fra { +additionalExports = (wid::widgetFactory) + + export "package:flutter/src/widgets/widget_inspector.dart" show widgetFactory; + + 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 "package:flutter/src/widgets/widget_inspector.dart" as wid { + + static const field dart.core::pragma widgetFactory = #C3; +} +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); + } + extension FooExtension on dart.core::Object? { + method foo = foo::FooExtension|foo; + method tearoff foo = foo::FooExtension|get#foo; + method bar = foo::FooExtension|bar; + method tearoff bar = foo::FooExtension|get#bar; + method baz = foo::FooExtension|baz; + method tearoff baz = foo::FooExtension|get#baz; + method boz = foo::FooExtension|boz; + method tearoff boz = foo::FooExtension|get#boz; + method constFoo = foo::FooExtension|constFoo; + method tearoff constFoo = foo::FooExtension|get#constFoo; + get getterFoo = foo::FooExtension|get#getterFoo; + set setterFoo = foo::FooExtension|set#setterFoo; + operator unary- = foo::FooExtension|unary-; + static method staticFoo = foo::FooExtension|staticFoo; + } + static const field dart.core::pragma alias = #C3; + static const field dart.core::pragma? nullableAlias = #C2; + static const field dart.core::pragma nonNullableAlias = #C3; + @#C3 + static extension-member method FooExtension|foo(lowered final dart.core::Object? #this, {dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C2}) → foo::Foo + return new foo::Foo::_($creationLocationd_0dea112b090073317d4: $creationLocationd_0dea112b090073317d4); + static extension-member method FooExtension|get#foo(lowered final dart.core::Object? #this) → () → foo::Foo + return () → foo::Foo => foo::FooExtension|foo(#this, $creationLocationd_0dea112b090073317d4: #C18); + @#C3 + static extension-member method FooExtension|bar(lowered final dart.core::Object? #this, {dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C2}) → foo::Foo + return new foo::Foo::_($creationLocationd_0dea112b090073317d4: $creationLocationd_0dea112b090073317d4); + static extension-member method FooExtension|get#bar(lowered final dart.core::Object? #this) → () → foo::Foo + return () → foo::Foo => foo::FooExtension|bar(#this, $creationLocationd_0dea112b090073317d4: #C21); + @#C2 + static extension-member method FooExtension|baz(lowered final dart.core::Object? #this) → foo::Foo + return new foo::Foo::_($creationLocationd_0dea112b090073317d4: #C24); + static extension-member method FooExtension|get#baz(lowered final dart.core::Object? #this) → () → foo::Foo + return () → foo::Foo => foo::FooExtension|baz(#this); + @#C3 + static extension-member method FooExtension|boz(lowered final dart.core::Object? #this) → foo::Foo + return new foo::Foo::_($creationLocationd_0dea112b090073317d4: #C26); + static extension-member method FooExtension|get#boz(lowered final dart.core::Object? #this) → () → foo::Foo + return () → foo::Foo => foo::FooExtension|boz(#this); + @#C3 + static extension-member method FooExtension|constFoo(lowered final dart.core::Object? #this, {dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C2}) → foo::Foo + return #C29; + static extension-member method FooExtension|get#constFoo(lowered final dart.core::Object? #this) → () → foo::Foo + return () → foo::Foo => foo::FooExtension|constFoo(#this, $creationLocationd_0dea112b090073317d4: #C31); + @#C3 + static extension-member method FooExtension|get#getterFoo(lowered final dart.core::Object? #this, {dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C2}) → foo::Foo + return new foo::Foo::_($creationLocationd_0dea112b090073317d4: $creationLocationd_0dea112b090073317d4); + @#C3 + static extension-member method FooExtension|set#setterFoo(lowered final dart.core::Object? #this, wildcard dynamic _#wc0#formal, {dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C2}) → void + return new foo::Foo::_($creationLocationd_0dea112b090073317d4: $creationLocationd_0dea112b090073317d4); + @#C3 + static extension-member method FooExtension|unary-(lowered final dart.core::Object? #this, {dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C2}) → foo::Foo + return new foo::Foo::_($creationLocationd_0dea112b090073317d4: $creationLocationd_0dea112b090073317d4); + @#C3 + static extension-member method FooExtension|staticFoo() → foo::Foo + return new foo::Foo::_($creationLocationd_0dea112b090073317d4: #C34); +} +library from "org-dartlang-test:///main.dart" as main { + + import "org-dartlang-test:///foo.dart"; + + static field foo::Foo newFoo = foo::Foo::•($creationLocationd_0dea112b090073317d4: #C38); + static field () → foo::Foo newFooFunction = #C39; + static field foo::Foo newFooFunctionCall = main::newFooFunction(){() → foo::Foo}; + static field foo::Foo extensionFoo = foo::FooExtension|foo(null, $creationLocationd_0dea112b090073317d4: #C42); + static field () → foo::Foo extensionFooFunction = foo::FooExtension|get#foo(null); + static field foo::Foo extensionFooFunctionCall = main::extensionFooFunction(){() → foo::Foo}; + static field foo::Foo extensionBar = foo::FooExtension|bar(null, $creationLocationd_0dea112b090073317d4: #C43); + static field foo::Foo extensionBaz = foo::FooExtension|baz(null); + static field foo::Foo extensionBoz = foo::FooExtension|boz(null); + static field foo::Foo extensionConstFoo = foo::FooExtension|constFoo(null, $creationLocationd_0dea112b090073317d4: #C45); + static field foo::Foo extensionGetterFoo = foo::FooExtension|get#getterFoo(null, $creationLocationd_0dea112b090073317d4: #C49); + static field () → Null extensionSetterFoo = () → Null => let final has-declared-initializer Null #t1 = null in let final void #t2 = foo::FooExtension|set#setterFoo(null, #t1, $creationLocationd_0dea112b090073317d4: #C53) in #t1; + static field foo::Foo extensionOperatorFoo = foo::FooExtension|unary-(null, $creationLocationd_0dea112b090073317d4: #C56); + static field foo::Foo extensionStaticFoo = foo::FooExtension|staticFoo(); +} +constants { + #C1 = "track-creation-locations" + #C2 = null + #C3 = dart.core::pragma {name:#C1, options:#C2} + #C4 = "org-dartlang-test:///foo.dart" + #C5 = 8.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 = 10.0 + #C13 = 9.0 + #C14 = dart.developer::CreationLocation {file:#C4, line:#C12, column:#C13, name:#C7} + #C15 = 14.0 + #C16 = 3.0 + #C17 = "FooExtension|foo" + #C18 = dart.developer::CreationLocation {file:#C4, line:#C15, column:#C16, name:#C17} + #C19 = 17.0 + #C20 = "FooExtension|bar" + #C21 = dart.developer::CreationLocation {file:#C4, line:#C19, column:#C16, name:#C20} + #C22 = 21.0 + #C23 = 20.0 + #C24 = dart.developer::CreationLocation {file:#C4, line:#C22, column:#C23, name:#C7} + #C25 = 24.0 + #C26 = dart.developer::CreationLocation {file:#C4, line:#C25, column:#C23, name:#C7} + #C27 = 27.0 + #C28 = dart.developer::CreationLocation {file:#C4, line:#C27, column:#C27, name:#C7} + #C29 = foo::Foo {_location:#C28} + #C30 = "FooExtension|constFoo" + #C31 = dart.developer::CreationLocation {file:#C4, line:#C6, column:#C16, name:#C30} + #C32 = 39.0 + #C33 = 33.0 + #C34 = dart.developer::CreationLocation {file:#C4, line:#C32, column:#C33, name:#C7} + #C35 = "org-dartlang-test:///main.dart" + #C36 = 2.0 + #C37 = 18.0 + #C38 = dart.developer::CreationLocation {file:#C35, line:#C36, column:#C37, name:#C7} + #C39 = static-tearoff foo::Foo::_#new#tearOff + #C40 = 5.0 + #C41 = 25.0 + #C42 = dart.developer::CreationLocation {file:#C35, line:#C40, column:#C41, name:#C17} + #C43 = dart.developer::CreationLocation {file:#C35, line:#C5, column:#C41, name:#C20} + #C44 = 30.0 + #C45 = dart.developer::CreationLocation {file:#C35, line:#C10, column:#C44, name:#C30} + #C46 = 12.0 + #C47 = 31.0 + #C48 = "FooExtension|get#getterFoo" + #C49 = dart.developer::CreationLocation {file:#C35, line:#C46, column:#C47, name:#C48} + #C50 = 13.0 + #C51 = 37.0 + #C52 = "FooExtension|set#setterFoo" + #C53 = dart.developer::CreationLocation {file:#C35, line:#C50, column:#C51, name:#C52} + #C54 = 28.0 + #C55 = "FooExtension|unary-" + #C56 = dart.developer::CreationLocation {file:#C35, line:#C15, column:#C54, name:#C55} +} + + +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:///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:10:9) +- Widget. (from org-dartlang-test:///flutter/lib/src/widgets/framework.dart:9:9) +- Bar. (from org-dartlang-test:///flutter/lib/src/widgets/framework.dart:4: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:///flutter/lib/src/widgets/widget_inspector.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_tracking_flutter_widget_factory.yaml.world.2.expect b/pkg/front_end/testcases/incremental/general_location_tracking_flutter_widget_factory.yaml.world.2.expect new file mode 100644 index 00000000000..8bfdcf08cd8 --- /dev/null +++ b/pkg/front_end/testcases/incremental/general_location_tracking_flutter_widget_factory.yaml.world.2.expect @@ -0,0 +1,194 @@ +main = ; +library from "package:flutter/src/widgets/framework.dart" as fra { +additionalExports = (wid::widgetFactory) + + export "package:flutter/src/widgets/widget_inspector.dart" show widgetFactory; + + 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 "package:flutter/src/widgets/widget_inspector.dart" as wid { + + static const field dart.core::pragma widgetFactory = #C3; +} +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); + } + extension FooExtension on dart.core::Object? { + method foo = foo::FooExtension|foo; + method tearoff foo = foo::FooExtension|get#foo; + method bar = foo::FooExtension|bar; + method tearoff bar = foo::FooExtension|get#bar; + method baz = foo::FooExtension|baz; + method tearoff baz = foo::FooExtension|get#baz; + method boz = foo::FooExtension|boz; + method tearoff boz = foo::FooExtension|get#boz; + method constFoo = foo::FooExtension|constFoo; + method tearoff constFoo = foo::FooExtension|get#constFoo; + get getterFoo = foo::FooExtension|get#getterFoo; + set setterFoo = foo::FooExtension|set#setterFoo; + operator unary- = foo::FooExtension|unary-; + static method staticFoo = foo::FooExtension|staticFoo; + } + static const field dart.core::pragma alias = #C3; + static const field dart.core::pragma? nullableAlias = #C2; + static const field dart.core::pragma nonNullableAlias = #C3; + @#C3 + static extension-member method FooExtension|foo(lowered final dart.core::Object? #this, {dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C2}) → foo::Foo + return new foo::Foo::_($creationLocationd_0dea112b090073317d4: $creationLocationd_0dea112b090073317d4); + static extension-member method FooExtension|get#foo(lowered final dart.core::Object? #this) → () → foo::Foo + return () → foo::Foo => foo::FooExtension|foo(#this, $creationLocationd_0dea112b090073317d4: #C18); + @#C3 + static extension-member method FooExtension|bar(lowered final dart.core::Object? #this, {dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C2}) → foo::Foo + return new foo::Foo::_($creationLocationd_0dea112b090073317d4: $creationLocationd_0dea112b090073317d4); + static extension-member method FooExtension|get#bar(lowered final dart.core::Object? #this) → () → foo::Foo + return () → foo::Foo => foo::FooExtension|bar(#this, $creationLocationd_0dea112b090073317d4: #C21); + @#C2 + static extension-member method FooExtension|baz(lowered final dart.core::Object? #this) → foo::Foo + return new foo::Foo::_($creationLocationd_0dea112b090073317d4: #C24); + static extension-member method FooExtension|get#baz(lowered final dart.core::Object? #this) → () → foo::Foo + return () → foo::Foo => foo::FooExtension|baz(#this); + @#C3 + static extension-member method FooExtension|boz(lowered final dart.core::Object? #this) → foo::Foo + return new foo::Foo::_($creationLocationd_0dea112b090073317d4: #C26); + static extension-member method FooExtension|get#boz(lowered final dart.core::Object? #this) → () → foo::Foo + return () → foo::Foo => foo::FooExtension|boz(#this); + @#C3 + static extension-member method FooExtension|constFoo(lowered final dart.core::Object? #this, {dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C2}) → foo::Foo + return #C29; + static extension-member method FooExtension|get#constFoo(lowered final dart.core::Object? #this) → () → foo::Foo + return () → foo::Foo => foo::FooExtension|constFoo(#this, $creationLocationd_0dea112b090073317d4: #C31); + @#C3 + static extension-member method FooExtension|get#getterFoo(lowered final dart.core::Object? #this, {dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C2}) → foo::Foo + return new foo::Foo::_($creationLocationd_0dea112b090073317d4: $creationLocationd_0dea112b090073317d4); + @#C3 + static extension-member method FooExtension|set#setterFoo(lowered final dart.core::Object? #this, wildcard dynamic _#wc0#formal, {dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C2}) → void + return new foo::Foo::_($creationLocationd_0dea112b090073317d4: $creationLocationd_0dea112b090073317d4); + @#C3 + static extension-member method FooExtension|unary-(lowered final dart.core::Object? #this, {dart.developer::CreationLocation? $creationLocationd_0dea112b090073317d4 = #C2}) → foo::Foo + return new foo::Foo::_($creationLocationd_0dea112b090073317d4: $creationLocationd_0dea112b090073317d4); + @#C3 + static extension-member method FooExtension|staticFoo() → foo::Foo + return new foo::Foo::_($creationLocationd_0dea112b090073317d4: #C34); +} +library from "org-dartlang-test:///main.dart" as main { + + import "org-dartlang-test:///foo.dart"; + + static field foo::Foo extensionConstFoo = foo::FooExtension|constFoo(null, $creationLocationd_0dea112b090073317d4: #C38); + static field foo::Foo newFoo = foo::Foo::•($creationLocationd_0dea112b090073317d4: #C40); + static field () → foo::Foo newFooFunction = #C41; + static field foo::Foo newFooFunctionCall = main::newFooFunction(){() → foo::Foo}; + static field foo::Foo extensionFoo = foo::FooExtension|foo(null, $creationLocationd_0dea112b090073317d4: #C44); + static field ({$creationLocationd_0dea112b090073317d4: dart.developer::CreationLocation?}) → foo::Foo extensionFooFunction = foo::FooExtension|get#foo(null); + static field foo::Foo extensionFooFunctionCall = main::extensionFooFunction(){({$creationLocationd_0dea112b090073317d4: dart.developer::CreationLocation?}) → foo::Foo}; + static field foo::Foo extensionBar = foo::FooExtension|bar(null, $creationLocationd_0dea112b090073317d4: #C45); + static field foo::Foo extensionBaz = foo::FooExtension|baz(null); + static field foo::Foo extensionBoz = foo::FooExtension|boz(null); + static field foo::Foo extensionGetterFoo = foo::FooExtension|get#getterFoo(null, $creationLocationd_0dea112b090073317d4: #C49); + static field () → Null extensionSetterFoo = () → Null => let final has-declared-initializer Null #t1 = null in let final void #t2 = foo::FooExtension|set#setterFoo(null, #t1, $creationLocationd_0dea112b090073317d4: #C53) in #t1; + static field foo::Foo extensionOperatorFoo = foo::FooExtension|unary-(null, $creationLocationd_0dea112b090073317d4: #C56); + static field foo::Foo extensionStaticFoo = foo::FooExtension|staticFoo(); +} +constants { + #C1 = "track-creation-locations" + #C2 = null + #C3 = dart.core::pragma {name:#C1, options:#C2} + #C4 = "org-dartlang-test:///foo.dart" + #C5 = 8.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 = 10.0 + #C13 = 9.0 + #C14 = dart.developer::CreationLocation {file:#C4, line:#C12, column:#C13, name:#C7} + #C15 = 14.0 + #C16 = 3.0 + #C17 = "FooExtension|foo" + #C18 = dart.developer::CreationLocation {file:#C4, line:#C15, column:#C16, name:#C17} + #C19 = 17.0 + #C20 = "FooExtension|bar" + #C21 = dart.developer::CreationLocation {file:#C4, line:#C19, column:#C16, name:#C20} + #C22 = 21.0 + #C23 = 20.0 + #C24 = dart.developer::CreationLocation {file:#C4, line:#C22, column:#C23, name:#C7} + #C25 = 24.0 + #C26 = dart.developer::CreationLocation {file:#C4, line:#C25, column:#C23, name:#C7} + #C27 = 27.0 + #C28 = dart.developer::CreationLocation {file:#C4, line:#C27, column:#C27, name:#C7} + #C29 = foo::Foo {_location:#C28} + #C30 = "FooExtension|constFoo" + #C31 = dart.developer::CreationLocation {file:#C4, line:#C6, column:#C16, name:#C30} + #C32 = 39.0 + #C33 = 33.0 + #C34 = dart.developer::CreationLocation {file:#C4, line:#C32, column:#C33, name:#C7} + #C35 = "org-dartlang-test:///main.dart" + #C36 = 2.0 + #C37 = 30.0 + #C38 = dart.developer::CreationLocation {file:#C35, line:#C36, column:#C37, name:#C30} + #C39 = 18.0 + #C40 = dart.developer::CreationLocation {file:#C35, line:#C16, column:#C39, name:#C7} + #C41 = static-tearoff foo::Foo::_#new#tearOff + #C42 = 6.0 + #C43 = 25.0 + #C44 = dart.developer::CreationLocation {file:#C35, line:#C42, column:#C43, name:#C17} + #C45 = dart.developer::CreationLocation {file:#C35, line:#C13, column:#C43, name:#C20} + #C46 = 12.0 + #C47 = 31.0 + #C48 = "FooExtension|get#getterFoo" + #C49 = dart.developer::CreationLocation {file:#C35, line:#C46, column:#C47, name:#C48} + #C50 = 13.0 + #C51 = 37.0 + #C52 = "FooExtension|set#setterFoo" + #C53 = dart.developer::CreationLocation {file:#C35, line:#C50, column:#C51, name:#C52} + #C54 = 28.0 + #C55 = "FooExtension|unary-" + #C56 = dart.developer::CreationLocation {file:#C35, line:#C15, column:#C54, name:#C55} +} + + +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:10:9) +- Widget. (from org-dartlang-test:///flutter/lib/src/widgets/framework.dart:9:9) +- Bar. (from org-dartlang-test:///flutter/lib/src/widgets/framework.dart:4: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:///flutter/lib/src/widgets/widget_inspector.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/kernel/lib/transformations/track_widget_constructor_locations.dart b/pkg/kernel/lib/transformations/track_widget_constructor_locations.dart index 375c3b54b18..ff646d5ba6c 100644 --- a/pkg/kernel/lib/transformations/track_widget_constructor_locations.dart +++ b/pkg/kernel/lib/transformations/track_widget_constructor_locations.dart @@ -218,13 +218,19 @@ class _WidgetCallSiteTransformer extends Transformer { ); return node; } - if (_isWidgetFactory(target) && _tracker._locationClass != null) { - _addLocationArgument( - node, - target.function, - locationClass: _tracker._locationClass!, + if (_isWidgetFactory(target)) { + final Variable parameter = target.function.namedParameters.firstWhere( + (p) => p.name == _creationLocationParameterName, ); - return node; + final DartType type = parameter.type; + if (type is InterfaceType) { + _addLocationArgument( + node, + target.function, + locationClass: type.classNode, + ); + return node; + } } return node; } @@ -383,7 +389,6 @@ class WidgetCreatorTracker { void _resolveWellKnownClasses(Iterable libraries) { // If the Widget or Debug location classes have been updated we need to get // the latest version - for (Library library in libraries) { final Uri importUri = library.importUri; @@ -589,14 +594,12 @@ class WidgetCreatorTracker { changedStructureNotifier, ); } - if (_widgetFactoryClass != null) { - for (Extension extension in library.extensions) { - _transformWidgetFactories( - librariesToTransform, - transformedExtensions, - extension, - ); - } + for (Extension extension in library.extensions) { + _transformExtension( + librariesToTransform, + transformedExtensions, + extension, + ); } } @@ -612,17 +615,6 @@ class WidgetCreatorTracker { } _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, - ); - } - } - // New Case: Check for 'pragma('track-creation-locations')' annotation. if (_hasTrackCreationLocationsPragmaAnnotation(clazz)) { if (_developerHasCreationLocationClass != null && @@ -635,6 +627,17 @@ class WidgetCreatorTracker { } } + // Legacy Case: Check for widget class. + if (_isSubclassOfWidget(clazz)) { + if (_hasCreationLocationClass != null && _locationClass != null) { + return new _TrackingClasses( + hasCreationLocationClass: _hasCreationLocationClass!, + locationClass: _locationClass!, + locationFieldName: _locationFieldName, + ); + } + } + return null; } @@ -645,50 +648,81 @@ class WidgetCreatorTracker { 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; + return _isSubclassWhere( + clazz, + (Class c) => _hasPragmaAnnotation(c, 'track-creation-locations'), + ); + } - if (expression is ConstructorInvocation) { - final Class enclosingClass = expression.target.enclosingClass; + bool _hasPragmaAnnotation(Annotatable node, String pragmaName) { + for (Expression annotation in node.annotations) { + if (_getPragmaName(annotation) == pragmaName) { + return true; + } + } + return false; + } - 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; - } - } - } + String? _getPragmaName(Expression expression) { + // Direct invocation, e.g. `@pragma('...')`. + // Applies before constant evaluation (newly compiled modules). + if (expression + case RedirectingFactoryInvocation( + expression: final ConstructorInvocation invocation, + ) || + final ConstructorInvocation invocation) { + final Class enclosingClass = invocation.target.enclosingClass; + if (enclosingClass.name == 'pragma' && + enclosingClass.enclosingLibrary.importUri.toString() == 'dart:core') { + if (invocation.arguments.positional.isNotEmpty) { + final Expression firstArg = invocation.arguments.positional.first; + if (firstArg is StringLiteral) { + return firstArg.value; } } - // 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; - } + // Aliased constant, e.g. `@widgetFactory` + // with `const widgetFactory = pragma('...');`. + // Applies before constant evaluation (newly compiled modules). + if (expression is StaticGet) { + final Member target = expression.target; + if (target is Field && target.isConst) { + if (target.initializer != null) { + return _getPragmaName(target.initializer!); + } + } + } + + // Evaluated constant pragma. + // Applies after constant evaluation (incremental or modular compilation). + if (expression is ConstantExpression) { + final Constant constant = expression.constant; + if (constant is InstanceConstant) { + 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 is StringConstant) { + return value.value; } } } } - return false; - }); + } + return null; + } + + Class? _getFactoryLocationClass(Procedure method) { + if (_hasPragmaAnnotation(method, 'track-creation-locations')) { + return _developerCreationLocationClass; + } + if (_hasWidgetFactoryAnnotation(method)) { + return _locationClass; + } + return null; } bool _isSubclassWhere(Class a, bool Function(Class b) predicate) { @@ -845,14 +879,13 @@ class WidgetCreatorTracker { clazz.constructors.forEach(handleConstructor); } - void _transformWidgetFactories( + void _transformExtension( Set librariesToBeTransformed, Set transformedExtensions, Extension extension, ) { // TODO(johnniwinther): We should have a lint for unsupported use of // `@widgetFactory`. - assert(_widgetFactoryClass != null); if (!librariesToBeTransformed.contains(extension.enclosingLibrary) || !transformedExtensions.add(extension)) { @@ -866,35 +899,34 @@ class WidgetCreatorTracker { continue; } final Procedure? method = member.memberReference?.asProcedure; - if (method != null && _hasWidgetFactoryAnnotation(method)) { - _maybeAddNamedParameter( - method.function, - new Variable( - _creationLocationParameterName, - type: new InterfaceType( - _locationClass!, - extension.enclosingLibrary.nullable, - ), - initializer: new NullLiteral(), - ), - ); + if (method != null) { + _transformExtensionProcedure(method, extension); } final Procedure? tearOff = member.tearOffReference?.asProcedure; - if (tearOff != null && _hasWidgetFactoryAnnotation(tearOff)) { - _maybeAddNamedParameter( - tearOff.function, - new Variable( - _creationLocationParameterName, - type: new InterfaceType( - _locationClass!, - extension.enclosingLibrary.nullable, - ), - initializer: new NullLiteral(), - ), - ); + if (tearOff != null) { + _transformExtensionProcedure(tearOff, extension); } } } + + void _transformExtensionProcedure(Procedure method, Extension extension) { + final Class? locationClass = _getFactoryLocationClass(method); + if (locationClass == null) { + return; + } + + _maybeAddNamedParameter( + method.function, + new Variable( + _creationLocationParameterName, + type: new InterfaceType( + locationClass, + extension.enclosingLibrary.nullable, + ), + initializer: new NullLiteral(), + ), + ); + } } /// Holds the set of creation location classes, either diff --git a/pkg/kernel/test/generalized_tracking_test.dart b/pkg/kernel/test/generalized_tracking_test.dart index eee2e8bad02..37cd4d83cfa 100644 --- a/pkg/kernel/test/generalized_tracking_test.dart +++ b/pkg/kernel/test/generalized_tracking_test.dart @@ -92,6 +92,74 @@ void main() { ); testLib.addProcedure(mainProcedure); + final Extension extension = + new Extension(name: 'MyExtension', fileUri: testUri) + ..fileOffset = fileOffset + ..onType = const DynamicType(); + testLib.addExtension(extension); + + final Procedure factoryMethod = + new Procedure( + new Name('myFactory'), + ProcedureKind.Method, + new FunctionNode( + new Block([ + new ReturnStatement( + new ConstructorInvocation( + myWidgetClass.constructors.first, + new Arguments([]), + )..fileOffset = fileOffset, + ), + ]), + returnType: new InterfaceType( + myWidgetClass, + Nullability.nonNullable, + ), + ), + isStatic: true, + fileUri: testUri, + ) + ..fileOffset = fileOffset + ..isExtensionMember = true; + factoryMethod.addAnnotation( + new ConstantExpression( + new InstanceConstant( + pragmaClass.reference, + [], + { + pragmaNameField.fieldReference: new StringConstant( + 'track-creation-locations', + ), + }, + ), + ), + ); + testLib.addProcedure(factoryMethod); + + extension.memberDescriptors.add( + new ExtensionMemberDescriptor( + name: new Name('myFactory'), + kind: ExtensionMemberKind.Method, + memberReference: factoryMethod.reference, + tearOffReference: null, + ), + ); + + final Block newBody = new Block([ + new ExpressionStatement( + new ConstructorInvocation( + myWidgetClass.constructors.first, + new Arguments([]), + )..fileOffset = fileOffset, + ), + new ExpressionStatement( + new StaticInvocation(factoryMethod, new Arguments([])) + ..fileOffset = fileOffset, + ), + ]); + newBody.parent = mainProcedure.function; + mainProcedure.function.body = newBody; + final WidgetCreatorTracker tracker = new WidgetCreatorTracker(); tracker.transform([testLib], [developerLib, testLib], null); @@ -124,8 +192,30 @@ void main() { 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); + + Expect.isTrue( + factoryMethod.function.namedParameters.any( + (p) => p.name!.startsWith(creationLocationPrefix), + ), + ); + + final ExpressionStatement stmt2 = body.statements[1] as ExpressionStatement; + final StaticInvocation staticInv = stmt2.expression as StaticInvocation; + Expect.isTrue( + staticInv.arguments.named.any( + (n) => n.name.startsWith(creationLocationPrefix), + ), + ); + final NamedExpression namedArg2 = staticInv.arguments.named.firstWhere( + (n) => n.name.startsWith(creationLocationPrefix), + ); + Expect.isTrue(namedArg2.value is ConstructorInvocation); + final ConstructorInvocation locInvocation2 = + namedArg2.value as ConstructorInvocation; + Expect.equals(creationLocationClass, locInvocation2.target.enclosingClass); }