[cfe] Variable assignments (VariableSet) for const functions.

Change-Id: Iab23493fc09b8fdad78968d905ac8f28e1236aa4
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/190620
Commit-Queue: Kallen Tu <kallentu@google.com>
Reviewed-by: Dmitry Stefantsov <dmitryas@google.com>
Reviewed-by: Jake Macdonald <jakemac@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 2fba597..1089515 100644
--- a/pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart
@@ -543,7 +543,7 @@
       if (node.function != null) {
         node.function = transform(node.function)..parent = node;
       }
-      constantEvaluator.env.updateVariableValue(
+      constantEvaluator.env.addVariableValue(
           node.variable, new IntermediateValue(node.function));
     } else {
       return super.visitFunctionDeclaration(node, removalSentinel);
@@ -559,7 +559,7 @@
     if (node.initializer != null) {
       if (node.isConst) {
         final Constant constant = evaluateWithContext(node, node.initializer);
-        constantEvaluator.env.updateVariableValue(node, constant);
+        constantEvaluator.env.addVariableValue(node, constant);
         node.initializer = makeConstantExpression(constant, node.initializer)
           ..parent = node;
 
@@ -1669,14 +1669,14 @@
             // TODO(johnniwinther): This should call [_evaluateSubexpression].
             : _evaluateNullableSubexpression(parameter.initializer);
         if (value is AbortConstant) return value;
-        env.updateVariableValue(parameter, value);
+        env.addVariableValue(parameter, value);
       }
       for (final VariableDeclaration parameter in function.namedParameters) {
         final Constant value = namedArguments[parameter.name] ??
             // TODO(johnniwinther): This should call [_evaluateSubexpression].
             _evaluateNullableSubexpression(parameter.initializer);
         if (value is AbortConstant) return value;
-        env.updateVariableValue(parameter, value);
+        env.addVariableValue(parameter, value);
       }
 
       // Step 2) Run all initializers (including super calls) with environment
@@ -1697,7 +1697,7 @@
           final VariableDeclaration variable = init.variable;
           Constant constant = _evaluateSubexpression(variable.initializer);
           if (constant is AbortConstant) return constant;
-          env.updateVariableValue(variable, constant);
+          env.addVariableValue(variable, constant);
         } else if (init is SuperInitializer) {
           AbortConstant error = checkConstructorConst(init, constructor);
           if (error != null) return error;
@@ -2412,7 +2412,7 @@
   Constant visitLet(Let node) {
     Constant value = _evaluateSubexpression(node.variable.initializer);
     if (value is AbortConstant) return value;
-    env.updateVariableValue(node.variable, value);
+    env.addVariableValue(node.variable, value);
     return _evaluateSubexpression(node.body);
   }
 
@@ -2445,6 +2445,19 @@
         node, 'Variable get of a non-const variable.');
   }
 
+  @override
+  Constant visitVariableSet(VariableSet node) {
+    if (enableConstFunctions) {
+      final VariableDeclaration variable = node.variable;
+      Constant value = _evaluateSubexpression(node.value);
+      if (value is AbortConstant) return value;
+      return env.updateVariableValue(variable, value) ??
+          createInvalidExpressionConstant(
+              node, 'Variable set of an unknown value.');
+    }
+    return defaultExpression(node);
+  }
+
   /// Computes the constant for [expression] defined in the context of [member].
   ///
   /// This compute the constant as seen in the current evaluation mode even when
@@ -2772,14 +2785,14 @@
             // TODO(johnniwinther): This should call [_evaluateSubexpression].
             : _evaluateNullableSubexpression(parameter.initializer);
         if (value is AbortConstant) return value;
-        env.updateVariableValue(parameter, value);
+        env.addVariableValue(parameter, value);
       }
       for (final VariableDeclaration parameter in function.namedParameters) {
         final Constant value = namedArguments[parameter.name] ??
             // TODO(johnniwinther): This should call [_evaluateSubexpression].
             _evaluateNullableSubexpression(parameter.initializer);
         if (value is AbortConstant) return value;
-        env.updateVariableValue(parameter, value);
+        env.addVariableValue(parameter, value);
       }
       return execute(function.body);
     });
