[CFE] Handle invalid constant string interpolation in unevaluated constant expressions

Non-primitive constants were incorrectly stringified in an unevaluated
context. These are now, as with unevaluated constants, checked when the
string concatenation is fully evaluated.

Closes #36609

Change-Id: Ia3266ebcb9d497b277690244569812f7cd3e30c8
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/99461
Reviewed-by: Sigmund Cherem <sigmund@google.com>
Commit-Queue: Sigmund Cherem <sigmund@google.com>
Auto-Submit: Johnni Winther <johnniwinther@google.com>
diff --git a/pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart b/pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart
index 372484b..b35550c 100644
--- a/pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart
@@ -1951,6 +1951,9 @@
           concatenated.add(new StringBuffer(value));
         }
       } else if (shouldBeUnevaluated) {
+        // The constant is either unevaluated or a non-primitive in an
+        // unevaluated context. In both cases we defer the evaluation and/or
+        // error reporting till later.
         concatenated.add(constant);
       } else {
         return report(
@@ -1963,11 +1966,13 @@
       final expressions = new List<Expression>(concatenated.length);
       for (int i = 0; i < concatenated.length; i++) {
         Object value = concatenated[i];
-        if (value is UnevaluatedConstant) {
-          expressions[i] = extract(value);
-        } else {
+        if (value is StringBuffer) {
           expressions[i] = new ConstantExpression(
               canonicalize(new StringConstant(value.toString())));
+        } else {
+          // The value is either unevaluated constant or a non-primitive
+          // constant in an unevaluated expression.
+          expressions[i] = extract(value);
         }
       }
       return unevaluated(node, new StringConcatenation(expressions));
diff --git a/tests/compiler/dart2js/model/cfe_constant_evaluation_test.dart b/tests/compiler/dart2js/model/cfe_constant_evaluation_test.dart
index c056024..52f93c0 100644
--- a/tests/compiler/dart2js/model/cfe_constant_evaluation_test.dart
+++ b/tests/compiler/dart2js/model/cfe_constant_evaluation_test.dart
@@ -99,11 +99,11 @@
     const ConstantData(
         'const {0: 1, 2: 3}',
         'MapConstant(<int, int>{IntConstant(0): IntConstant(1), '
-        'IntConstant(2): IntConstant(3)})'),
+            'IntConstant(2): IntConstant(3)})'),
     const ConstantData(
         'const <int, int>{0: 1, 2: 3}',
         'MapConstant(<int, int>{IntConstant(0): IntConstant(1), '
-        'IntConstant(2): IntConstant(3)})'),
+            'IntConstant(2): IntConstant(3)})'),
     const ConstantData('const <int, int>{0: 1, 0: 2}', 'NonConstant',
         expectedErrors: 'ConstEvalDuplicateKey'),
     const ConstantData(
@@ -155,36 +155,36 @@
     const ConstantData(
         'const C()',
         'ConstructedConstant(C(field1=IntConstant(42),'
-        'field2=BoolConstant(false)))'),
+            'field2=BoolConstant(false)))'),
     const ConstantData(
         'const C(field1: 87)',
         'ConstructedConstant(C(field1=IntConstant(87),'
-        'field2=BoolConstant(false)))'),
+            'field2=BoolConstant(false)))'),
     const ConstantData(
         'const C(field2: true)',
         'ConstructedConstant(C(field1=IntConstant(42),'
-        'field2=BoolConstant(true)))'),
+            'field2=BoolConstant(true)))'),
     const ConstantData(
         'const C.named()',
         'ConstructedConstant(C(field1=BoolConstant(false),'
-        'field2=BoolConstant(false)))'),
+            'field2=BoolConstant(false)))'),
     const ConstantData(
         'const C.named(87)',
         'ConstructedConstant(C(field1=IntConstant(87),'
-        'field2=IntConstant(87)))'),
+            'field2=IntConstant(87)))'),
     const ConstantData(
         'const C(field1: a, field2: b)', const <Map<String, String>, String>{
       const {}: 'ConstructedConstant(C(field1=BoolConstant(true),'
           'field2=IntConstant(42)))',
       const {'foo': 'false', 'bar': '87'}:
           'ConstructedConstant(C(field1=BoolConstant(false),'
-          'field2=IntConstant(87)))',
+              'field2=IntConstant(87)))',
     }),
     const ConstantData(
         'const D(42, 87)',
         'ConstructedConstant(D(field1=IntConstant(87),'
-        'field2=IntConstant(42),'
-        'field3=IntConstant(99)))'),
+            'field2=IntConstant(42),'
+            'field3=IntConstant(99)))'),
   ]),
   const TestData('redirect', '''
 class A<T> implements B<Null> {
@@ -252,19 +252,19 @@
     const ConstantData(
         'const A.named(99, 100)',
         'ConstructedConstant(A('
-        't=IntConstant(100),'
-        'u=IntConstant(42),'
-        'x=IntConstant(3),'
-        'y=IntConstant(499),'
-        'z=IntConstant(99)))'),
+            't=IntConstant(100),'
+            'u=IntConstant(42),'
+            'x=IntConstant(3),'
+            'y=IntConstant(499),'
+            'z=IntConstant(99)))'),
     const ConstantData(
         'const A(99, 100)',
         'ConstructedConstant(A('
-        't=IntConstant(100),'
-        'u=IntConstant(42),'
-        'x=IntConstant(3),'
-        'y=IntConstant(499),'
-        'z=IntConstant(99)))'),
+            't=IntConstant(100),'
+            'u=IntConstant(42),'
+            'x=IntConstant(3),'
+            'y=IntConstant(499),'
+            'z=IntConstant(99)))'),
   ]),
   const TestData('errors', r'''
  const dynamic null_ = const bool.fromEnvironment('x') ? null : null;
@@ -317,9 +317,8 @@
         r'"$integer $string $boolean"', 'StringConstant("5 baz false")'),
     const ConstantData('integer ? true : false', 'NonConstant',
         expectedErrors: 'ConstEvalInvalidType'),
-    // TODO(sigmund): CFE incorrectly stringifies proxy (issue 36609).
-    //const ConstantData(r'"${proxy}"', 'NonConstant',
-    //    expectedErrors: 'ConstEvalInvalidStringInterpolationOperand'),
+    const ConstantData(r'"${proxy}"', 'NonConstant',
+        expectedErrors: 'ConstEvalInvalidStringInterpolationOperand'),
     const ConstantData('0 + string', 'NonConstant',
         expectedErrors: 'ConstEvalInvalidType'),
     const ConstantData('string + 0', 'NonConstant',
@@ -442,13 +441,13 @@
     const ConstantData(
         'const C<int>(0, identity)',
         'ConstructedConstant(C<int>(defaultValue=IntConstant(0),'
-        'identityFunction=InstantiationConstant([int],'
-        'FunctionConstant(identity))))'),
+            'identityFunction=InstantiationConstant([int],'
+            'FunctionConstant(identity))))'),
     const ConstantData(
         'const C<double>(0.5, identity)',
         'ConstructedConstant(C<double>(defaultValue=DoubleConstant(0.5),'
-        'identityFunction=InstantiationConstant([double],'
-        'FunctionConstant(identity))))'),
+            'identityFunction=InstantiationConstant([double],'
+            'FunctionConstant(identity))))'),
   ]),
   const TestData('generic class', '''
 class C<T> {