[cfe] Implements recursiveScanner to solve copyWith ambiguities
Change-Id: I087e12702d8164ad0a7383bb5b0665d58a516f7c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/162743
Reviewed-by: Dmitry Stefantsov <dmitryas@google.com>
Commit-Queue: Javier López-Contreras <jlcontreras@google.com>
diff --git a/pkg/front_end/lib/src/fasta/kernel/kernel_target.dart b/pkg/front_end/lib/src/fasta/kernel/kernel_target.dart
index 53d5493..375ac5c 100644
--- a/pkg/front_end/lib/src/fasta/kernel/kernel_target.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/kernel_target.dart
@@ -1128,7 +1128,7 @@
if (loader.target.context.options
.isExperimentEnabledGlobally(ExperimentalFlag.valueClass)) {
valueClass.transformComponent(
- component, loader.coreTypes, loader.hierarchy);
+ component, loader.coreTypes, loader.hierarchy, environment);
ticker.logMs("Lowered value classes");
}
diff --git a/pkg/front_end/testcases/strong.status b/pkg/front_end/testcases/strong.status
index 6c237da..660c22a 100644
--- a/pkg/front_end/testcases/strong.status
+++ b/pkg/front_end/testcases/strong.status
@@ -257,4 +257,5 @@
set_literals/disambiguation_rule: RuntimeError
value_class/simple: RuntimeError # Expected
value_class/value_extends_non_value: RuntimeError # Expected
-value_class/value_implements_non_value: RuntimeError # Expected
\ No newline at end of file
+value_class/value_implements_non_value: RuntimeError # Expected
+value_class/copy_with_call_sites: RuntimeError # Expected
\ No newline at end of file
diff --git a/pkg/front_end/testcases/text_serialization.status b/pkg/front_end/testcases/text_serialization.status
index 825cdf1..07eb30c 100644
--- a/pkg/front_end/testcases/text_serialization.status
+++ b/pkg/front_end/testcases/text_serialization.status
@@ -255,3 +255,4 @@
value_class/simple: RuntimeError # Expected
value_class/value_extends_non_value: RuntimeError # Expected
value_class/value_implements_non_value: RuntimeError # Expected
+value_class/copy_with_call_sites: RuntimeError # Expected
\ No newline at end of file
diff --git a/pkg/front_end/testcases/value_class/copy_with_call_sites.dart b/pkg/front_end/testcases/value_class/copy_with_call_sites.dart
new file mode 100644
index 0000000..031deb0
--- /dev/null
+++ b/pkg/front_end/testcases/value_class/copy_with_call_sites.dart
@@ -0,0 +1,23 @@
+// Copyright (c) 2020, 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 'value_class_support_lib.dart';
+
+class Animal {
+ final int numberOfLegs;
+ Animal({required this.numberOfLegs});
+}
+
+@valueClass
+class Cat extends Animal {
+ final int numberOfWhiskers;
+}
+
+main() {
+ Cat cat = new Cat(numberOfWhiskers: 20, numberOfLegs: 4);
+ (cat as dynamic).copyWith(numberOfWhiskers: 4);
+}
+
+
+
diff --git a/pkg/front_end/testcases/value_class/copy_with_call_sites.dart.outline.expect b/pkg/front_end/testcases/value_class/copy_with_call_sites.dart.outline.expect
new file mode 100644
index 0000000..0e4993f
--- /dev/null
+++ b/pkg/front_end/testcases/value_class/copy_with_call_sites.dart.outline.expect
@@ -0,0 +1,34 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+import "value_class_support_lib.dart" as val;
+
+import "org-dartlang-testcase:///value_class_support_lib.dart";
+
+class Animal extends core::Object {
+ final field core::int numberOfLegs;
+ constructor •({required core::int numberOfLegs}) → self::Animal
+ ;
+}
+@val::valueClass
+class Cat extends self::Animal {
+ final field core::int numberOfWhiskers;
+ synthetic constructor •() → self::Cat
+ ;
+}
+static method main() → dynamic
+ ;
+
+library /*isNonNullableByDefault*/;
+import self as val;
+import "dart:core" as core;
+
+class JenkinsSmiHash extends core::Object {
+ synthetic constructor •() → val::JenkinsSmiHash
+ ;
+ static method combine(core::int hash, core::int value) → core::int
+ ;
+ static method finish(core::int hash) → core::int
+ ;
+}
+static const field core::String valueClass = "valueClass";
diff --git a/pkg/front_end/testcases/value_class/copy_with_call_sites.dart.strong.expect b/pkg/front_end/testcases/value_class/copy_with_call_sites.dart.strong.expect
new file mode 100644
index 0000000..dd3b9ec
--- /dev/null
+++ b/pkg/front_end/testcases/value_class/copy_with_call_sites.dart.strong.expect
@@ -0,0 +1,72 @@
+library /*isNonNullableByDefault*/;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/value_class/copy_with_call_sites.dart:18:20: Error: No named parameter with the name 'numberOfWhiskers'.
+// Cat cat = new Cat(numberOfWhiskers: 20, numberOfLegs: 4);
+// ^^^^^^^^^^^^^^^^
+// pkg/front_end/testcases/value_class/copy_with_call_sites.dart:13:7: Context: The class 'Cat' has a constructor that takes no arguments.
+// class Cat extends Animal {
+// ^
+//
+// pkg/front_end/testcases/value_class/copy_with_call_sites.dart:14:13: Error: Final field 'numberOfWhiskers' is not initialized.
+// Try to initialize the field in the declaration or in every constructor.
+// final int numberOfWhiskers;
+// ^^^^^^^^^^^^^^^^
+//
+import self as self;
+import "dart:core" as core;
+import "value_class_support_lib.dart" as val;
+
+import "org-dartlang-testcase:///value_class_support_lib.dart";
+
+class Animal extends core::Object {
+ final field core::int numberOfLegs;
+ constructor •({required core::int numberOfLegs = #C1}) → self::Animal
+ : self::Animal::numberOfLegs = numberOfLegs, super core::Object::•()
+ ;
+}
+class Cat extends self::Animal {
+ final field core::int numberOfWhiskers = null;
+ synthetic constructor •({required core::int numberOfWhiskers, core::int numberOfLegs}) → self::Cat
+ : self::Cat::numberOfWhiskers = numberOfWhiskers, super self::Animal::•(numberOfLegs)
+ ;
+ operator /*isNullableByDefault*/ ==(core::Object other) → core::bool
+ return other is self::Cat && this.{self::Animal::numberOfLegs}.{core::num::==}(other{self::Cat}.{self::Animal::numberOfLegs}) && this.{self::Cat::numberOfWhiskers}.{core::num::==}(other{self::Cat}.{self::Cat::numberOfWhiskers});
+ get /*isNullableByDefault*/ hashCode() → core::int
+ return val::JenkinsSmiHash::finish(val::JenkinsSmiHash::combine(val::JenkinsSmiHash::combine("org-dartlang-testcase:///copy_with_call_sites.dartCat".{core::String::hashCode}, this.{self::Animal::numberOfLegs}.{core::num::hashCode}), this.{self::Cat::numberOfWhiskers}.{core::num::hashCode}));
+ method /*isNullableByDefault*/ copyWith({core::int numberOfLegs, core::int numberOfWhiskers}) → dynamic
+ return new self::Cat::•(numberOfLegs: numberOfLegs, numberOfWhiskers: numberOfWhiskers);
+}
+static method main() → dynamic {
+ self::Cat cat = invalid-expression "pkg/front_end/testcases/value_class/copy_with_call_sites.dart:18:20: Error: No named parameter with the name 'numberOfWhiskers'.
+ Cat cat = new Cat(numberOfWhiskers: 20, numberOfLegs: 4);
+ ^^^^^^^^^^^^^^^^" as{TypeError,ForDynamic,ForNonNullableByDefault} self::Cat;
+ let final dynamic #t1 = cat as{ForNonNullableByDefault} dynamic in #t1.copyWith(numberOfWhiskers: 4, numberOfLegs: #t1.numberOfLegs);
+}
+
+library /*isNonNullableByDefault*/;
+import self as val;
+import "dart:core" as core;
+
+class JenkinsSmiHash extends core::Object {
+ synthetic constructor •() → val::JenkinsSmiHash
+ : super core::Object::•()
+ ;
+ static method combine(core::int hash, core::int value) → core::int {
+ hash = 536870911.{core::int::&}(hash.{core::num::+}(value));
+ hash = 536870911.{core::int::&}(hash.{core::num::+}(524287.{core::int::&}(hash).{core::int::<<}(10)));
+ return hash.{core::int::^}(hash.{core::int::>>}(6));
+ }
+ static method finish(core::int hash) → core::int {
+ hash = 536870911.{core::int::&}(hash.{core::num::+}(67108863.{core::int::&}(hash).{core::int::<<}(3)));
+ hash = hash.{core::int::^}(hash.{core::int::>>}(11));
+ return 536870911.{core::int::&}(hash.{core::num::+}(16383.{core::int::&}(hash).{core::int::<<}(15)));
+ }
+}
+static const field core::String valueClass = #C2;
+
+constants {
+ #C1 = null
+ #C2 = "valueClass"
+}
diff --git a/pkg/front_end/testcases/value_class/copy_with_call_sites.dart.strong.transformed.expect b/pkg/front_end/testcases/value_class/copy_with_call_sites.dart.strong.transformed.expect
new file mode 100644
index 0000000..f361669
--- /dev/null
+++ b/pkg/front_end/testcases/value_class/copy_with_call_sites.dart.strong.transformed.expect
@@ -0,0 +1,72 @@
+library /*isNonNullableByDefault*/;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/value_class/copy_with_call_sites.dart:18:20: Error: No named parameter with the name 'numberOfWhiskers'.
+// Cat cat = new Cat(numberOfWhiskers: 20, numberOfLegs: 4);
+// ^^^^^^^^^^^^^^^^
+// pkg/front_end/testcases/value_class/copy_with_call_sites.dart:13:7: Context: The class 'Cat' has a constructor that takes no arguments.
+// class Cat extends Animal {
+// ^
+//
+// pkg/front_end/testcases/value_class/copy_with_call_sites.dart:14:13: Error: Final field 'numberOfWhiskers' is not initialized.
+// Try to initialize the field in the declaration or in every constructor.
+// final int numberOfWhiskers;
+// ^^^^^^^^^^^^^^^^
+//
+import self as self;
+import "dart:core" as core;
+import "value_class_support_lib.dart" as val;
+
+import "org-dartlang-testcase:///value_class_support_lib.dart";
+
+class Animal extends core::Object {
+ final field core::int numberOfLegs;
+ constructor •({required core::int numberOfLegs = #C1}) → self::Animal
+ : self::Animal::numberOfLegs = numberOfLegs, super core::Object::•()
+ ;
+}
+class Cat extends self::Animal {
+ final field core::int numberOfWhiskers = null;
+ synthetic constructor •({required core::int numberOfWhiskers, core::int numberOfLegs}) → self::Cat
+ : self::Cat::numberOfWhiskers = numberOfWhiskers, super self::Animal::•(numberOfLegs)
+ ;
+ operator /*isNullableByDefault*/ ==(core::Object other) → core::bool
+ return other is self::Cat && this.{self::Animal::numberOfLegs}.{core::num::==}(other{self::Cat}.{self::Animal::numberOfLegs}) && this.{self::Cat::numberOfWhiskers}.{core::num::==}(other{self::Cat}.{self::Cat::numberOfWhiskers});
+ get /*isNullableByDefault*/ hashCode() → core::int
+ return val::JenkinsSmiHash::finish(val::JenkinsSmiHash::combine(val::JenkinsSmiHash::combine("org-dartlang-testcase:///copy_with_call_sites.dartCat".{core::String::hashCode}, this.{self::Animal::numberOfLegs}.{core::num::hashCode}), this.{self::Cat::numberOfWhiskers}.{core::num::hashCode}));
+ method /*isNullableByDefault*/ copyWith({core::int numberOfLegs, core::int numberOfWhiskers}) → dynamic
+ return new self::Cat::•(numberOfLegs: numberOfLegs, numberOfWhiskers: numberOfWhiskers);
+}
+static method main() → dynamic {
+ self::Cat cat = invalid-expression "pkg/front_end/testcases/value_class/copy_with_call_sites.dart:18:20: Error: No named parameter with the name 'numberOfWhiskers'.
+ Cat cat = new Cat(numberOfWhiskers: 20, numberOfLegs: 4);
+ ^^^^^^^^^^^^^^^^";
+ let final dynamic #t1 = cat as{ForNonNullableByDefault} dynamic in #t1.copyWith(numberOfWhiskers: 4, numberOfLegs: #t1.numberOfLegs);
+}
+
+library /*isNonNullableByDefault*/;
+import self as val;
+import "dart:core" as core;
+
+class JenkinsSmiHash extends core::Object {
+ synthetic constructor •() → val::JenkinsSmiHash
+ : super core::Object::•()
+ ;
+ static method combine(core::int hash, core::int value) → core::int {
+ hash = 536870911.{core::int::&}(hash.{core::num::+}(value));
+ hash = 536870911.{core::int::&}(hash.{core::num::+}(524287.{core::int::&}(hash).{core::int::<<}(10)));
+ return hash.{core::int::^}(hash.{core::int::>>}(6));
+ }
+ static method finish(core::int hash) → core::int {
+ hash = 536870911.{core::int::&}(hash.{core::num::+}(67108863.{core::int::&}(hash).{core::int::<<}(3)));
+ hash = hash.{core::int::^}(hash.{core::int::>>}(11));
+ return 536870911.{core::int::&}(hash.{core::num::+}(16383.{core::int::&}(hash).{core::int::<<}(15)));
+ }
+}
+static const field core::String valueClass = #C2;
+
+constants {
+ #C1 = null
+ #C2 = "valueClass"
+}
diff --git a/pkg/front_end/testcases/value_class/copy_with_call_sites.dart.textual_outline.expect b/pkg/front_end/testcases/value_class/copy_with_call_sites.dart.textual_outline.expect
new file mode 100644
index 0000000..93a182d
--- /dev/null
+++ b/pkg/front_end/testcases/value_class/copy_with_call_sites.dart.textual_outline.expect
@@ -0,0 +1,13 @@
+import 'value_class_support_lib.dart';
+
+class Animal {
+ final int numberOfLegs;
+ Animal({required this.numberOfLegs});
+}
+
+@valueClass
+class Cat extends Animal {
+ final int numberOfWhiskers;
+}
+
+main() {}
diff --git a/pkg/front_end/testcases/value_class/copy_with_call_sites.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/value_class/copy_with_call_sites.dart.textual_outline_modelled.expect
new file mode 100644
index 0000000..ce2b2e3
--- /dev/null
+++ b/pkg/front_end/testcases/value_class/copy_with_call_sites.dart.textual_outline_modelled.expect
@@ -0,0 +1,13 @@
+import 'value_class_support_lib.dart';
+
+class Animal {
+ Animal({required this.numberOfLegs});
+ final int numberOfLegs;
+}
+
+@valueClass
+class Cat extends Animal {
+ final int numberOfWhiskers;
+}
+
+main() {}
diff --git a/pkg/front_end/testcases/value_class/copy_with_call_sites.dart.weak.expect b/pkg/front_end/testcases/value_class/copy_with_call_sites.dart.weak.expect
new file mode 100644
index 0000000..dd3b9ec
--- /dev/null
+++ b/pkg/front_end/testcases/value_class/copy_with_call_sites.dart.weak.expect
@@ -0,0 +1,72 @@
+library /*isNonNullableByDefault*/;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/value_class/copy_with_call_sites.dart:18:20: Error: No named parameter with the name 'numberOfWhiskers'.
+// Cat cat = new Cat(numberOfWhiskers: 20, numberOfLegs: 4);
+// ^^^^^^^^^^^^^^^^
+// pkg/front_end/testcases/value_class/copy_with_call_sites.dart:13:7: Context: The class 'Cat' has a constructor that takes no arguments.
+// class Cat extends Animal {
+// ^
+//
+// pkg/front_end/testcases/value_class/copy_with_call_sites.dart:14:13: Error: Final field 'numberOfWhiskers' is not initialized.
+// Try to initialize the field in the declaration or in every constructor.
+// final int numberOfWhiskers;
+// ^^^^^^^^^^^^^^^^
+//
+import self as self;
+import "dart:core" as core;
+import "value_class_support_lib.dart" as val;
+
+import "org-dartlang-testcase:///value_class_support_lib.dart";
+
+class Animal extends core::Object {
+ final field core::int numberOfLegs;
+ constructor •({required core::int numberOfLegs = #C1}) → self::Animal
+ : self::Animal::numberOfLegs = numberOfLegs, super core::Object::•()
+ ;
+}
+class Cat extends self::Animal {
+ final field core::int numberOfWhiskers = null;
+ synthetic constructor •({required core::int numberOfWhiskers, core::int numberOfLegs}) → self::Cat
+ : self::Cat::numberOfWhiskers = numberOfWhiskers, super self::Animal::•(numberOfLegs)
+ ;
+ operator /*isNullableByDefault*/ ==(core::Object other) → core::bool
+ return other is self::Cat && this.{self::Animal::numberOfLegs}.{core::num::==}(other{self::Cat}.{self::Animal::numberOfLegs}) && this.{self::Cat::numberOfWhiskers}.{core::num::==}(other{self::Cat}.{self::Cat::numberOfWhiskers});
+ get /*isNullableByDefault*/ hashCode() → core::int
+ return val::JenkinsSmiHash::finish(val::JenkinsSmiHash::combine(val::JenkinsSmiHash::combine("org-dartlang-testcase:///copy_with_call_sites.dartCat".{core::String::hashCode}, this.{self::Animal::numberOfLegs}.{core::num::hashCode}), this.{self::Cat::numberOfWhiskers}.{core::num::hashCode}));
+ method /*isNullableByDefault*/ copyWith({core::int numberOfLegs, core::int numberOfWhiskers}) → dynamic
+ return new self::Cat::•(numberOfLegs: numberOfLegs, numberOfWhiskers: numberOfWhiskers);
+}
+static method main() → dynamic {
+ self::Cat cat = invalid-expression "pkg/front_end/testcases/value_class/copy_with_call_sites.dart:18:20: Error: No named parameter with the name 'numberOfWhiskers'.
+ Cat cat = new Cat(numberOfWhiskers: 20, numberOfLegs: 4);
+ ^^^^^^^^^^^^^^^^" as{TypeError,ForDynamic,ForNonNullableByDefault} self::Cat;
+ let final dynamic #t1 = cat as{ForNonNullableByDefault} dynamic in #t1.copyWith(numberOfWhiskers: 4, numberOfLegs: #t1.numberOfLegs);
+}
+
+library /*isNonNullableByDefault*/;
+import self as val;
+import "dart:core" as core;
+
+class JenkinsSmiHash extends core::Object {
+ synthetic constructor •() → val::JenkinsSmiHash
+ : super core::Object::•()
+ ;
+ static method combine(core::int hash, core::int value) → core::int {
+ hash = 536870911.{core::int::&}(hash.{core::num::+}(value));
+ hash = 536870911.{core::int::&}(hash.{core::num::+}(524287.{core::int::&}(hash).{core::int::<<}(10)));
+ return hash.{core::int::^}(hash.{core::int::>>}(6));
+ }
+ static method finish(core::int hash) → core::int {
+ hash = 536870911.{core::int::&}(hash.{core::num::+}(67108863.{core::int::&}(hash).{core::int::<<}(3)));
+ hash = hash.{core::int::^}(hash.{core::int::>>}(11));
+ return 536870911.{core::int::&}(hash.{core::num::+}(16383.{core::int::&}(hash).{core::int::<<}(15)));
+ }
+}
+static const field core::String valueClass = #C2;
+
+constants {
+ #C1 = null
+ #C2 = "valueClass"
+}
diff --git a/pkg/front_end/testcases/value_class/copy_with_call_sites.dart.weak.transformed.expect b/pkg/front_end/testcases/value_class/copy_with_call_sites.dart.weak.transformed.expect
new file mode 100644
index 0000000..f361669
--- /dev/null
+++ b/pkg/front_end/testcases/value_class/copy_with_call_sites.dart.weak.transformed.expect
@@ -0,0 +1,72 @@
+library /*isNonNullableByDefault*/;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/value_class/copy_with_call_sites.dart:18:20: Error: No named parameter with the name 'numberOfWhiskers'.
+// Cat cat = new Cat(numberOfWhiskers: 20, numberOfLegs: 4);
+// ^^^^^^^^^^^^^^^^
+// pkg/front_end/testcases/value_class/copy_with_call_sites.dart:13:7: Context: The class 'Cat' has a constructor that takes no arguments.
+// class Cat extends Animal {
+// ^
+//
+// pkg/front_end/testcases/value_class/copy_with_call_sites.dart:14:13: Error: Final field 'numberOfWhiskers' is not initialized.
+// Try to initialize the field in the declaration or in every constructor.
+// final int numberOfWhiskers;
+// ^^^^^^^^^^^^^^^^
+//
+import self as self;
+import "dart:core" as core;
+import "value_class_support_lib.dart" as val;
+
+import "org-dartlang-testcase:///value_class_support_lib.dart";
+
+class Animal extends core::Object {
+ final field core::int numberOfLegs;
+ constructor •({required core::int numberOfLegs = #C1}) → self::Animal
+ : self::Animal::numberOfLegs = numberOfLegs, super core::Object::•()
+ ;
+}
+class Cat extends self::Animal {
+ final field core::int numberOfWhiskers = null;
+ synthetic constructor •({required core::int numberOfWhiskers, core::int numberOfLegs}) → self::Cat
+ : self::Cat::numberOfWhiskers = numberOfWhiskers, super self::Animal::•(numberOfLegs)
+ ;
+ operator /*isNullableByDefault*/ ==(core::Object other) → core::bool
+ return other is self::Cat && this.{self::Animal::numberOfLegs}.{core::num::==}(other{self::Cat}.{self::Animal::numberOfLegs}) && this.{self::Cat::numberOfWhiskers}.{core::num::==}(other{self::Cat}.{self::Cat::numberOfWhiskers});
+ get /*isNullableByDefault*/ hashCode() → core::int
+ return val::JenkinsSmiHash::finish(val::JenkinsSmiHash::combine(val::JenkinsSmiHash::combine("org-dartlang-testcase:///copy_with_call_sites.dartCat".{core::String::hashCode}, this.{self::Animal::numberOfLegs}.{core::num::hashCode}), this.{self::Cat::numberOfWhiskers}.{core::num::hashCode}));
+ method /*isNullableByDefault*/ copyWith({core::int numberOfLegs, core::int numberOfWhiskers}) → dynamic
+ return new self::Cat::•(numberOfLegs: numberOfLegs, numberOfWhiskers: numberOfWhiskers);
+}
+static method main() → dynamic {
+ self::Cat cat = invalid-expression "pkg/front_end/testcases/value_class/copy_with_call_sites.dart:18:20: Error: No named parameter with the name 'numberOfWhiskers'.
+ Cat cat = new Cat(numberOfWhiskers: 20, numberOfLegs: 4);
+ ^^^^^^^^^^^^^^^^";
+ let final dynamic #t1 = cat as{ForNonNullableByDefault} dynamic in #t1.copyWith(numberOfWhiskers: 4, numberOfLegs: #t1.numberOfLegs);
+}
+
+library /*isNonNullableByDefault*/;
+import self as val;
+import "dart:core" as core;
+
+class JenkinsSmiHash extends core::Object {
+ synthetic constructor •() → val::JenkinsSmiHash
+ : super core::Object::•()
+ ;
+ static method combine(core::int hash, core::int value) → core::int {
+ hash = 536870911.{core::int::&}(hash.{core::num::+}(value));
+ hash = 536870911.{core::int::&}(hash.{core::num::+}(524287.{core::int::&}(hash).{core::int::<<}(10)));
+ return hash.{core::int::^}(hash.{core::int::>>}(6));
+ }
+ static method finish(core::int hash) → core::int {
+ hash = 536870911.{core::int::&}(hash.{core::num::+}(67108863.{core::int::&}(hash).{core::int::<<}(3)));
+ hash = hash.{core::int::^}(hash.{core::int::>>}(11));
+ return 536870911.{core::int::&}(hash.{core::num::+}(16383.{core::int::&}(hash).{core::int::<<}(15)));
+ }
+}
+static const field core::String valueClass = #C2;
+
+constants {
+ #C1 = null
+ #C2 = "valueClass"
+}
diff --git a/pkg/front_end/testcases/weak.status b/pkg/front_end/testcases/weak.status
index fee0310..eae95b4 100644
--- a/pkg/front_end/testcases/weak.status
+++ b/pkg/front_end/testcases/weak.status
@@ -75,4 +75,5 @@
nnbd_mixed/never_opt_out: TypeCheckError
value_class/simple: RuntimeError # Expected
value_class/value_extends_non_value: RuntimeError # Expected
-value_class/value_implements_non_value: RuntimeError # Expected
\ No newline at end of file
+value_class/value_implements_non_value: RuntimeError # Expected
+value_class/copy_with_call_sites: RuntimeError # Expected
\ No newline at end of file
diff --git a/pkg/kernel/lib/transformations/scanner.dart b/pkg/kernel/lib/transformations/scanner.dart
index 6d4a690..ce4b9fc 100644
--- a/pkg/kernel/lib/transformations/scanner.dart
+++ b/pkg/kernel/lib/transformations/scanner.dart
@@ -11,7 +11,7 @@
}
class ScanResult<X extends TreeNode, Y extends TreeNode> {
- Map<X, ScanResult<Y, TreeNode>> targets;
+ Map<X, ScanResult<Y, TreeNode>> targets = new Map();
Map<X, ScanError> errors;
}
@@ -26,11 +26,12 @@
ScanResult<Class, Y> scan(TreeNode node) {
ScanResult<Class, Y> result = new ScanResult();
- result.targets = new Map();
if (node is Class) {
if (predicate(node)) {
result.targets[node] = next?.scan(node);
+ // TODO(dmitryas): set result.errors when specification is landed,
+ // same with all other places where targets is set
}
} else if (node is Library) {
_scanLibrary(node, result);
@@ -65,12 +66,9 @@
ScanResult<Field, Y> scan(TreeNode node) {
ScanResult<Field, Y> result = new ScanResult();
- result.targets = new Map();
if (node is Field) {
- if (predicate(node)) {
- result.targets[node] = next?.scan(node);
- }
+ _scanField(node, result);
} else if (node is Class) {
_scanClass(node, result);
} else if (node is Library) {
@@ -82,11 +80,15 @@
return result;
}
+ void _scanField(Field field, ScanResult<Field, Y> result) {
+ if (predicate(field)) {
+ result.targets[field] = next?.scan(field);
+ }
+ }
+
void _scanClass(Class cls, ScanResult<Field, Y> result) {
for (Field field in cls.fields) {
- if (predicate(field)) {
- result.targets[field] = next?.scan(field);
- }
+ _scanField(field, result);
}
}
@@ -95,9 +97,7 @@
_scanClass(cls, result);
}
for (Field field in library.fields) {
- if (predicate(field)) {
- result.targets[field] = next?.scan(field);
- }
+ _scanField(field, result);
}
}
@@ -108,6 +108,57 @@
}
}
+abstract class MemberScanner<Y extends TreeNode> implements Scanner<Member, Y> {
+ final Scanner<Y, TreeNode> next;
+
+ MemberScanner(this.next);
+
+ bool predicate(Member node);
+
+ ScanResult<Member, Y> scan(TreeNode node) {
+ ScanResult<Member, Y> result = new ScanResult();
+
+ if (node is Member) {
+ _scanMember(node, result);
+ } else if (node is Class) {
+ _scanClass(node, result);
+ } else if (node is Library) {
+ _scanLibrary(node, result);
+ } else if (node is Component) {
+ _scanComponent(node, result);
+ }
+
+ return result;
+ }
+
+ void _scanMember(Member member, ScanResult<Member, Y> result) {
+ if (predicate(member)) {
+ result.targets[member] = next?.scan(member);
+ }
+ }
+
+ void _scanClass(Class cls, ScanResult<Member, Y> result) {
+ for (Member member in cls.members) {
+ _scanMember(member, result);
+ }
+ }
+
+ void _scanLibrary(Library library, ScanResult<Member, Y> result) {
+ for (Class cls in library.classes) {
+ _scanClass(cls, result);
+ }
+ for (Member member in library.members) {
+ _scanMember(member, result);
+ }
+ }
+
+ void _scanComponent(Component component, ScanResult<Member, Y> result) {
+ for (Library library in component.libraries) {
+ _scanLibrary(library, result);
+ }
+ }
+}
+
abstract class ProcedureScanner<Y extends TreeNode>
implements Scanner<Procedure, Y> {
final Scanner<Y, TreeNode> next;
@@ -118,12 +169,9 @@
ScanResult<Procedure, Y> scan(TreeNode node) {
ScanResult<Procedure, Y> result = new ScanResult();
- result.targets = new Map();
if (node is Procedure) {
- if (predicate(node)) {
- result.targets[node] = next?.scan(node);
- }
+ _scanProcedure(node, result);
} else if (node is Class) {
_scanClass(node, result);
} else if (node is Library) {
@@ -135,11 +183,15 @@
return result;
}
+ void _scanProcedure(Procedure procedure, ScanResult<Procedure, Y> result) {
+ if (predicate(procedure)) {
+ result.targets[procedure] = next?.scan(procedure);
+ }
+ }
+
void _scanClass(Class cls, ScanResult<Procedure, Y> result) {
for (Procedure procedure in cls.procedures) {
- if (predicate(procedure)) {
- result.targets[procedure] = next?.scan(procedure);
- }
+ _scanProcedure(procedure, result);
}
}
@@ -148,9 +200,7 @@
_scanClass(cls, result);
}
for (Procedure procedure in library.procedures) {
- if (predicate(procedure)) {
- result.targets[procedure] = next?.scan(procedure);
- }
+ _scanProcedure(procedure, result);
}
}
@@ -160,3 +210,51 @@
}
}
}
+
+abstract class ExpressionScanner<Y extends TreeNode>
+ extends RecursiveVisitor<void> implements Scanner<Expression, Y> {
+ final Scanner<Y, TreeNode> next;
+ ScanResult<Expression, Y> _result;
+
+ ExpressionScanner(this.next);
+
+ bool predicate(Expression node);
+
+ ScanResult<Expression, Y> scan(TreeNode node) {
+ ScanResult<Expression, Y> result = _result = new ScanResult();
+ node.accept(this);
+ _result = null;
+ return result;
+ }
+
+ void visitExpression(Expression node) {
+ if (predicate(node)) {
+ _result.targets[node] = next?.scan(node);
+ // TODO: Update result.errors.
+ }
+ }
+}
+
+abstract class MethodInvocationScanner<Y extends TreeNode>
+ extends RecursiveVisitor<void> implements Scanner<MethodInvocation, Y> {
+ final Scanner<Y, TreeNode> next;
+ ScanResult<MethodInvocation, Y> _result;
+
+ MethodInvocationScanner(this.next);
+
+ bool predicate(MethodInvocation node);
+
+ ScanResult<MethodInvocation, Y> scan(TreeNode node) {
+ ScanResult<MethodInvocation, Y> result = _result = new ScanResult();
+ node.accept(this);
+ _result = null;
+ return result;
+ }
+
+ void visitMethodInvocation(MethodInvocation node) {
+ if (predicate(node)) {
+ _result.targets[node] = next?.scan(node);
+ // TODO: Update result.errors.
+ }
+ }
+}
diff --git a/pkg/kernel/lib/transformations/value_class.dart b/pkg/kernel/lib/transformations/value_class.dart
index c760a89..c616cea 100644
--- a/pkg/kernel/lib/transformations/value_class.dart
+++ b/pkg/kernel/lib/transformations/value_class.dart
@@ -4,6 +4,8 @@
library kernel.transformations.value_class;
+import 'package:kernel/type_environment.dart';
+
import '../ast.dart';
import '../kernel.dart';
import '../core_types.dart' show CoreTypes;
@@ -13,18 +15,7 @@
class ValueClassScanner extends ClassScanner<Null> {
ValueClassScanner() : super(null);
- bool predicate(Class node) {
- for (Expression annotation in node.annotations) {
- if (annotation is ConstantExpression &&
- annotation.constant is StringConstant) {
- StringConstant constant = annotation.constant;
- if (constant.value == 'valueClass') {
- return true;
- }
- }
- }
- return false;
- }
+ bool predicate(Class node) => isValueClass(node);
}
class JenkinsClassScanner extends ClassScanner<Procedure> {
@@ -43,17 +34,48 @@
}
}
-void transformComponent(
- Component node, CoreTypes coreTypes, ClassHierarchy hierarchy) {
- ValueClassScanner scanner = new ValueClassScanner();
- ScanResult<Class, Null> valueClasses = scanner.scan(node);
- for (Class valueClass in valueClasses.targets.keys) {
- transformValueClass(valueClass, coreTypes, hierarchy);
+class AllMemberScanner extends MemberScanner<MethodInvocation> {
+ AllMemberScanner(Scanner<MethodInvocation, TreeNode> next) : super(next);
+
+ bool predicate(Member member) => true;
+}
+
+// Scans and matches all copyWith invocations were the reciever is _ as dynamic
+// It will filter out the results that are not value classes afterwards
+class ValueClassCopyWithScanner extends MethodInvocationScanner<Null> {
+ ValueClassCopyWithScanner() : super(null);
+
+ // The matching construct followed in unit-tests is:
+ // @valueClass V {}
+ // V v;
+ // (v as dynamic).copyWith() as V
+ bool predicate(MethodInvocation node) {
+ return node.name.name == "copyWith" &&
+ _isValueClassAsConstruct(node.receiver);
+ }
+
+ bool _isValueClassAsConstruct(Expression node) {
+ return node is AsExpression && node.type is DynamicType;
}
}
-void transformValueClass(
- Class cls, CoreTypes coreTypes, ClassHierarchy hierarchy) {
+void transformComponent(Component node, CoreTypes coreTypes,
+ ClassHierarchy hierarchy, TypeEnvironment typeEnvironment) {
+ ValueClassScanner scanner = new ValueClassScanner();
+ ScanResult<Class, Null> valueClasses = scanner.scan(node);
+ for (Class valueClass in valueClasses.targets.keys) {
+ transformValueClass(valueClass, coreTypes, hierarchy, typeEnvironment);
+ }
+
+ treatCopyWithCallSites(node, coreTypes, typeEnvironment, hierarchy);
+
+ for (Class valueClass in valueClasses.targets.keys) {
+ removeValueClassAnnotation(valueClass);
+ }
+}
+
+void transformValueClass(Class cls, CoreTypes coreTypes,
+ ClassHierarchy hierarchy, TypeEnvironment typeEnvironment) {
List<VariableDeclaration> allVariables = queryAllInstanceVariables(cls);
Constructor syntheticConstructor = null;
for (Constructor constructor in cls.constructors) {
@@ -65,8 +87,8 @@
addConstructor(cls, coreTypes, syntheticConstructor);
addEqualsOperator(cls, coreTypes, hierarchy, allVariables.toList());
addHashCode(cls, coreTypes, hierarchy, allVariables.toList());
- addCopyWith(
- cls, coreTypes, hierarchy, allVariables.toList(), syntheticConstructor);
+ addCopyWith(cls, coreTypes, hierarchy, allVariables.toList(),
+ syntheticConstructor, typeEnvironment);
}
void addConstructor(
@@ -103,21 +125,6 @@
..addAll(ownFields.values)
..addAll(superParameters);
syntheticConstructor.initializers = initializersConstructor;
-
- int valueClassAnnotationIndex;
- for (int annotationIndex = 0;
- annotationIndex < cls.annotations.length;
- annotationIndex++) {
- Expression annotation = cls.annotations[annotationIndex];
- if (annotation is ConstantExpression &&
- annotation.constant is StringConstant) {
- StringConstant constant = annotation.constant;
- if (constant.value == 'valueClass') {
- valueClassAnnotationIndex = annotationIndex;
- }
- }
- }
- cls.annotations.removeAt(valueClassAnnotationIndex);
}
void addEqualsOperator(Class cls, CoreTypes coreTypes, ClassHierarchy hierarchy,
@@ -245,8 +252,13 @@
..fileOffset = cls.fileOffset);
}
-void addCopyWith(Class cls, CoreTypes coreTypes, ClassHierarchy hierarchy,
- List<VariableDeclaration> allVariables, Constructor syntheticConstructor) {
+void addCopyWith(
+ Class cls,
+ CoreTypes coreTypes,
+ ClassHierarchy hierarchy,
+ List<VariableDeclaration> allVariables,
+ Constructor syntheticConstructor,
+ TypeEnvironment typeEnvironment) {
Map<VariableDeclaration, Member> targetsEquals = new Map();
Map<VariableDeclaration, Member> targets = new Map();
for (VariableDeclaration variable in allVariables) {
@@ -291,3 +303,98 @@
..addAll(cls.fields.map<VariableDeclaration>(
(f) => VariableDeclaration(f.name.text, type: f.type)));
}
+
+void removeValueClassAnnotation(Class cls) {
+ int valueClassAnnotationIndex;
+ for (int annotationIndex = 0;
+ annotationIndex < cls.annotations.length;
+ annotationIndex++) {
+ Expression annotation = cls.annotations[annotationIndex];
+ if (annotation is ConstantExpression &&
+ annotation.constant is StringConstant) {
+ StringConstant constant = annotation.constant;
+ if (constant.value == 'valueClass') {
+ valueClassAnnotationIndex = annotationIndex;
+ }
+ }
+ }
+ cls.annotations.removeAt(valueClassAnnotationIndex);
+}
+
+void treatCopyWithCallSites(Component component, CoreTypes coreTypes,
+ TypeEnvironment typeEnvironment, ClassHierarchy hierarchy) {
+ ValueClassCopyWithScanner valueCopyWithScanner =
+ new ValueClassCopyWithScanner();
+ AllMemberScanner copyWithScanner = AllMemberScanner(valueCopyWithScanner);
+ ScanResult<Member, MethodInvocation> copyWithCallSites =
+ copyWithScanner.scan(component);
+ for (Member memberWithCopyWith in copyWithCallSites.targets.keys) {
+ if (copyWithCallSites.targets[memberWithCopyWith].targets != null) {
+ StaticTypeContext staticTypeContext =
+ StaticTypeContext(memberWithCopyWith, typeEnvironment);
+ for (MethodInvocation copyWithCall
+ in copyWithCallSites.targets[memberWithCopyWith].targets.keys) {
+ AsExpression receiver = copyWithCall.receiver as AsExpression;
+
+ Expression valueClassInstance = receiver.operand;
+ DartType valueClassType =
+ valueClassInstance.getStaticType(staticTypeContext);
+ if (valueClassType is InterfaceType) {
+ Class valueClass = valueClassType.classNode;
+ if (isValueClass(valueClass)) {
+ treatCopyWithCallSite(
+ valueClass, copyWithCall, coreTypes, hierarchy);
+ }
+ }
+ }
+ }
+ }
+}
+
+void treatCopyWithCallSite(Class valueClass, MethodInvocation copyWithCall,
+ CoreTypes coreTypes, ClassHierarchy hierarchy) {
+ Map<String, Expression> preTransformationArguments = new Map();
+ for (NamedExpression argument in copyWithCall.arguments.named) {
+ preTransformationArguments[argument.name] = argument.value;
+ }
+ Constructor syntheticConstructor;
+ for (Constructor constructor in valueClass.constructors) {
+ if (constructor.isSynthetic) {
+ syntheticConstructor = constructor;
+ }
+ }
+ List<VariableDeclaration> allArguments =
+ syntheticConstructor.function.namedParameters;
+
+ VariableDeclaration letVariable =
+ VariableDeclaration.forValue(copyWithCall.receiver);
+ Arguments postTransformationArguments = Arguments.empty();
+ for (VariableDeclaration argument in allArguments) {
+ if (preTransformationArguments.containsKey(argument.name)) {
+ postTransformationArguments.named.add(NamedExpression(
+ argument.name, preTransformationArguments[argument.name])
+ ..parent = postTransformationArguments);
+ } else {
+ postTransformationArguments.named.add(NamedExpression(argument.name,
+ PropertyGet(VariableGet(letVariable), Name(argument.name)))
+ ..parent = postTransformationArguments);
+ }
+ }
+ copyWithCall.replaceWith(Let(
+ letVariable,
+ MethodInvocation(VariableGet(letVariable), Name("copyWith"),
+ postTransformationArguments)));
+}
+
+bool isValueClass(Class node) {
+ for (Expression annotation in node.annotations) {
+ if (annotation is ConstantExpression &&
+ annotation.constant is StringConstant) {
+ StringConstant constant = annotation.constant;
+ if (constant.value == "valueClass") {
+ return true;
+ }
+ }
+ }
+ return false;
+}