[cfe] Propagate isRedirectingFactoryConstructor flag when patching

Bug: https://github.com/dart-lang/sdk/issues/45101
Change-Id: Id0c9f3ce319111a79b2085f44a4e4243dbe6597a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/187440
Commit-Queue: Dmitry Stefantsov <dmitryas@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Daco Harkes <dacoharkes@google.com>
diff --git a/pkg/front_end/lib/src/fasta/builder/procedure_builder.dart b/pkg/front_end/lib/src/fasta/builder/procedure_builder.dart
index 4c89f92..ae2dc15 100644
--- a/pkg/front_end/lib/src/fasta/builder/procedure_builder.dart
+++ b/pkg/front_end/lib/src/fasta/builder/procedure_builder.dart
@@ -198,6 +198,8 @@
     origin.procedure.isExternal = _procedure.isExternal;
     origin.procedure.function = _procedure.function;
     origin.procedure.function.parent = origin.procedure;
+    origin.procedure.isRedirectingFactoryConstructor =
+        _procedure.isRedirectingFactoryConstructor;
     return 1;
   }
 }
@@ -712,6 +714,7 @@
     bodyInternal = new RedirectingFactoryBody(target, typeArguments);
     function.body = bodyInternal;
     bodyInternal?.parent = function;
+    procedure.isRedirectingFactoryConstructor = true;
     if (isPatch) {
       if (function.typeParameters != null) {
         Map<TypeParameter, DartType> substitution = <TypeParameter, DartType>{};
diff --git a/pkg/front_end/lib/src/fasta/kernel/redirecting_factory_body.dart b/pkg/front_end/lib/src/fasta/kernel/redirecting_factory_body.dart
index 0d82df2..cdbabbe 100644
--- a/pkg/front_end/lib/src/fasta/kernel/redirecting_factory_body.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/redirecting_factory_body.dart
@@ -6,22 +6,7 @@
 
 library fasta.redirecting_factory_body;
 
-import 'package:kernel/ast.dart'
-    show
-        DartType,
-        Expression,
-        ExpressionStatement,
-        Field,
-        FunctionNode,
-        InvalidExpression,
-        Let,
-        Member,
-        NullLiteral,
-        Procedure,
-        StaticGet,
-        StringLiteral,
-        TypeParameterType,
-        VariableDeclaration;
+import 'package:kernel/ast.dart';
 
 import 'package:kernel/type_algebra.dart' show Substitution;
 
@@ -44,6 +29,10 @@
 /// information in a factory method body.
 const String letName = "#redirecting_factory";
 
+/// Name used for a synthesized let variable used to encode type arguments to
+/// the redirection target in a factory method body.
+const String varNamePrefix = "#typeArg";
+
 class RedirectingFactoryBody extends ExpressionStatement {
   RedirectingFactoryBody.internal(Expression value,
       [List<DartType> typeArguments])
@@ -104,8 +93,38 @@
       ..parent = function;
   }
 
+  static bool hasRedirectingFactoryBodyShape(Procedure factory) {
+    if (factory.function.body is! ExpressionStatement) return false;
+    Expression body = (factory.function.body as ExpressionStatement).expression;
+    if (body is Let &&
+        body.variable.name == letName &&
+        body.variable.type is DynamicType &&
+        body.variable.initializer is StaticGet) {
+      Expression currentArgument = body.body;
+      int argumentCount = 0;
+      while (currentArgument is! InvalidExpression) {
+        Expression argument = currentArgument;
+        if (argument is Let) {
+          String argumentName = "${varNamePrefix}${argumentCount}";
+          if (argument.variable.name != argumentName) {
+            return false;
+          }
+          if (argument.variable.initializer is! NullLiteral) {
+            return false;
+          }
+          currentArgument = argument.body;
+          ++argumentCount;
+        } else {
+          return false;
+        }
+      }
+      return true;
+    } else {
+      return false;
+    }
+  }
+
   static Expression encodeTypeArguments(List<DartType> typeArguments) {
-    String varNamePrefix = "#typeArg";
     Expression result = new InvalidExpression(null);
     if (typeArguments == null) {
       return result;
diff --git a/pkg/front_end/lib/src/fasta/kernel/verifier.dart b/pkg/front_end/lib/src/fasta/kernel/verifier.dart
index 468697e..9a23516 100644
--- a/pkg/front_end/lib/src/fasta/kernel/verifier.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/verifier.dart
@@ -29,7 +29,10 @@
 import '../type_inference/type_schema.dart' show UnknownType;
 
 import 'redirecting_factory_body.dart'
-    show RedirectingFactoryBody, getRedirectingFactoryBody;
+    show
+        RedirectingFactoryBody,
+        getRedirectingFactoryBody,
+        isRedirectingFactory;
 
 List<LocatedMessage> verifyComponent(Component component,
     {bool isOutline, bool afterConst, bool skipPlatform: false}) {
@@ -220,7 +223,11 @@
   @override
   void visitLibrary(Library node) {
     // Issue(http://dartbug.com/32530)
-    if (skipPlatform && node.importUri.scheme == 'dart') {
+    // 'dart:test' is used in the unit tests and isn't an actual part of the
+    // platform.
+    if (skipPlatform &&
+        node.importUri.scheme == 'dart' &&
+        node.importUri.path != 'test') {
       return;
     }
 
@@ -260,6 +267,22 @@
   void visitProcedure(Procedure node) {
     enterTreeNode(node);
     fileUri = checkLocation(node, node.name.text, node.fileUri);
+
+    // TODO(dmitryas): Investigate why some redirecting factory bodies retain
+    // the shape, but aren't of the RedirectingFactoryBody type.
+    bool hasBody = isRedirectingFactory(node) ||
+        RedirectingFactoryBody.hasRedirectingFactoryBodyShape(node);
+    bool hasFlag = node.isRedirectingFactoryConstructor;
+    if (hasBody != hasFlag) {
+      String hasBodyString = hasBody ? "has" : "doesn't have";
+      String hasFlagString = hasFlag ? "has" : "doesn't have";
+      problem(
+          node,
+          "Procedure '${node.name}' ${hasBodyString} a body "
+          "of a redirecting factory, but ${hasFlagString} the "
+          "'isRedirectingFactoryConstructor' bit set.");
+    }
+
     super.visitProcedure(node);
     exitTreeNode(node);
   }
@@ -422,7 +445,11 @@
 
   @override
   visitLibrary(Library node) {
-    if (skipPlatform && node.importUri.scheme == 'dart') {
+    // 'dart:test' is used in the unit tests and isn't an actual part of the
+    // platform.
+    if (skipPlatform &&
+        node.importUri.scheme == 'dart' &&
+        node.importUri.path != "test") {
       return;
     }
 
diff --git a/pkg/front_end/test/desugar_test.dart b/pkg/front_end/test/desugar_test.dart
index 3ebad1e..7aa716d 100644
--- a/pkg/front_end/test/desugar_test.dart
+++ b/pkg/front_end/test/desugar_test.dart
@@ -54,13 +54,11 @@
   var component = new ir.Component();
   new BinaryBuilder(new File.fromUri(componentUri).readAsBytesSync())
       .readComponent(component);
-  checkIsRedirectingFactory(component, 'collection', 'HashMap', 'identity',
-      isPatch: true);
+  checkIsRedirectingFactory(component, 'collection', 'HashMap', 'identity');
 }
 
 void checkIsRedirectingFactory(ir.Component component, String uriPath,
-    String className, String constructorName,
-    {bool isPatch: false}) {
+    String className, String constructorName) {
   var lib =
       component.libraries.firstWhere((l) => l.importUri.path.endsWith(uriPath));
   var cls = lib.classes.firstWhere((c) => c.name == className);
@@ -69,8 +67,7 @@
   Expect.isTrue(
       member.kind == ir.ProcedureKind.Factory, "$member is not a factory");
   Expect.isTrue(api.isRedirectingFactory(member));
-  // TODO: this should always be true. Issue #33495
-  Expect.equals(!isPatch, member.isRedirectingFactoryConstructor);
+  Expect.isTrue(member.isRedirectingFactoryConstructor);
 }
 
 const aSource = '''
diff --git a/pkg/front_end/testcases/general/issue45101/libraries.json b/pkg/front_end/testcases/general/issue45101/libraries.json
new file mode 100644
index 0000000..154c73c
--- /dev/null
+++ b/pkg/front_end/testcases/general/issue45101/libraries.json
@@ -0,0 +1,12 @@
+{
+  "none": {
+    "libraries": {
+      "test": {
+        "patches": [
+          "patch_lib.dart"
+        ],
+        "uri": "origin_lib.dart"
+      }
+    }
+  }
+}
diff --git a/pkg/front_end/testcases/general/issue45101/main.dart b/pkg/front_end/testcases/general/issue45101/main.dart
new file mode 100644
index 0000000..171b59b
--- /dev/null
+++ b/pkg/front_end/testcases/general/issue45101/main.dart
@@ -0,0 +1,7 @@
+// Copyright (c) 2021, 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.
+// @dart=2.9
+import 'dart:test';
+
+main() {}
diff --git a/pkg/front_end/testcases/general/issue45101/main.dart.textual_outline.expect b/pkg/front_end/testcases/general/issue45101/main.dart.textual_outline.expect
new file mode 100644
index 0000000..9ededd9
--- /dev/null
+++ b/pkg/front_end/testcases/general/issue45101/main.dart.textual_outline.expect
@@ -0,0 +1,4 @@
+// @dart = 2.9
+import 'dart:test';
+
+main() {}
diff --git a/pkg/front_end/testcases/general/issue45101/main.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/general/issue45101/main.dart.textual_outline_modelled.expect
new file mode 100644
index 0000000..9ededd9
--- /dev/null
+++ b/pkg/front_end/testcases/general/issue45101/main.dart.textual_outline_modelled.expect
@@ -0,0 +1,4 @@
+// @dart = 2.9
+import 'dart:test';
+
+main() {}
diff --git a/pkg/front_end/testcases/general/issue45101/main.dart.weak.expect b/pkg/front_end/testcases/general/issue45101/main.dart.weak.expect
new file mode 100644
index 0000000..8c7b289
--- /dev/null
+++ b/pkg/front_end/testcases/general/issue45101/main.dart.weak.expect
@@ -0,0 +1,60 @@
+library;
+import self as self;
+
+import "dart:test";
+
+static method main() → dynamic {}
+
+library;
+import self as self2;
+import "dart:core" as core;
+import "dart:_internal" as _in;
+
+import "dart:_internal";
+
+class _ArraySize<T extends core::Object* = dynamic> extends core::Object implements self2::Array<self2::_ArraySize::T*> /*hasConstConstructor*/  { // from org-dartlang-testcase:///patch_lib.dart
+  final field core::int* foo;
+  const constructor •(core::int* foo) → self2::_ArraySize<self2::_ArraySize::T*>*
+    : self2::_ArraySize::foo = foo, super core::Object::•()
+    ;
+  abstract member-signature get _identityHashCode() → core::int*; -> core::Object::_identityHashCode
+  abstract member-signature method _instanceOf(dynamic instantiatorTypeArguments, dynamic functionTypeArguments, dynamic type) → core::bool*; -> core::Object::_instanceOf
+  abstract member-signature method _simpleInstanceOf(dynamic type) → core::bool*; -> core::Object::_simpleInstanceOf
+  abstract member-signature method _simpleInstanceOfTrue(dynamic type) → core::bool*; -> core::Object::_simpleInstanceOfTrue
+  abstract member-signature method _simpleInstanceOfFalse(dynamic type) → core::bool*; -> core::Object::_simpleInstanceOfFalse
+  abstract member-signature operator ==(dynamic other) → core::bool*; -> core::Object::==
+  abstract member-signature get hashCode() → core::int*; -> core::Object::hashCode
+  abstract member-signature method toString() → core::String*; -> core::Object::toString
+  abstract member-signature method noSuchMethod(core::Invocation* invocation) → dynamic; -> core::Object::noSuchMethod
+  abstract member-signature get runtimeType() → core::Type*; -> core::Object::runtimeType
+}
+@#C1
+@#C4
+class Array<T extends core::Object* = dynamic> extends core::Object {
+  static final field dynamic _redirecting# = <dynamic>[self2::Array::•];
+  @#C1
+  static factory /* from org-dartlang-testcase:///patch_lib.dart */ •<T extends core::Object* = dynamic>(core::int* foo) → self2::Array<self2::Array::•::T*>*
+    let dynamic #redirecting_factory = self2::_ArraySize::• in let self2::Array::•::T* #typeArg0 = null in invalid-expression;
+  abstract member-signature get _identityHashCode() → core::int*; -> core::Object::_identityHashCode
+  abstract member-signature method _instanceOf(dynamic instantiatorTypeArguments, dynamic functionTypeArguments, dynamic type) → core::bool*; -> core::Object::_instanceOf
+  abstract member-signature method _simpleInstanceOf(dynamic type) → core::bool*; -> core::Object::_simpleInstanceOf
+  abstract member-signature method _simpleInstanceOfTrue(dynamic type) → core::bool*; -> core::Object::_simpleInstanceOfTrue
+  abstract member-signature method _simpleInstanceOfFalse(dynamic type) → core::bool*; -> core::Object::_simpleInstanceOfFalse
+  abstract member-signature operator ==(dynamic other) → core::bool*; -> core::Object::==
+  abstract member-signature get hashCode() → core::int*; -> core::Object::hashCode
+  abstract member-signature method toString() → core::String*; -> core::Object::toString
+  abstract member-signature method noSuchMethod(core::Invocation* invocation) → dynamic; -> core::Object::noSuchMethod
+  abstract member-signature get runtimeType() → core::Type*; -> core::Object::runtimeType
+}
+
+constants  {
+  #C1 = _in::_Patch {}
+  #C2 = "vm:entry-point"
+  #C3 = null
+  #C4 = core::pragma {name:#C2, options:#C3}
+}
+
+
+Constructor coverage from constants:
+org-dartlang-testcase:///origin_lib.dart:
+- pragma._ (from org-dartlang-sdk:///sdk/lib/core/annotations.dart:188:9)
diff --git a/pkg/front_end/testcases/general/issue45101/main.dart.weak.outline.expect b/pkg/front_end/testcases/general/issue45101/main.dart.weak.outline.expect
new file mode 100644
index 0000000..c56c375
--- /dev/null
+++ b/pkg/front_end/testcases/general/issue45101/main.dart.weak.outline.expect
@@ -0,0 +1,56 @@
+library;
+import self as self;
+
+import "dart:test";
+
+static method main() → dynamic
+  ;
+
+library;
+import self as self2;
+import "dart:core" as core;
+import "dart:_internal" as _in;
+
+import "dart:_internal";
+
+class _ArraySize<T extends core::Object* = dynamic> extends core::Object implements self2::Array<self2::_ArraySize::T*> /*hasConstConstructor*/  { // from org-dartlang-testcase:///patch_lib.dart
+  final field core::int* foo;
+  const constructor •(core::int* foo) → self2::_ArraySize<self2::_ArraySize::T*>*
+    : self2::_ArraySize::foo = foo, super core::Object::•()
+    ;
+  abstract member-signature get _identityHashCode() → core::int*; -> core::Object::_identityHashCode
+  abstract member-signature method _instanceOf(dynamic instantiatorTypeArguments, dynamic functionTypeArguments, dynamic type) → core::bool*; -> core::Object::_instanceOf
+  abstract member-signature method _simpleInstanceOf(dynamic type) → core::bool*; -> core::Object::_simpleInstanceOf
+  abstract member-signature method _simpleInstanceOfTrue(dynamic type) → core::bool*; -> core::Object::_simpleInstanceOfTrue
+  abstract member-signature method _simpleInstanceOfFalse(dynamic type) → core::bool*; -> core::Object::_simpleInstanceOfFalse
+  abstract member-signature operator ==(dynamic other) → core::bool*; -> core::Object::==
+  abstract member-signature get hashCode() → core::int*; -> core::Object::hashCode
+  abstract member-signature method toString() → core::String*; -> core::Object::toString
+  abstract member-signature method noSuchMethod(core::Invocation* invocation) → dynamic; -> core::Object::noSuchMethod
+  abstract member-signature get runtimeType() → core::Type*; -> core::Object::runtimeType
+}
+@_in::patch
+@core::pragma::_("vm:entry-point")
+class Array<T extends core::Object* = dynamic> extends core::Object {
+  static final field dynamic _redirecting# = <dynamic>[self2::Array::•];
+  @_in::patch
+  external static factory •<T extends core::Object* = dynamic>(core::int* foo) → self2::Array<self2::Array::•::T*>*
+    let dynamic #redirecting_factory = self2::_ArraySize::• in let self2::Array::•::T* #typeArg0 = null in invalid-expression;
+  abstract member-signature get _identityHashCode() → core::int*; -> core::Object::_identityHashCode
+  abstract member-signature method _instanceOf(dynamic instantiatorTypeArguments, dynamic functionTypeArguments, dynamic type) → core::bool*; -> core::Object::_instanceOf
+  abstract member-signature method _simpleInstanceOf(dynamic type) → core::bool*; -> core::Object::_simpleInstanceOf
+  abstract member-signature method _simpleInstanceOfTrue(dynamic type) → core::bool*; -> core::Object::_simpleInstanceOfTrue
+  abstract member-signature method _simpleInstanceOfFalse(dynamic type) → core::bool*; -> core::Object::_simpleInstanceOfFalse
+  abstract member-signature operator ==(dynamic other) → core::bool*; -> core::Object::==
+  abstract member-signature get hashCode() → core::int*; -> core::Object::hashCode
+  abstract member-signature method toString() → core::String*; -> core::Object::toString
+  abstract member-signature method noSuchMethod(core::Invocation* invocation) → dynamic; -> core::Object::noSuchMethod
+  abstract member-signature get runtimeType() → core::Type*; -> core::Object::runtimeType
+}
+
+
+Extra constant evaluation status:
+Evaluated: StaticGet @ org-dartlang-testcase:///origin_lib.dart:10:1 -> InstanceConstant(const _Patch{})
+Evaluated: ConstructorInvocation @ (unknown position in org-dartlang-testcase:///origin_lib.dart) -> InstanceConstant(const pragma{pragma.name: "vm:entry-point", pragma.options: null})
+Evaluated: StaticGet @ (unknown position in org-dartlang-testcase:///origin_lib.dart) -> InstanceConstant(const _Patch{})
+Extra constant evaluation: evaluated: 9, effectively constant: 3
diff --git a/pkg/front_end/testcases/general/issue45101/main.dart.weak.transformed.expect b/pkg/front_end/testcases/general/issue45101/main.dart.weak.transformed.expect
new file mode 100644
index 0000000..8c7b289
--- /dev/null
+++ b/pkg/front_end/testcases/general/issue45101/main.dart.weak.transformed.expect
@@ -0,0 +1,60 @@
+library;
+import self as self;
+
+import "dart:test";
+
+static method main() → dynamic {}
+
+library;
+import self as self2;
+import "dart:core" as core;
+import "dart:_internal" as _in;
+
+import "dart:_internal";
+
+class _ArraySize<T extends core::Object* = dynamic> extends core::Object implements self2::Array<self2::_ArraySize::T*> /*hasConstConstructor*/  { // from org-dartlang-testcase:///patch_lib.dart
+  final field core::int* foo;
+  const constructor •(core::int* foo) → self2::_ArraySize<self2::_ArraySize::T*>*
+    : self2::_ArraySize::foo = foo, super core::Object::•()
+    ;
+  abstract member-signature get _identityHashCode() → core::int*; -> core::Object::_identityHashCode
+  abstract member-signature method _instanceOf(dynamic instantiatorTypeArguments, dynamic functionTypeArguments, dynamic type) → core::bool*; -> core::Object::_instanceOf
+  abstract member-signature method _simpleInstanceOf(dynamic type) → core::bool*; -> core::Object::_simpleInstanceOf
+  abstract member-signature method _simpleInstanceOfTrue(dynamic type) → core::bool*; -> core::Object::_simpleInstanceOfTrue
+  abstract member-signature method _simpleInstanceOfFalse(dynamic type) → core::bool*; -> core::Object::_simpleInstanceOfFalse
+  abstract member-signature operator ==(dynamic other) → core::bool*; -> core::Object::==
+  abstract member-signature get hashCode() → core::int*; -> core::Object::hashCode
+  abstract member-signature method toString() → core::String*; -> core::Object::toString
+  abstract member-signature method noSuchMethod(core::Invocation* invocation) → dynamic; -> core::Object::noSuchMethod
+  abstract member-signature get runtimeType() → core::Type*; -> core::Object::runtimeType
+}
+@#C1
+@#C4
+class Array<T extends core::Object* = dynamic> extends core::Object {
+  static final field dynamic _redirecting# = <dynamic>[self2::Array::•];
+  @#C1
+  static factory /* from org-dartlang-testcase:///patch_lib.dart */ •<T extends core::Object* = dynamic>(core::int* foo) → self2::Array<self2::Array::•::T*>*
+    let dynamic #redirecting_factory = self2::_ArraySize::• in let self2::Array::•::T* #typeArg0 = null in invalid-expression;
+  abstract member-signature get _identityHashCode() → core::int*; -> core::Object::_identityHashCode
+  abstract member-signature method _instanceOf(dynamic instantiatorTypeArguments, dynamic functionTypeArguments, dynamic type) → core::bool*; -> core::Object::_instanceOf
+  abstract member-signature method _simpleInstanceOf(dynamic type) → core::bool*; -> core::Object::_simpleInstanceOf
+  abstract member-signature method _simpleInstanceOfTrue(dynamic type) → core::bool*; -> core::Object::_simpleInstanceOfTrue
+  abstract member-signature method _simpleInstanceOfFalse(dynamic type) → core::bool*; -> core::Object::_simpleInstanceOfFalse
+  abstract member-signature operator ==(dynamic other) → core::bool*; -> core::Object::==
+  abstract member-signature get hashCode() → core::int*; -> core::Object::hashCode
+  abstract member-signature method toString() → core::String*; -> core::Object::toString
+  abstract member-signature method noSuchMethod(core::Invocation* invocation) → dynamic; -> core::Object::noSuchMethod
+  abstract member-signature get runtimeType() → core::Type*; -> core::Object::runtimeType
+}
+
+constants  {
+  #C1 = _in::_Patch {}
+  #C2 = "vm:entry-point"
+  #C3 = null
+  #C4 = core::pragma {name:#C2, options:#C3}
+}
+
+
+Constructor coverage from constants:
+org-dartlang-testcase:///origin_lib.dart:
+- pragma._ (from org-dartlang-sdk:///sdk/lib/core/annotations.dart:188:9)
diff --git a/pkg/front_end/testcases/general/issue45101/origin_lib.dart b/pkg/front_end/testcases/general/issue45101/origin_lib.dart
new file mode 100644
index 0000000..2aabe5a
--- /dev/null
+++ b/pkg/front_end/testcases/general/issue45101/origin_lib.dart
@@ -0,0 +1,8 @@
+// Copyright (c) 2021, 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.
+// @dart=2.9
+
+class Array<T> {
+  external const factory Array(int foo);
+}
diff --git a/pkg/front_end/testcases/general/issue45101/patch_lib.dart b/pkg/front_end/testcases/general/issue45101/patch_lib.dart
new file mode 100644
index 0000000..b5a1fd5
--- /dev/null
+++ b/pkg/front_end/testcases/general/issue45101/patch_lib.dart
@@ -0,0 +1,21 @@
+// Copyright (c) 2021, 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.
+// @dart=2.9
+// ignore: import_internal_library
+import 'dart:_internal';
+
+@patch
+@pragma("vm:entry-point")
+class Array<T> {
+  // ...
+
+  @patch
+  const factory Array(int foo) = _ArraySize<T>;
+}
+
+class _ArraySize<T> implements Array<T> {
+  final int foo;
+
+  const _ArraySize(this.foo);
+}