@@ -3295,9 +3308,6 @@
 
   @override
   Constant visitThrow(Throw node) => defaultExpression(node);
-
-  @override
-  Constant visitVariableSet(VariableSet node) => defaultExpression(node);
 }
 
 class StatementConstantEvaluator extends StatementVisitor<ExecutionStatus> {
@@ -3330,6 +3340,13 @@
   }
 
   @override
+  ExecutionStatus visitExpressionStatement(ExpressionStatement node) {
+    Constant value = evaluate(node.expression);
+    if (value is AbortConstant) return new ReturnStatus(value);
+    return const ProceedStatus();
+  }
+
+  @override
   ExecutionStatus visitReturnStatement(ReturnStatement node) =>
       new ReturnStatus(evaluate(node.expression));
 
@@ -3337,7 +3354,7 @@
   ExecutionStatus visitVariableDeclaration(VariableDeclaration node) {
     Constant value = evaluate(node.initializer);
     if (value is AbortConstant) return new ReturnStatus(value);
-    exprEvaluator.env.updateVariableValue(node, value);
+    exprEvaluator.env.addVariableValue(node, value);
     return const ProceedStatus();
   }
 }
@@ -3426,17 +3443,21 @@
     _typeVariables[parameter] = value;
   }
 
-  void updateVariableValue(VariableDeclaration variable, Constant value) {
-    if (!_variables.containsKey(variable)) {
-      _variables[variable] = new EvaluationReference(value);
-    } else {
-      _variables[variable].value = value;
-    }
+  void addVariableValue(VariableDeclaration variable, Constant value) {
+    _variables[variable] = new EvaluationReference(value);
     if (value is UnevaluatedConstant) {
       _unreadUnevaluatedVariables.add(variable);
     }
   }
 
+  Constant updateVariableValue(VariableDeclaration variable, Constant value) {
+    if (_variables.containsKey(variable)) {
+      _variables[variable].value = value;
+      return value;
+    }
+    return _parent?.updateVariableValue(variable, value);
+  }
+
   Constant lookupVariable(VariableDeclaration variable) {
     Constant value = _variables[variable]?.value;
     if (value is UnevaluatedConstant) {
diff --git a/pkg/front_end/testcases/const_functions/const_functions_variable_assignments.dart b/pkg/front_end/testcases/const_functions/const_functions_variable_assignments.dart
new file mode 100644
index 0000000..3a3fc6f
--- /dev/null
+++ b/pkg/front_end/testcases/const_functions/const_functions_variable_assignments.dart
@@ -0,0 +1,42 @@
+// 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.
+
+// Tests variable assignments for const functions.
+
+// SharedOptions=--enable-experiment=const-functions
+
+import "package:expect/expect.dart";
+
+const var1 = varAssignmentTest(1);
+int varAssignmentTest(int a) {
+  int x = 4;
+  {
+    x = 3;
+  }
+  return x;
+}
+
+int function() {
+  int varAssignmentTest2() {
+    int x = 2;
+    x += 1;
+    return x;
+  }
+
+  const var2 = varAssignmentTest2();
+  return var2;
+}
+
+const var3 = varAssignmentTest3(1);
+int varAssignmentTest3(int a) {
+  int x = 4;
+  x = a + 1;
+  return x;
+}
+
+void main() {
+  Expect.equals(var1, 3);
+  Expect.equals(function(), 3);
+  Expect.equals(var3, 2);
+}
diff --git a/pkg/front_end/testcases/const_functions/const_functions_variable_assignments.dart.strong.expect b/pkg/front_end/testcases/const_functions/const_functions_variable_assignments.dart.strong.expect
new file mode 100644
index 0000000..337c656
--- /dev/null
+++ b/pkg/front_end/testcases/const_functions/const_functions_variable_assignments.dart.strong.expect
@@ -0,0 +1,39 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+import "package:expect/expect.dart" as exp;
+
+import "package:expect/expect.dart";
+
+static const field core::int var1 = #C1;
+static const field core::int var3 = #C2;
+static method varAssignmentTest(core::int a) → core::int {
+  core::int x = 4;
+  {
+    x = 3;
+  }
+  return x;
+}
+static method function() → core::int {
+  function varAssignmentTest2() → core::int {
+    core::int x = 2;
+    x = x.{core::num::+}(1);
+    return x;
+  }
+  return #C1;
+}
+static method varAssignmentTest3(core::int a) → core::int {
+  core::int x = 4;
+  x = a.{core::num::+}(1);
+  return x;
+}
+static method main() → void {
+  exp::Expect::equals(#C1, 3);
+  exp::Expect::equals(self::function(), 3);
+  exp::Expect::equals(#C2, 2);
+}
+
+constants  {
+  #C1 = 3
+  #C2 = 2
+}
diff --git a/pkg/front_end/testcases/const_functions/const_functions_variable_assignments.dart.strong.transformed.expect b/pkg/front_end/testcases/const_functions/const_functions_variable_assignments.dart.strong.transformed.expect
new file mode 100644
index 0000000..337c656
--- /dev/null
+++ b/pkg/front_end/testcases/const_functions/const_functions_variable_assignments.dart.strong.transformed.expect
@@ -0,0 +1,39 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+import "package:expect/expect.dart" as exp;
+
+import "package:expect/expect.dart";
+
+static const field core::int var1 = #C1;
+static const field core::int var3 = #C2;
+static method varAssignmentTest(core::int a) → core::int {
+  core::int x = 4;
+  {
+    x = 3;
+  }
+  return x;
+}
+static method function() → core::int {
+  function varAssignmentTest2() → core::int {
+    core::int x = 2;
+    x = x.{core::num::+}(1);
+    return x;
+  }
+  return #C1;
+}
+static method varAssignmentTest3(core::int a) → core::int {
+  core::int x = 4;
+  x = a.{core::num::+}(1);
+  return x;
+}
+static method main() → void {
+  exp::Expect::equals(#C1, 3);
+  exp::Expect::equals(self::function(), 3);
+  exp::Expect::equals(#C2, 2);
+}
+
+constants  {
+  #C1 = 3
+  #C2 = 2
+}
diff --git a/pkg/front_end/testcases/const_functions/const_functions_variable_assignments.dart.textual_outline.expect b/pkg/front_end/testcases/const_functions/const_functions_variable_assignments.dart.textual_outline.expect
new file mode 100644
index 0000000..801ab46
--- /dev/null
+++ b/pkg/front_end/testcases/const_functions/const_functions_variable_assignments.dart.textual_outline.expect
@@ -0,0 +1,8 @@
+import "package:expect/expect.dart";
+
+const var1 = varAssignmentTest(1);
+int varAssignmentTest(int a) {}
+int function() {}
+const var3 = varAssignmentTest3(1);
+int varAssignmentTest3(int a) {}
+void main() {}
diff --git a/pkg/front_end/testcases/const_functions/const_functions_variable_assignments.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/const_functions/const_functions_variable_assignments.dart.textual_outline_modelled.expect
new file mode 100644
index 0000000..5f57ea4
--- /dev/null
+++ b/pkg/front_end/testcases/const_functions/const_functions_variable_assignments.dart.textual_outline_modelled.expect
@@ -0,0 +1,8 @@
+import "package:expect/expect.dart";
+
+const var1 = varAssignmentTest(1);
+const var3 = varAssignmentTest3(1);
+int function() {}
+int varAssignmentTest(int a) {}
+int varAssignmentTest3(int a) {}
+void main() {}
diff --git a/pkg/front_end/testcases/const_functions/const_functions_variable_assignments.dart.weak.expect b/pkg/front_end/testcases/const_functions/const_functions_variable_assignments.dart.weak.expect
new file mode 100644
index 0000000..337c656
--- /dev/null
+++ b/pkg/front_end/testcases/const_functions/const_functions_variable_assignments.dart.weak.expect
@@ -0,0 +1,39 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+import "package:expect/expect.dart" as exp;
+
+import "package:expect/expect.dart";
+
+static const field core::int var1 = #C1;
+static const field core::int var3 = #C2;
+static method varAssignmentTest(core::int a) → core::int {
+  core::int x = 4;
+  {
+    x = 3;
+  }
+  return x;
+}
+static method function() → core::int {
+  function varAssignmentTest2() → core::int {
+    core::int x = 2;
+    x = x.{core::num::+}(1);
+    return x;
+  }
+  return #C1;
+}
+static method varAssignmentTest3(core::int a) → core::int {
+  core::int x = 4;
+  x = a.{core::num::+}(1);
+  return x;
+}
+static method main() → void {
+  exp::Expect::equals(#C1, 3);
+  exp::Expect::equals(self::function(), 3);
+  exp::Expect::equals(#C2, 2);
+}
+
+constants  {
+  #C1 = 3
+  #C2 = 2
+}
diff --git a/pkg/front_end/testcases/const_functions/const_functions_variable_assignments.dart.weak.outline.expect b/pkg/front_end/testcases/const_functions/const_functions_variable_assignments.dart.weak.outline.expect
new file mode 100644
index 0000000..71847ee
--- /dev/null
+++ b/pkg/front_end/testcases/const_functions/const_functions_variable_assignments.dart.weak.outline.expect
@@ -0,0 +1,16 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+
+import "package:expect/expect.dart";
+
+static const field core::int var1 = self::varAssignmentTest(1);
+static const field core::int var3 = self::varAssignmentTest3(1);
+static method varAssignmentTest(core::int a) → core::int
+  ;
+static method function() → core::int
+  ;
+static method varAssignmentTest3(core::int a) → core::int
+  ;
+static method main() → void
+  ;
diff --git a/pkg/front_end/testcases/const_functions/const_functions_variable_assignments.dart.weak.transformed.expect b/pkg/front_end/testcases/const_functions/const_functions_variable_assignments.dart.weak.transformed.expect
new file mode 100644
index 0000000..337c656
--- /dev/null
+++ b/pkg/front_end/testcases/const_functions/const_functions_variable_assignments.dart.weak.transformed.expect
@@ -0,0 +1,39 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+import "package:expect/expect.dart" as exp;
+
+import "package:expect/expect.dart";
+
+static const field core::int var1 = #C1;
+static const field core::int var3 = #C2;
+static method varAssignmentTest(core::int a) → core::int {
+  core::int x = 4;
+  {
+    x = 3;
+  }
+  return x;
+}
+static method function() → core::int {
+  function varAssignmentTest2() → core::int {
+    core::int x = 2;
+    x = x.{core::num::+}(1);
+    return x;
+  }
+  return #C1;
+}
+static method varAssignmentTest3(core::int a) → core::int {
+  core::int x = 4;
+  x = a.{core::num::+}(1);
+  return x;
+}
+static method main() → void {
+  exp::Expect::equals(#C1, 3);
+  exp::Expect::equals(self::function(), 3);
+  exp::Expect::equals(#C2, 2);
+}
+
+constants  {
+  #C1 = 3
+  #C2 = 2
+}
diff --git a/tests/language/const_functions/const_functions_variable_assignments_test.dart b/tests/language/const_functions/const_functions_variable_assignments_test.dart
new file mode 100644
index 0000000..f2c47cb
--- /dev/null
+++ b/tests/language/const_functions/const_functions_variable_assignments_test.dart
@@ -0,0 +1,48 @@
+// 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.
+
+// Tests variable assignments for const functions.
+
+// SharedOptions=--enable-experiment=const-functions
+
+import "package:expect/expect.dart";
+
+const var1 = varAssignmentTest(1);
+//           ^^^^^^^^^^^^^^^^^^^^
+// [analyzer] COMPILE_TIME_ERROR.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE
+int varAssignmentTest(int a) {
+  int x = 4;
+  {
+    x = 3;
+  }
+  return x;
+}
+
+int function() {
+  int varAssignmentTest2() {
+    int x = 2;
+    x += 1;
+    return x;
+  }
+
+  const var2 = varAssignmentTest2();
+  //           ^^^^^^^^^^^^^^^^^^^^
+  // [analyzer] COMPILE_TIME_ERROR.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE
+  return var2;
+}
+
+const var3 = varAssignmentTest3(1);
+//           ^^^^^^^^^^^^^^^^^^^^^
+// [analyzer] COMPILE_TIME_ERROR.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE
+int varAssignmentTest3(int a) {
+  int x = 4;
+  x = a + 1;
+  return x;
+}
+
+void main() {
+  Expect.equals(var1, 3);
+  Expect.equals(function(), 3);
+  Expect.equals(var3, 2);
+}