Advanced tracking of field initialization

Change-Id: I8c8edb517bc3a3805ac0d72388f4e5c7fc34247f
Reviewed-on: https://dart-review.googlesource.com/c/93680
Commit-Queue: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Sigmund Cherem <sigmund@google.com>
diff --git a/pkg/compiler/lib/src/js_backend/field_analysis.dart b/pkg/compiler/lib/src/js_backend/field_analysis.dart
index 8489a25..33cb6c5 100644
--- a/pkg/compiler/lib/src/js_backend/field_analysis.dart
+++ b/pkg/compiler/lib/src/js_backend/field_analysis.dart
@@ -37,7 +37,7 @@
 class KFieldAnalysis implements FieldAnalysis {
   final KernelToElementMap _elementMap;
 
-  final Map<KField, AllocatorData> _fixedInitializers = {};
+  final Map<KClass, ClassData> _classData = {};
 
   KFieldAnalysis(KernelFrontEndStrategy kernelStrategy)
       : _elementMap = kernelStrategy.elementMap;
@@ -47,22 +47,27 @@
   void registerInstantiatedClass(KClass class_) {
     ir.Class classNode = _elementMap.getClassNode(class_);
 
-    Map<ir.Field, AllocatorData> fieldData = {};
+    List<KConstructor> constructors = [];
+    Map<KField, AllocatorData> fieldData = {};
     for (ir.Field field in classNode.fields) {
       if (!field.isInstanceMember) continue;
+
+      FieldEntity fieldElement = _elementMap.getField(field);
       ir.Expression expression = field.initializer;
       ConstantValue value = _elementMap.getConstantValue(expression,
           requireConstant: false, implicitNull: true);
       if (value != null && value.isConstant) {
-        fieldData[field] = new AllocatorData(value);
+        fieldData[fieldElement] = new AllocatorData(value);
       }
     }
 
     for (ir.Constructor constructor in classNode.constructors) {
       KConstructor constructorElement = _elementMap.getConstructor(constructor);
+      constructors.add(constructorElement);
       for (ir.Initializer initializer in constructor.initializers) {
         if (initializer is ir.FieldInitializer) {
-          AllocatorData data = fieldData[initializer.field];
+          AllocatorData data =
+              fieldData[_elementMap.getField(initializer.field)];
           if (data == null) {
             // TODO(johnniwinther): Support initializers with side-effects?
 
@@ -111,13 +116,19 @@
       }
     }
 
-    fieldData.forEach((ir.Field fieldNode, AllocatorData data) {
-      _fixedInitializers[_elementMap.getField(fieldNode)] = data;
-    });
+    _classData[class_] = new ClassData(constructors, fieldData);
   }
 
-  AllocatorData getFixedInitializerForTesting(KField field) =>
-      _fixedInitializers[field];
+  AllocatorData getFixedInitializerForTesting(KField field) {
+    return _classData[field.enclosingClass].fieldData[field];
+  }
+}
+
+class ClassData {
+  final List<KConstructor> constructors;
+  final Map<KField, AllocatorData> fieldData;
+
+  ClassData(this.constructors, this.fieldData);
 }
 
 class AllocatorData {
@@ -159,7 +170,7 @@
         name = null,
         value = null;
 
-  String shortText() {
+  String get shortText {
     switch (kind) {
       case InitializerKind.direct:
         return value.toStructuredText();
@@ -172,6 +183,8 @@
     }
     throw new UnsupportedError('Unexpected kind $kind');
   }
+
+  String toString() => shortText;
 }
 
 class JFieldAnalysis implements FieldAnalysis {
@@ -223,41 +236,103 @@
           !closedWorld.nativeData.isNativeMember(field);
     }
 
-    closedWorld.fieldAnalysis._fixedInitializers
-        .forEach((KField kField, AllocatorData data) {
-      JField jField = map.toBackendMember(kField);
-      if (jField == null) {
-        return;
-      }
-
-      // TODO(johnniwinther): Should elided static fields be removed from the
-      // J model? Static setters might still assign to them.
-
-      MemberUsage memberUsage = closedWorld.liveMemberUsage[kField];
-      if (!memberUsage.hasRead) {
-        if (canBeElided(kField)) {
-          elidedFields.add(jField);
+    closedWorld.fieldAnalysis._classData
+        .forEach((ClassEntity cls, ClassData classData) {
+      classData.fieldData.forEach((KField kField, AllocatorData data) {
+        JField jField = map.toBackendMember(kField);
+        if (jField == null) {
+          return;
         }
-      } else {
-        // TODO(johnniwinther): Use liveness of constructors and elided optional
-        // parameters to recognize more constant initializers.
-        if (data.initialValue != null && data.initializers.isEmpty) {
-          ConstantValue value = map.toBackendConstant(data.initialValue);
-          assert(value != null);
-          if (!memberUsage.hasWrite && canBeElided(kField)) {
+
+        // TODO(johnniwinther): Should elided static fields be removed from the
+        // J model? Static setters might still assign to them.
+
+        MemberUsage memberUsage = closedWorld.liveMemberUsage[kField];
+        if (!memberUsage.hasRead) {
+          if (canBeElided(kField)) {
             elidedFields.add(jField);
-            effectivelyConstantFields[jField] = value;
-          } else if (value.isNull ||
-              value.isInt ||
-              value.isBool ||
-              value.isString) {
-            // TODO(johnniwinther,sra): Support non-primitive constants in
-            // allocators when it does cause allocators to deoptimized because
-            // of deferred loading.
-            fixedInitializers[jField] = value;
+          }
+        } else {
+          // TODO(johnniwinther): Use liveness of constructors and elided optional
+          // parameters to recognize more constant initializers.
+          if (data.initialValue != null) {
+            ConstantValue initialValue;
+            bool isTooComplex = false;
+
+            void includeInitialValue(ConstantValue value) {
+              if (isTooComplex) return;
+              if (initialValue == null) {
+                initialValue = value;
+              } else if (initialValue != value) {
+                initialValue = null;
+                isTooComplex = true;
+              }
+            }
+
+            bool inAllConstructors = true;
+            for (KConstructor constructor in classData.constructors) {
+              if (isTooComplex) {
+                break;
+              }
+
+              MemberUsage constructorUsage =
+                  closedWorld.liveMemberUsage[constructor];
+              if (constructorUsage == null) return;
+              ParameterStructure invokedParameters =
+                  constructorUsage.invokedParameters;
+
+              Initializer initializer = data.initializers[constructor];
+              if (initializer == null) {
+                inAllConstructors = false;
+              } else {
+                switch (initializer.kind) {
+                  case InitializerKind.direct:
+                    includeInitialValue(initializer.value);
+                    break;
+                  case InitializerKind.positional:
+                    if (initializer.index >=
+                        invokedParameters.positionalParameters) {
+                      includeInitialValue(initializer.value);
+                    } else {
+                      isTooComplex = true;
+                    }
+                    break;
+                  case InitializerKind.named:
+                    if (!invokedParameters.namedParameters
+                        .contains(initializer.name)) {
+                      includeInitialValue(initializer.value);
+                    } else {
+                      isTooComplex = true;
+                    }
+                    break;
+                  case InitializerKind.complex:
+                    isTooComplex = true;
+                    break;
+                }
+              }
+            }
+            if (!inAllConstructors) {
+              includeInitialValue(data.initialValue);
+            }
+            if (!isTooComplex && initialValue != null) {
+              ConstantValue value = map.toBackendConstant(initialValue);
+              assert(value != null);
+              if (!memberUsage.hasWrite && canBeElided(kField)) {
+                elidedFields.add(jField);
+                effectivelyConstantFields[jField] = value;
+              } else if (value.isNull ||
+                  value.isInt ||
+                  value.isBool ||
+                  value.isString) {
+                // TODO(johnniwinther,sra): Support non-primitive constants in
+                // allocators when it does cause allocators to deoptimized
+                // because of deferred loading.
+                fixedInitializers[jField] = value;
+              }
+            }
           }
         }
-      }
+      });
     });
 
     // TODO(johnniwinther): Recognize effectively constant top level/static
diff --git a/tests/compiler/dart2js/codegen/expect_annotations2_test.dart b/tests/compiler/dart2js/codegen/expect_annotations2_test.dart
index 5b0ba5c..803520e 100644
--- a/tests/compiler/dart2js/codegen/expect_annotations2_test.dart
+++ b/tests/compiler/dart2js/codegen/expect_annotations2_test.dart
@@ -5,6 +5,7 @@
 import "package:expect/expect.dart";
 import "package:async_helper/async_helper.dart";
 import 'package:compiler/compiler_new.dart';
+import 'package:compiler/src/commandline_options.dart';
 import '../helpers/memory_compiler.dart';
 
 const MEMORY_SOURCE_FILES = const {
@@ -15,6 +16,7 @@
         foo(y) => 49912344 + y;
 
         class A {
+          @pragma('dart2js:noElision')
           var field;
 
           @NoInline()
@@ -39,7 +41,9 @@
   runTest() async {
     OutputCollector collector = new OutputCollector();
     await runCompiler(
-        memorySourceFiles: MEMORY_SOURCE_FILES, outputProvider: collector);
+        memorySourceFiles: MEMORY_SOURCE_FILES,
+        outputProvider: collector,
+        options: [Flags.testMode]);
     // Simply check that the constants of the small functions are still in the
     // output, and that we don't see the result of constant folding.
     String jsOutput = collector.getOutput('', OutputType.js);
diff --git a/tests/compiler/dart2js/codegen/model_data/effectively_constant_state.dart b/tests/compiler/dart2js/codegen/model_data/effectively_constant_state.dart
new file mode 100644
index 0000000..fc33c4b
--- /dev/null
+++ b/tests/compiler/dart2js/codegen/model_data/effectively_constant_state.dart
@@ -0,0 +1,92 @@
+// Copyright (c) 2019, 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.
+
+enum Enum {
+  a,
+  b,
+  c,
+}
+
+/*element: tester1:params=0*/
+@pragma('dart2js:noInline')
+tester1() {}
+
+/*element: tester2:params=0*/
+@pragma('dart2js:noInline')
+tester2() {}
+
+/*element: tester3:params=0*/
+@pragma('dart2js:noInline')
+tester3() {}
+
+class Class {
+  /*element: Class.state1:elided*/
+  final int state1;
+
+  /*element: Class.state2:elided*/
+  final Enum state2;
+
+  Class({this.state1: 1, this.state2: Enum.c});
+
+  /*element: Class.method1a:calls=[tester2(0)],params=0*/
+  @pragma('dart2js:noInline')
+  method1a() {
+    if (state1 == 0) {
+      return tester1();
+    } else if (state1 == 1) {
+      return tester2();
+    } else if (state1 == 2) {
+      return tester3();
+    }
+  }
+
+  // TODO(johnniwinther): Inline switch cases with constant expressions.
+  /*element: Class.method1b:calls=[tester2(0)],params=0,switch*/
+  @pragma('dart2js:noInline')
+  method1b() {
+    switch (state1) {
+      case 0:
+        return tester1();
+      case 1:
+        return tester2();
+      case 2:
+        return tester3();
+    }
+  }
+
+  /*element: Class.method2a:calls=[tester3(0)],params=0*/
+  @pragma('dart2js:noInline')
+  method2a() {
+    if (state2 == Enum.a) {
+      return tester1();
+    } else if (state2 == Enum.b) {
+      return tester2();
+    } else if (state2 == Enum.c) {
+      return tester3();
+    }
+  }
+
+  /*element: Class.method2b:calls=[tester1(0),tester2(0),tester3(0)],params=0,switch*/
+  @pragma('dart2js:noInline')
+  method2b() {
+    // TODO(johnniwinther): Eliminate dead code in enum switch.
+    switch (state2) {
+      case Enum.a:
+        return tester1();
+      case Enum.b:
+        return tester2();
+      case Enum.c:
+        return tester3();
+    }
+  }
+}
+
+/*element: main:calls=*,params=0*/
+main() {
+  var c = new Class();
+  c.method1a();
+  c.method1b();
+  c.method2a();
+  c.method2b();
+}
diff --git a/tests/compiler/dart2js/codegen/model_test.dart b/tests/compiler/dart2js/codegen/model_test.dart
index 974ca4a..7e1262d 100644
--- a/tests/compiler/dart2js/codegen/model_test.dart
+++ b/tests/compiler/dart2js/codegen/model_test.dart
@@ -64,6 +64,7 @@
   static const String assignment = 'assign';
   static const String isLazy = 'lazy';
   static const String propertyAccess = 'access';
+  static const String switchCase = 'switch';
 }
 
 /// AST visitor for computing inference data for a member.
@@ -222,6 +223,11 @@
             features.addElement(Tags.propertyAccess, '${name}');
           }
         });
+
+        forEachNode(code, onSwitch: (js.Switch node) {
+          features.add(Tags.switchCase);
+        });
+
         return features;
       }
     }
diff --git a/tests/compiler/dart2js/field_analysis/jdata/effectively_constant_state.dart b/tests/compiler/dart2js/field_analysis/jdata/effectively_constant_state.dart
new file mode 100644
index 0000000..84f532e
--- /dev/null
+++ b/tests/compiler/dart2js/field_analysis/jdata/effectively_constant_state.dart
@@ -0,0 +1,82 @@
+// Copyright (c) 2019, 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.
+
+enum Enum {
+  a,
+  b,
+  c,
+}
+
+@pragma('dart2js:noInline')
+tester1() {}
+
+@pragma('dart2js:noInline')
+tester2() {}
+
+@pragma('dart2js:noInline')
+tester3() {}
+
+class Class {
+  /*element: Class.state1:constant=IntConstant(1)*/
+  final int state1;
+
+  /*element: Class.state2:constant=ConstructedConstant(Enum(_name=StringConstant("Enum.c"),index=IntConstant(2)))*/
+  final Enum state2;
+
+  Class({this.state1: 1, this.state2: Enum.c});
+
+  @pragma('dart2js:noInline')
+  method1a() {
+    if (state1 == 0) {
+      return tester1();
+    } else if (state1 == 1) {
+      return tester2();
+    } else if (state1 == 2) {
+      return tester3();
+    }
+  }
+
+  @pragma('dart2js:noInline')
+  method1b() {
+    switch (state1) {
+      case 0:
+        return tester1();
+      case 1:
+        return tester2();
+      case 2:
+        return tester3();
+    }
+  }
+
+  @pragma('dart2js:noInline')
+  method2a() {
+    if (state2 == Enum.a) {
+      return tester1();
+    } else if (state2 == Enum.b) {
+      return tester2();
+    } else if (state2 == Enum.c) {
+      return tester3();
+    }
+  }
+
+  @pragma('dart2js:noInline')
+  method2b() {
+    switch (state2) {
+      case Enum.a:
+        return tester1();
+      case Enum.b:
+        return tester2();
+      case Enum.c:
+        return tester3();
+    }
+  }
+}
+
+main() {
+  var c = new Class();
+  c.method1a();
+  c.method1b();
+  c.method2a();
+  c.method2b();
+}
diff --git a/tests/compiler/dart2js/field_analysis/jdata/multi_initializers.dart b/tests/compiler/dart2js/field_analysis/jdata/multi_initializers.dart
index d72731d..f7d423f 100644
--- a/tests/compiler/dart2js/field_analysis/jdata/multi_initializers.dart
+++ b/tests/compiler/dart2js/field_analysis/jdata/multi_initializers.dart
@@ -4,21 +4,30 @@
 
 main() {
   var c = new Class1.a();
+  c.field3a = null;
   c.field4a = null;
+  c.field5a = null;
   new Class1.b();
 
   print(c.field1);
   print(c.field2);
-  print(c.field3);
+  print(c.field3a);
+  print(c.field3b);
   print(c.field4a);
   print(c.field4b);
-  print(c.field5);
+  print(c.field5a);
+  print(c.field5b);
 }
 
 class Class1 {
   var field1 = 0;
   var field2;
-  var field3;
+
+  /*element: Class1.field3a:initial=IntConstant(3)*/
+  var field3a;
+
+  /*element: Class1.field3b:constant=IntConstant(3)*/
+  var field3b;
 
   /*element: Class1.field4a:initial=IntConstant(4)*/
   var field4a = 4;
@@ -26,16 +35,24 @@
   /*element: Class1.field4b:constant=IntConstant(4)*/
   var field4b = 4;
 
-  var field5 = 5;
+  /*element: Class1.field5a:initial=IntConstant(5)*/
+  var field5a = 5;
+
+  /*element: Class1.field5b:constant=IntConstant(5)*/
+  var field5b = 5;
 
   Class1.a()
       : field1 = 1,
         field2 = 1,
-        field3 = 3,
-        field5 = 5;
+        field3a = 3,
+        field3b = 3,
+        field5a = 5,
+        field5b = 5;
 
   Class1.b()
       : field2 = 2,
-        field3 = 3,
-        field5 = 5;
+        field3a = 3,
+        field3b = 3,
+        field5a = 5,
+        field5b = 5;
 }
diff --git a/tests/compiler/dart2js/field_analysis/jdata/optional_parameters.dart b/tests/compiler/dart2js/field_analysis/jdata/optional_parameters.dart
index cad9372..2f63ffb 100644
--- a/tests/compiler/dart2js/field_analysis/jdata/optional_parameters.dart
+++ b/tests/compiler/dart2js/field_analysis/jdata/optional_parameters.dart
@@ -3,24 +3,85 @@
 // BSD-style license that can be found in the LICENSE file.
 
 main() {
-  new Class1(0);
-  new Class1(0, 1);
-  new Class2(0);
-  new Class2(0, field2: 1);
+  var c1a = new Class1a(0);
+  new Class1a(0, 1);
+  c1a.field1 = null;
+  c1a.field2 = null;
+  c1a.field3 = null;
+  print(c1a.field1);
+  print(c1a.field2);
+  print(c1a.field3);
+
+  var c1b = new Class1b(0);
+  new Class1b(0, 1);
+  print(c1b.field1);
+  print(c1b.field2);
+  print(c1b.field3);
+
+  var c2a = new Class2a(0);
+  new Class2a(0, field2: 1);
+  c2a.field1 = null;
+  c2a.field2 = null;
+  c2a.field3 = null;
+  print(c2a.field1);
+  print(c2a.field2);
+  print(c2a.field3);
+
+  var c2b = new Class2b(0);
+  new Class2b(0, field2: 1);
+  print(c2b.field1);
+  print(c2b.field2);
+  print(c2b.field3);
 }
 
-class Class1 {
+class Class1a {
+  /*element: Class1a.field1:*/
   var field1;
+
+  /*element: Class1a.field2:*/
   var field2;
+
+  /*element: Class1a.field3:initial=IntConstant(3)*/
   var field3;
 
-  Class1(this.field1, [this.field2 = 2, this.field3 = 3]);
+  Class1a(this.field1, [this.field2 = 2, this.field3 = 3]);
 }
 
-class Class2 {
+class Class1b {
+  /*element: Class1b.field1:*/
   var field1;
+
+  /*element: Class1b.field2:*/
   var field2;
+
+  /*element: Class1b.field3:constant=IntConstant(3)*/
   var field3;
 
-  Class2(this.field1, {this.field2 = 2, this.field3 = 3});
+  Class1b(this.field1, [this.field2 = 2, this.field3 = 3]);
+}
+
+class Class2a {
+  /*element: Class2a.field1:*/
+  var field1;
+
+  /*element: Class2a.field2:*/
+  var field2;
+
+  /*element: Class2a.field3:initial=IntConstant(3)*/
+  var field3;
+
+  Class2a(this.field1, {this.field2 = 2, this.field3 = 3});
+}
+
+class Class2b {
+  /*element: Class2b.field1:*/
+  var field1;
+
+  /*element: Class2b.field2:*/
+  var field2;
+
+  /*element: Class2b.field3:constant=IntConstant(3)*/
+  var field3;
+
+  Class2b(this.field1, {this.field2 = 2, this.field3 = 3});
 }
diff --git a/tests/compiler/dart2js/field_analysis/jdata/simple_initializers.dart b/tests/compiler/dart2js/field_analysis/jdata/simple_initializers.dart
index 0a5a612..a18580f 100644
--- a/tests/compiler/dart2js/field_analysis/jdata/simple_initializers.dart
+++ b/tests/compiler/dart2js/field_analysis/jdata/simple_initializers.dart
@@ -214,94 +214,94 @@
 }
 
 class Class2 {
-  /*element: Class2.field1a:*/
+  /*element: Class2.field1a:initial=NullConstant*/
   var field1a;
 
-  /*element: Class2.field1b:*/
+  /*element: Class2.field1b:constant=NullConstant*/
   var field1b;
 
-  /*element: Class2.field2a:*/
+  /*element: Class2.field2a:initial=BoolConstant(true)*/
   var field2a;
 
-  /*element: Class2.field2b:*/
+  /*element: Class2.field2b:constant=BoolConstant(true)*/
   var field2b;
 
-  /*element: Class2.field3a:*/
+  /*element: Class2.field3a:initial=BoolConstant(false)*/
   var field3a;
 
-  /*element: Class2.field3b:*/
+  /*element: Class2.field3b:constant=BoolConstant(false)*/
   var field3b;
 
-  /*element: Class2.field4a:*/
+  /*element: Class2.field4a:initial=IntConstant(0)*/
   var field4a;
 
-  /*element: Class2.field4b:*/
+  /*element: Class2.field4b:constant=IntConstant(0)*/
   var field4b;
 
-  /*element: Class2.field5a:*/
+  /*element: Class2.field5a:initial=IntConstant(1)*/
   var field5a;
 
-  /*element: Class2.field5b:*/
+  /*element: Class2.field5b:constant=IntConstant(1)*/
   var field5b;
 
-  /*element: Class2.field6a:*/
+  /*element: Class2.field6a:initial=StringConstant("")*/
   var field6a;
 
-  /*element: Class2.field6b:*/
+  /*element: Class2.field6b:constant=StringConstant("")*/
   var field6b;
 
-  /*element: Class2.field7a:*/
+  /*element: Class2.field7a:initial=StringConstant("foo")*/
   var field7a;
 
-  /*element: Class2.field7b:*/
+  /*element: Class2.field7b:constant=StringConstant("foo")*/
   var field7b;
 
   /*element: Class2.field8a:*/
   var field8a;
 
-  /*element: Class2.field8b:*/
+  /*element: Class2.field8b:constant=DoubleConstant(0.5)*/
   var field8b;
 
   /*element: Class2.field9a:*/
   var field9a;
 
-  /*element: Class2.field9b:*/
+  /*element: Class2.field9b:constant=ListConstant([])*/
   var field9b;
 
   /*element: Class2.field9c:*/
   var field9c;
 
-  /*element: Class2.field9d:*/
+  /*element: Class2.field9d:constant=ListConstant(<int>[IntConstant(0), IntConstant(1), IntConstant(2)])*/
   var field9d;
 
   /*element: Class2.field10a:*/
   var field10a;
 
-  /*element: Class2.field10b:*/
+  /*element: Class2.field10b:constant=MapConstant({})*/
   var field10b;
 
   /*element: Class2.field10c:*/
   var field10c;
 
-  /*element: Class2.field10d:*/
+  /*element: Class2.field10d:constant=MapConstant(<int, int>{IntConstant(0): IntConstant(1), IntConstant(2): IntConstant(3), IntConstant(4): IntConstant(5)})*/
   var field10d;
 
   /*element: Class2.field11a:*/
   var field11a;
 
-  /*element: Class2.field11b:*/
+  /*element: Class2.field11b:constant=ConstructedConstant(Symbol(_name=StringConstant("foo")))*/
   var field11b;
 
-  /*element: Class2.field12a:*/
+  /*element: Class2.field12a:initial=IntConstant(5)*/
   var field12a;
 
-  /*element: Class2.field12b:*/
+  /*element: Class2.field12b:constant=IntConstant(5)*/
   var field12b;
 
-  /*element: Class2.field13a:*/
+  /*element: Class2.field13a:initial=BoolConstant(true)*/
   var field13a;
 
-  /*element: Class2.field13b:*/
+  /*element: Class2.field13b:constant=BoolConstant(true)*/
   var field13b;
 
   Class2()
diff --git a/tests/compiler/dart2js/field_analysis/kfield_analysis_test.dart b/tests/compiler/dart2js/field_analysis/kfield_analysis_test.dart
index fbc8b7e..124ff4c 100644
--- a/tests/compiler/dart2js/field_analysis/kfield_analysis_test.dart
+++ b/tests/compiler/dart2js/field_analysis/kfield_analysis_test.dart
@@ -47,7 +47,7 @@
         }
         data.initializers.forEach((constructor, value) {
           features['${constructor.enclosingClass.name}.${constructor.name}'] =
-              value?.shortText();
+              value?.shortText;
         });
       }
       Id id = computeEntityId(node);
diff --git a/tests/compiler/dart2js/helpers/program_lookup.dart b/tests/compiler/dart2js/helpers/program_lookup.dart
index cac728c..ccbf049 100644
--- a/tests/compiler/dart2js/helpers/program_lookup.dart
+++ b/tests/compiler/dart2js/helpers/program_lookup.dart
@@ -227,11 +227,13 @@
 void forEachNode(js.Node root,
     {void Function(js.Call) onCall,
     void Function(js.PropertyAccess) onPropertyAccess,
-    void Function(js.Assignment) onAssignment}) {
+    void Function(js.Assignment) onAssignment,
+    void Function(js.Switch) onSwitch}) {
   CallbackVisitor visitor = new CallbackVisitor(
       onCall: onCall,
       onPropertyAccess: onPropertyAccess,
-      onAssignment: onAssignment);
+      onAssignment: onAssignment,
+      onSwitch: onSwitch);
   root.accept(visitor);
 }
 
@@ -239,8 +241,10 @@
   final void Function(js.Call) onCall;
   final void Function(js.PropertyAccess) onPropertyAccess;
   final void Function(js.Assignment) onAssignment;
+  final void Function(js.Switch) onSwitch;
 
-  CallbackVisitor({this.onCall, this.onPropertyAccess, this.onAssignment});
+  CallbackVisitor(
+      {this.onCall, this.onPropertyAccess, this.onAssignment, this.onSwitch});
 
   @override
   visitCall(js.Call node) {
@@ -259,4 +263,10 @@
     if (onAssignment != null) onAssignment(node);
     return super.visitAssignment(node);
   }
+
+  @override
+  visitSwitch(js.Switch node) {
+    if (onSwitch != null) onSwitch(node);
+    return super.visitSwitch(node);
+  }
 }
diff --git a/tests/compiler/dart2js/inlining/meta_annotations2_test.dart b/tests/compiler/dart2js/inlining/meta_annotations2_test.dart
index 27e0277..d809601 100644
--- a/tests/compiler/dart2js/inlining/meta_annotations2_test.dart
+++ b/tests/compiler/dart2js/inlining/meta_annotations2_test.dart
@@ -7,6 +7,7 @@
 import "package:expect/expect.dart";
 import "package:async_helper/async_helper.dart";
 import 'package:compiler/compiler_new.dart';
+import 'package:compiler/src/commandline_options.dart';
 import '../helpers/memory_compiler.dart';
 
 const MEMORY_SOURCE_FILES = const {
@@ -17,6 +18,7 @@
         foo(y) => 49912344 + y;
 
         class A {
+          @pragma('dart2js:noElision')
           var field;
 
           @noInline
@@ -41,7 +43,9 @@
   runTests() async {
     OutputCollector collector = new OutputCollector();
     await runCompiler(
-        memorySourceFiles: MEMORY_SOURCE_FILES, outputProvider: collector);
+        memorySourceFiles: MEMORY_SOURCE_FILES,
+        outputProvider: collector,
+        options: [Flags.testMode]);
     // Simply check that the constants of the small functions are still in the
     // output, and that we don't see the result of constant folding.
     String jsOutput = collector.getOutput('', OutputType.js);