From 6f90c5531ef8929d0d1413b0de7404d88b6aa00a Mon Sep 17 00:00:00 2001 From: Kallen Tu Date: Thu, 8 Apr 2021 15:09:39 +0000 Subject: [PATCH] [cfe] Closures and environment changes for const functions. Change-Id: If844a28192c1eef4dcbb62075ae07e86297f2c92 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/193920 Reviewed-by: Dmitry Stefantsov Reviewed-by: Bob Nystrom Reviewed-by: Jake Macdonald Commit-Queue: Kallen Tu --- .../lib/src/messages/codes_generated.dart | 22 ++++ .../src/fasta/kernel/constant_evaluator.dart | 65 ++++++++--- pkg/front_end/messages.status | 2 + pkg/front_end/messages.yaml | 3 + .../const_functions_assert_statements.dart | 11 ++ ...tions_assert_statements.dart.strong.expect | 10 ++ ..._statements.dart.strong.transformed.expect | 10 ++ ...ert_statements.dart.textual_outline.expect | 2 + ...ments.dart.textual_outline_modelled.expect | 2 + ...nctions_assert_statements.dart.weak.expect | 10 ++ ...assert_statements.dart.weak.outline.expect | 3 + ...rt_statements.dart.weak.transformed.expect | 10 ++ .../const_functions_closures.dart | 91 +++++++++++++++ ...onst_functions_closures.dart.strong.expect | 86 ++++++++++++++ ...ns_closures.dart.strong.transformed.expect | 86 ++++++++++++++ ...tions_closures.dart.textual_outline.expect | 16 +++ ...sures.dart.textual_outline_modelled.expect | 16 +++ .../const_functions_closures.dart.weak.expect | 86 ++++++++++++++ ...unctions_closures.dart.weak.outline.expect | 27 +++++ ...ions_closures.dart.weak.transformed.expect | 86 ++++++++++++++ ...onst_functions_assert_statements_test.dart | 13 +++ .../const_functions_closures_error_test.dart | 81 ++++++++++++++ .../const_functions_closures_test.dart | 105 ++++++++++++++++++ 23 files changed, 826 insertions(+), 17 deletions(-) create mode 100644 pkg/front_end/testcases/const_functions/const_functions_closures.dart create mode 100644 pkg/front_end/testcases/const_functions/const_functions_closures.dart.strong.expect create mode 100644 pkg/front_end/testcases/const_functions/const_functions_closures.dart.strong.transformed.expect create mode 100644 pkg/front_end/testcases/const_functions/const_functions_closures.dart.textual_outline.expect create mode 100644 pkg/front_end/testcases/const_functions/const_functions_closures.dart.textual_outline_modelled.expect create mode 100644 pkg/front_end/testcases/const_functions/const_functions_closures.dart.weak.expect create mode 100644 pkg/front_end/testcases/const_functions/const_functions_closures.dart.weak.outline.expect create mode 100644 pkg/front_end/testcases/const_functions/const_functions_closures.dart.weak.transformed.expect create mode 100644 tests/language/const_functions/const_functions_closures_error_test.dart create mode 100644 tests/language/const_functions/const_functions_closures_test.dart diff --git a/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart b/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart index 530baf10bb3..f05045ecbe2 100644 --- a/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart +++ b/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart @@ -1352,6 +1352,28 @@ Message _withArgumentsConstEvalFailedAssertionWithMessage( arguments: {'stringOKEmpty': stringOKEmpty}); } +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. +const Template + templateConstEvalGetterNotFound = + const Template( + messageTemplate: r"""Variable get not found: '#nameOKEmpty'""", + withArguments: _withArgumentsConstEvalGetterNotFound); + +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. +const Code codeConstEvalGetterNotFound = + const Code( + "ConstEvalGetterNotFound", +); + +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. +Message _withArgumentsConstEvalGetterNotFound(String nameOKEmpty) { + // ignore: unnecessary_null_comparison + if (nameOKEmpty == null || nameOKEmpty.isEmpty) nameOKEmpty = '(unnamed)'; + return new Message(codeConstEvalGetterNotFound, + message: """Variable get not found: '${nameOKEmpty}'""", + arguments: {'nameOKEmpty': nameOKEmpty}); +} + // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. const Template templateConstEvalInvalidStaticInvocation = 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 97f21f34888..1a2a02ff221 100644 --- a/pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart +++ b/pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart @@ -59,6 +59,7 @@ import '../fasta_codes.dart' templateConstEvalElementImplementsEqual, templateConstEvalFailedAssertionWithMessage, templateConstEvalFreeTypeParameter, + templateConstEvalGetterNotFound, templateConstEvalInvalidType, templateConstEvalInvalidBinaryOperandType, templateConstEvalInvalidEqualsOperandType, @@ -559,7 +560,7 @@ class ConstantsTransformer extends RemovingTransformer { node.function = transform(node.function)..parent = node; } constantEvaluator.env.addVariableValue( - node.variable, new IntermediateValue(node.function)); + node.variable, new FunctionValue(node.function, null)); } else { return super.visitFunctionDeclaration(node, removalSentinel); } @@ -1394,6 +1395,9 @@ class ConstantEvaluator implements ExpressionVisitor { @override Constant visitFunctionExpression(FunctionExpression node) { + if (enableConstFunctions) { + return new FunctionValue(node.function, env); + } return createInvalidExpressionConstant(node, "Function literal"); } @@ -2165,6 +2169,9 @@ class ConstantEvaluator implements ExpressionVisitor { node, "method invocation with named arguments"); } + final Constant receiver = _evaluateSubexpression(node.receiver); + if (receiver is AbortConstant) return receiver; + final List arguments = _evaluatePositionalArguments(node.arguments); @@ -2176,12 +2183,7 @@ class ConstantEvaluator implements ExpressionVisitor { assert(_gotError == null); assert(arguments != null); - final Constant receiver = _evaluateSubexpression(node.receiver); - if (receiver is AbortConstant) { - return receiver; - } else if (enableConstFunctions && - receiver is IntermediateValue && - receiver.value is FunctionNode) { + if (enableConstFunctions && receiver is FunctionValue) { // Evaluate type arguments of the method invoked. List types = _evaluateTypeArguments(node, node.arguments); if (types == null && _gotError != null) { @@ -2203,7 +2205,9 @@ class ConstantEvaluator implements ExpressionVisitor { assert(_gotError == null); assert(named != null); - return _handleFunctionInvocation(receiver.value, types, arguments, named); + return _handleFunctionInvocation( + receiver.function, types, arguments, named, + functionEnvironment: receiver.environment); } if (shouldBeUnevaluated) { @@ -2465,8 +2469,8 @@ class ConstantEvaluator implements ExpressionVisitor { final VariableDeclaration variable = node.variable; if (enableConstFunctions) { return env.lookupVariable(variable) ?? - createInvalidExpressionConstant( - node, 'Variable get of an unknown value.'); + createErrorConstant(node, + templateConstEvalGetterNotFound.withArguments(variable.name)); } else { if (variable.parent is Let || _isFormalParameter(variable)) { return env.lookupVariable(node.variable) ?? @@ -2815,8 +2819,9 @@ class ConstantEvaluator implements ExpressionVisitor { FunctionNode function, List typeArguments, List positionalArguments, - Map namedArguments) { - return withNewEnvironment(() { + Map namedArguments, + {EvaluationEnvironment functionEnvironment}) { + Constant executeFunction() { // Map arguments from caller to callee. for (int i = 0; i < function.typeParameters.length; i++) { env.addTypeParameterValue(function.typeParameters[i], typeArguments[i]); @@ -2838,7 +2843,12 @@ class ConstantEvaluator implements ExpressionVisitor { env.addVariableValue(parameter, value); } return execute(function.body); - }); + } + + if (functionEnvironment != null) { + return withEnvironment(functionEnvironment, executeFunction); + } + return withNewEnvironment(executeFunction); } @override @@ -3264,6 +3274,14 @@ class ConstantEvaluator implements ExpressionVisitor { return result; } + T withEnvironment(EvaluationEnvironment newEnv, T fn()) { + final EvaluationEnvironment oldEnv = env; + env = newEnv; + T result = fn(); + env = oldEnv; + return result; + } + /// Binary operation between two operands, at least one of which is a double. Constant evaluateBinaryNumericOperation( String op, num a, num b, TreeNode node) { @@ -3423,6 +3441,17 @@ class StatementConstantEvaluator extends StatementVisitor { return const ProceedStatus(); } + @override + ExecutionStatus visitFunctionDeclaration(FunctionDeclaration node) { + final EvaluationEnvironment newEnv = + new EvaluationEnvironment.withParent(exprEvaluator.env); + newEnv.addVariableValue( + node.variable, new FunctionValue(node.function, null)); + final FunctionValue function = new FunctionValue(node.function, newEnv); + exprEvaluator.env.addVariableValue(node.variable, function); + return const ProceedStatus(); + } + @override ExecutionStatus visitIfStatement(IfStatement node) { Constant condition = evaluate(node.condition); @@ -3740,11 +3769,13 @@ class BreakStatus extends ExecutionStatus { BreakStatus(this.target); } -/// An intermediate result that is used within the [ConstantEvaluator]. -class IntermediateValue implements Constant { - dynamic value; +/// An intermediate result that is used for invoking function nodes with their +/// respective environment within the [ConstantEvaluator]. +class FunctionValue implements Constant { + final FunctionNode function; + final EvaluationEnvironment environment; - IntermediateValue(this.value); + FunctionValue(this.function, this.environment); @override R accept(ConstantVisitor v) { diff --git a/pkg/front_end/messages.status b/pkg/front_end/messages.status index a8965aceb10..35e9097c228 100644 --- a/pkg/front_end/messages.status +++ b/pkg/front_end/messages.status @@ -106,6 +106,8 @@ ConstEvalFailedAssertion/example: Fail ConstEvalFailedAssertionWithMessage/example: Fail ConstEvalFreeTypeParameter/analyzerCode: Fail ConstEvalFreeTypeParameter/example: Fail +ConstEvalGetterNotFound/analyzerCode: Fail +ConstEvalGetterNotFound/example: Fail ConstEvalInvalidBinaryOperandType/analyzerCode: Fail # CONST_EVAL_TYPE_NUM / CONST_EVAL_TYPE_BOOL ConstEvalInvalidBinaryOperandType/example: Fail ConstEvalInvalidEqualsOperandType/analyzerCode: Fail diff --git a/pkg/front_end/messages.yaml b/pkg/front_end/messages.yaml index 0226a47bea5..540ad5d5696 100644 --- a/pkg/front_end/messages.yaml +++ b/pkg/front_end/messages.yaml @@ -150,6 +150,9 @@ ConstEvalTruncateError: ConstEvalNonNull: template: "Constant expression must be non-null." +ConstEvalGetterNotFound: + template: "Variable get not found: '#nameOKEmpty'" + ConstEvalInvalidMethodInvocation: template: "The method '#stringOKEmpty' can't be invoked on '#constant' in a constant expression." analyzerCode: UNDEFINED_OPERATOR diff --git a/pkg/front_end/testcases/const_functions/const_functions_assert_statements.dart b/pkg/front_end/testcases/const_functions/const_functions_assert_statements.dart index de66c5bf62c..6be9676ba41 100644 --- a/pkg/front_end/testcases/const_functions/const_functions_assert_statements.dart +++ b/pkg/front_end/testcases/const_functions/const_functions_assert_statements.dart @@ -13,6 +13,17 @@ int fn() { return x; } +const var2 = fn2(); +int fn2() { + int x = 0; + assert(() { + var y = x + 1; + return y == 1; + }()); + return x; +} + void main() { Expect.equals(var1, 0); + Expect.equals(var2, 0); } diff --git a/pkg/front_end/testcases/const_functions/const_functions_assert_statements.dart.strong.expect b/pkg/front_end/testcases/const_functions/const_functions_assert_statements.dart.strong.expect index 8fa0afba088..6f5f7b1e0bc 100644 --- a/pkg/front_end/testcases/const_functions/const_functions_assert_statements.dart.strong.expect +++ b/pkg/front_end/testcases/const_functions/const_functions_assert_statements.dart.strong.expect @@ -6,13 +6,23 @@ import "package:expect/expect.dart" as exp; import "package:expect/expect.dart"; static const field core::int var1 = #C1; +static const field core::int var2 = #C1; static method fn() → core::int { core::int x = 0; assert(x.{core::num::==}(0), "fail"); return x; } +static method fn2() → core::int { + core::int x = 0; + assert((() → core::bool { + core::int y = x.{core::num::+}(1); + return y.{core::num::==}(1); + }).call()); + return x; +} static method main() → void { exp::Expect::equals(#C1, 0); + exp::Expect::equals(#C1, 0); } constants { diff --git a/pkg/front_end/testcases/const_functions/const_functions_assert_statements.dart.strong.transformed.expect b/pkg/front_end/testcases/const_functions/const_functions_assert_statements.dart.strong.transformed.expect index 8fa0afba088..6f5f7b1e0bc 100644 --- a/pkg/front_end/testcases/const_functions/const_functions_assert_statements.dart.strong.transformed.expect +++ b/pkg/front_end/testcases/const_functions/const_functions_assert_statements.dart.strong.transformed.expect @@ -6,13 +6,23 @@ import "package:expect/expect.dart" as exp; import "package:expect/expect.dart"; static const field core::int var1 = #C1; +static const field core::int var2 = #C1; static method fn() → core::int { core::int x = 0; assert(x.{core::num::==}(0), "fail"); return x; } +static method fn2() → core::int { + core::int x = 0; + assert((() → core::bool { + core::int y = x.{core::num::+}(1); + return y.{core::num::==}(1); + }).call()); + return x; +} static method main() → void { exp::Expect::equals(#C1, 0); + exp::Expect::equals(#C1, 0); } constants { diff --git a/pkg/front_end/testcases/const_functions/const_functions_assert_statements.dart.textual_outline.expect b/pkg/front_end/testcases/const_functions/const_functions_assert_statements.dart.textual_outline.expect index 574b595bdb9..62e4caded34 100644 --- a/pkg/front_end/testcases/const_functions/const_functions_assert_statements.dart.textual_outline.expect +++ b/pkg/front_end/testcases/const_functions/const_functions_assert_statements.dart.textual_outline.expect @@ -2,4 +2,6 @@ import "package:expect/expect.dart"; const var1 = fn(); int fn() {} +const var2 = fn2(); +int fn2() {} void main() {} diff --git a/pkg/front_end/testcases/const_functions/const_functions_assert_statements.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/const_functions/const_functions_assert_statements.dart.textual_outline_modelled.expect index 574b595bdb9..6f46f7ee52f 100644 --- a/pkg/front_end/testcases/const_functions/const_functions_assert_statements.dart.textual_outline_modelled.expect +++ b/pkg/front_end/testcases/const_functions/const_functions_assert_statements.dart.textual_outline_modelled.expect @@ -1,5 +1,7 @@ import "package:expect/expect.dart"; const var1 = fn(); +const var2 = fn2(); int fn() {} +int fn2() {} void main() {} diff --git a/pkg/front_end/testcases/const_functions/const_functions_assert_statements.dart.weak.expect b/pkg/front_end/testcases/const_functions/const_functions_assert_statements.dart.weak.expect index 8fa0afba088..6f5f7b1e0bc 100644 --- a/pkg/front_end/testcases/const_functions/const_functions_assert_statements.dart.weak.expect +++ b/pkg/front_end/testcases/const_functions/const_functions_assert_statements.dart.weak.expect @@ -6,13 +6,23 @@ import "package:expect/expect.dart" as exp; import "package:expect/expect.dart"; static const field core::int var1 = #C1; +static const field core::int var2 = #C1; static method fn() → core::int { core::int x = 0; assert(x.{core::num::==}(0), "fail"); return x; } +static method fn2() → core::int { + core::int x = 0; + assert((() → core::bool { + core::int y = x.{core::num::+}(1); + return y.{core::num::==}(1); + }).call()); + return x; +} static method main() → void { exp::Expect::equals(#C1, 0); + exp::Expect::equals(#C1, 0); } constants { diff --git a/pkg/front_end/testcases/const_functions/const_functions_assert_statements.dart.weak.outline.expect b/pkg/front_end/testcases/const_functions/const_functions_assert_statements.dart.weak.outline.expect index 786a90f306f..92461e98ac4 100644 --- a/pkg/front_end/testcases/const_functions/const_functions_assert_statements.dart.weak.outline.expect +++ b/pkg/front_end/testcases/const_functions/const_functions_assert_statements.dart.weak.outline.expect @@ -5,7 +5,10 @@ import "dart:core" as core; import "package:expect/expect.dart"; static const field core::int var1 = self::fn(); +static const field core::int var2 = self::fn2(); static method fn() → core::int ; +static method fn2() → core::int + ; static method main() → void ; diff --git a/pkg/front_end/testcases/const_functions/const_functions_assert_statements.dart.weak.transformed.expect b/pkg/front_end/testcases/const_functions/const_functions_assert_statements.dart.weak.transformed.expect index 8fa0afba088..6f5f7b1e0bc 100644 --- a/pkg/front_end/testcases/const_functions/const_functions_assert_statements.dart.weak.transformed.expect +++ b/pkg/front_end/testcases/const_functions/const_functions_assert_statements.dart.weak.transformed.expect @@ -6,13 +6,23 @@ import "package:expect/expect.dart" as exp; import "package:expect/expect.dart"; static const field core::int var1 = #C1; +static const field core::int var2 = #C1; static method fn() → core::int { core::int x = 0; assert(x.{core::num::==}(0), "fail"); return x; } +static method fn2() → core::int { + core::int x = 0; + assert((() → core::bool { + core::int y = x.{core::num::+}(1); + return y.{core::num::==}(1); + }).call()); + return x; +} static method main() → void { exp::Expect::equals(#C1, 0); + exp::Expect::equals(#C1, 0); } constants { diff --git a/pkg/front_end/testcases/const_functions/const_functions_closures.dart b/pkg/front_end/testcases/const_functions/const_functions_closures.dart new file mode 100644 index 00000000000..70ffa7c2cd8 --- /dev/null +++ b/pkg/front_end/testcases/const_functions/const_functions_closures.dart @@ -0,0 +1,91 @@ +// 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 closures with const functions. + +import "package:expect/expect.dart"; + +const var1 = foo(); +int foo() { + var f = () { + int count = 0; + int baz() { + ++count; + return count; + } + + return baz; + }; + var c1 = f(); + var c2 = f(); + + var c1_val1 = c1(); + assert(c1_val1 == 1); + var c1_val2 = c1(); + assert(c1_val2 == 2); + var c1_val3 = c1(); + assert(c1_val3 == 3); + + var c2_val1 = c2(); + assert(c1_val1 == 1); + var c2_val2 = c2(); + assert(c1_val2 == 2); + var c2_val3 = c2(); + assert(c1_val3 == 3); + + return 0; +} + +const var2 = fn(); +int fn() { + return (() => 0)(); +} + +const y = 1; +const var3 = fn3(); +int fn3() { + int y = 2; + return y; +} + +const var4 = fn4(); +int fn4() { + var x = 0; + int innerFn() { + return x; + } + + return innerFn(); +} + +const var5 = fn5(3); +int fn5(int a) { + int recurse(int b) { + if (b == 1) return 1; + int result = recurse(b - 1); + return b * result; + } + + return recurse(a); +} + +const var6 = fn6(4); +int fn6(int a) { + int recurse() { + a--; + if (a == 1) return 1; + return a * recurse(); + } + + return recurse(); +} + +void main() { + Expect.equals(var1, 0); + Expect.equals(var2, 0); + Expect.equals(var3, 2); + Expect.equals(var4, 0); + Expect.equals(var5, 6); + Expect.equals(var6, 6); +} diff --git a/pkg/front_end/testcases/const_functions/const_functions_closures.dart.strong.expect b/pkg/front_end/testcases/const_functions/const_functions_closures.dart.strong.expect new file mode 100644 index 00000000000..64a08b6b53a --- /dev/null +++ b/pkg/front_end/testcases/const_functions/const_functions_closures.dart.strong.expect @@ -0,0 +1,86 @@ +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 var2 = #C1; +static const field core::int y = #C2; +static const field core::int var3 = #C3; +static const field core::int var4 = #C1; +static const field core::int var5 = #C4; +static const field core::int var6 = #C4; +static method foo() → core::int { + () → () → core::int f = () → () → core::int { + core::int count = 0; + function baz() → core::int { + count = count.{core::num::+}(1); + return count; + } + return baz; + }; + () → core::int c1 = f.call(); + () → core::int c2 = f.call(); + core::int c1_val1 = c1.call(); + assert(c1_val1.{core::num::==}(1)); + core::int c1_val2 = c1.call(); + assert(c1_val2.{core::num::==}(2)); + core::int c1_val3 = c1.call(); + assert(c1_val3.{core::num::==}(3)); + core::int c2_val1 = c2.call(); + assert(c1_val1.{core::num::==}(1)); + core::int c2_val2 = c2.call(); + assert(c1_val2.{core::num::==}(2)); + core::int c2_val3 = c2.call(); + assert(c1_val3.{core::num::==}(3)); + return 0; +} +static method fn() → core::int { + return (() → core::int => 0).call(); +} +static method fn3() → core::int { + core::int y = 2; + return y; +} +static method fn4() → core::int { + core::int x = 0; + function innerFn() → core::int { + return x; + } + return innerFn.call(); +} +static method fn5(core::int a) → core::int { + function recurse(core::int b) → core::int { + if(b.{core::num::==}(1)) + return 1; + core::int result = recurse.call(b.{core::num::-}(1)); + return b.{core::num::*}(result); + } + return recurse.call(a); +} +static method fn6(core::int a) → core::int { + function recurse() → core::int { + a = a.{core::num::-}(1); + if(a.{core::num::==}(1)) + return 1; + return a.{core::num::*}(recurse.call()); + } + return recurse.call(); +} +static method main() → void { + exp::Expect::equals(#C1, 0); + exp::Expect::equals(#C1, 0); + exp::Expect::equals(#C3, 2); + exp::Expect::equals(#C1, 0); + exp::Expect::equals(#C4, 6); + exp::Expect::equals(#C4, 6); +} + +constants { + #C1 = 0 + #C2 = 1 + #C3 = 2 + #C4 = 6 +} diff --git a/pkg/front_end/testcases/const_functions/const_functions_closures.dart.strong.transformed.expect b/pkg/front_end/testcases/const_functions/const_functions_closures.dart.strong.transformed.expect new file mode 100644 index 00000000000..64a08b6b53a --- /dev/null +++ b/pkg/front_end/testcases/const_functions/const_functions_closures.dart.strong.transformed.expect @@ -0,0 +1,86 @@ +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 var2 = #C1; +static const field core::int y = #C2; +static const field core::int var3 = #C3; +static const field core::int var4 = #C1; +static const field core::int var5 = #C4; +static const field core::int var6 = #C4; +static method foo() → core::int { + () → () → core::int f = () → () → core::int { + core::int count = 0; + function baz() → core::int { + count = count.{core::num::+}(1); + return count; + } + return baz; + }; + () → core::int c1 = f.call(); + () → core::int c2 = f.call(); + core::int c1_val1 = c1.call(); + assert(c1_val1.{core::num::==}(1)); + core::int c1_val2 = c1.call(); + assert(c1_val2.{core::num::==}(2)); + core::int c1_val3 = c1.call(); + assert(c1_val3.{core::num::==}(3)); + core::int c2_val1 = c2.call(); + assert(c1_val1.{core::num::==}(1)); + core::int c2_val2 = c2.call(); + assert(c1_val2.{core::num::==}(2)); + core::int c2_val3 = c2.call(); + assert(c1_val3.{core::num::==}(3)); + return 0; +} +static method fn() → core::int { + return (() → core::int => 0).call(); +} +static method fn3() → core::int { + core::int y = 2; + return y; +} +static method fn4() → core::int { + core::int x = 0; + function innerFn() → core::int { + return x; + } + return innerFn.call(); +} +static method fn5(core::int a) → core::int { + function recurse(core::int b) → core::int { + if(b.{core::num::==}(1)) + return 1; + core::int result = recurse.call(b.{core::num::-}(1)); + return b.{core::num::*}(result); + } + return recurse.call(a); +} +static method fn6(core::int a) → core::int { + function recurse() → core::int { + a = a.{core::num::-}(1); + if(a.{core::num::==}(1)) + return 1; + return a.{core::num::*}(recurse.call()); + } + return recurse.call(); +} +static method main() → void { + exp::Expect::equals(#C1, 0); + exp::Expect::equals(#C1, 0); + exp::Expect::equals(#C3, 2); + exp::Expect::equals(#C1, 0); + exp::Expect::equals(#C4, 6); + exp::Expect::equals(#C4, 6); +} + +constants { + #C1 = 0 + #C2 = 1 + #C3 = 2 + #C4 = 6 +} diff --git a/pkg/front_end/testcases/const_functions/const_functions_closures.dart.textual_outline.expect b/pkg/front_end/testcases/const_functions/const_functions_closures.dart.textual_outline.expect new file mode 100644 index 00000000000..03a1b47113f --- /dev/null +++ b/pkg/front_end/testcases/const_functions/const_functions_closures.dart.textual_outline.expect @@ -0,0 +1,16 @@ +import "package:expect/expect.dart"; + +const var1 = foo(); +int foo() {} +const var2 = fn(); +int fn() {} +const y = 1; +const var3 = fn3(); +int fn3() {} +const var4 = fn4(); +int fn4() {} +const var5 = fn5(3); +int fn5(int a) {} +const var6 = fn6(4); +int fn6(int a) {} +void main() {} diff --git a/pkg/front_end/testcases/const_functions/const_functions_closures.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/const_functions/const_functions_closures.dart.textual_outline_modelled.expect new file mode 100644 index 00000000000..183315837d5 --- /dev/null +++ b/pkg/front_end/testcases/const_functions/const_functions_closures.dart.textual_outline_modelled.expect @@ -0,0 +1,16 @@ +import "package:expect/expect.dart"; + +const var1 = foo(); +const var2 = fn(); +const var3 = fn3(); +const var4 = fn4(); +const var5 = fn5(3); +const var6 = fn6(4); +const y = 1; +int fn() {} +int fn3() {} +int fn4() {} +int fn5(int a) {} +int fn6(int a) {} +int foo() {} +void main() {} diff --git a/pkg/front_end/testcases/const_functions/const_functions_closures.dart.weak.expect b/pkg/front_end/testcases/const_functions/const_functions_closures.dart.weak.expect new file mode 100644 index 00000000000..64a08b6b53a --- /dev/null +++ b/pkg/front_end/testcases/const_functions/const_functions_closures.dart.weak.expect @@ -0,0 +1,86 @@ +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 var2 = #C1; +static const field core::int y = #C2; +static const field core::int var3 = #C3; +static const field core::int var4 = #C1; +static const field core::int var5 = #C4; +static const field core::int var6 = #C4; +static method foo() → core::int { + () → () → core::int f = () → () → core::int { + core::int count = 0; + function baz() → core::int { + count = count.{core::num::+}(1); + return count; + } + return baz; + }; + () → core::int c1 = f.call(); + () → core::int c2 = f.call(); + core::int c1_val1 = c1.call(); + assert(c1_val1.{core::num::==}(1)); + core::int c1_val2 = c1.call(); + assert(c1_val2.{core::num::==}(2)); + core::int c1_val3 = c1.call(); + assert(c1_val3.{core::num::==}(3)); + core::int c2_val1 = c2.call(); + assert(c1_val1.{core::num::==}(1)); + core::int c2_val2 = c2.call(); + assert(c1_val2.{core::num::==}(2)); + core::int c2_val3 = c2.call(); + assert(c1_val3.{core::num::==}(3)); + return 0; +} +static method fn() → core::int { + return (() → core::int => 0).call(); +} +static method fn3() → core::int { + core::int y = 2; + return y; +} +static method fn4() → core::int { + core::int x = 0; + function innerFn() → core::int { + return x; + } + return innerFn.call(); +} +static method fn5(core::int a) → core::int { + function recurse(core::int b) → core::int { + if(b.{core::num::==}(1)) + return 1; + core::int result = recurse.call(b.{core::num::-}(1)); + return b.{core::num::*}(result); + } + return recurse.call(a); +} +static method fn6(core::int a) → core::int { + function recurse() → core::int { + a = a.{core::num::-}(1); + if(a.{core::num::==}(1)) + return 1; + return a.{core::num::*}(recurse.call()); + } + return recurse.call(); +} +static method main() → void { + exp::Expect::equals(#C1, 0); + exp::Expect::equals(#C1, 0); + exp::Expect::equals(#C3, 2); + exp::Expect::equals(#C1, 0); + exp::Expect::equals(#C4, 6); + exp::Expect::equals(#C4, 6); +} + +constants { + #C1 = 0 + #C2 = 1 + #C3 = 2 + #C4 = 6 +} diff --git a/pkg/front_end/testcases/const_functions/const_functions_closures.dart.weak.outline.expect b/pkg/front_end/testcases/const_functions/const_functions_closures.dart.weak.outline.expect new file mode 100644 index 00000000000..6be7921ce14 --- /dev/null +++ b/pkg/front_end/testcases/const_functions/const_functions_closures.dart.weak.outline.expect @@ -0,0 +1,27 @@ +library /*isNonNullableByDefault*/; +import self as self; +import "dart:core" as core; + +import "package:expect/expect.dart"; + +static const field core::int var1 = self::foo(); +static const field core::int var2 = self::fn(); +static const field core::int y = 1; +static const field core::int var3 = self::fn3(); +static const field core::int var4 = self::fn4(); +static const field core::int var5 = self::fn5(3); +static const field core::int var6 = self::fn6(4); +static method foo() → core::int + ; +static method fn() → core::int + ; +static method fn3() → core::int + ; +static method fn4() → core::int + ; +static method fn5(core::int a) → core::int + ; +static method fn6(core::int a) → core::int + ; +static method main() → void + ; diff --git a/pkg/front_end/testcases/const_functions/const_functions_closures.dart.weak.transformed.expect b/pkg/front_end/testcases/const_functions/const_functions_closures.dart.weak.transformed.expect new file mode 100644 index 00000000000..64a08b6b53a --- /dev/null +++ b/pkg/front_end/testcases/const_functions/const_functions_closures.dart.weak.transformed.expect @@ -0,0 +1,86 @@ +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 var2 = #C1; +static const field core::int y = #C2; +static const field core::int var3 = #C3; +static const field core::int var4 = #C1; +static const field core::int var5 = #C4; +static const field core::int var6 = #C4; +static method foo() → core::int { + () → () → core::int f = () → () → core::int { + core::int count = 0; + function baz() → core::int { + count = count.{core::num::+}(1); + return count; + } + return baz; + }; + () → core::int c1 = f.call(); + () → core::int c2 = f.call(); + core::int c1_val1 = c1.call(); + assert(c1_val1.{core::num::==}(1)); + core::int c1_val2 = c1.call(); + assert(c1_val2.{core::num::==}(2)); + core::int c1_val3 = c1.call(); + assert(c1_val3.{core::num::==}(3)); + core::int c2_val1 = c2.call(); + assert(c1_val1.{core::num::==}(1)); + core::int c2_val2 = c2.call(); + assert(c1_val2.{core::num::==}(2)); + core::int c2_val3 = c2.call(); + assert(c1_val3.{core::num::==}(3)); + return 0; +} +static method fn() → core::int { + return (() → core::int => 0).call(); +} +static method fn3() → core::int { + core::int y = 2; + return y; +} +static method fn4() → core::int { + core::int x = 0; + function innerFn() → core::int { + return x; + } + return innerFn.call(); +} +static method fn5(core::int a) → core::int { + function recurse(core::int b) → core::int { + if(b.{core::num::==}(1)) + return 1; + core::int result = recurse.call(b.{core::num::-}(1)); + return b.{core::num::*}(result); + } + return recurse.call(a); +} +static method fn6(core::int a) → core::int { + function recurse() → core::int { + a = a.{core::num::-}(1); + if(a.{core::num::==}(1)) + return 1; + return a.{core::num::*}(recurse.call()); + } + return recurse.call(); +} +static method main() → void { + exp::Expect::equals(#C1, 0); + exp::Expect::equals(#C1, 0); + exp::Expect::equals(#C3, 2); + exp::Expect::equals(#C1, 0); + exp::Expect::equals(#C4, 6); + exp::Expect::equals(#C4, 6); +} + +constants { + #C1 = 0 + #C2 = 1 + #C3 = 2 + #C4 = 6 +} diff --git a/tests/language/const_functions/const_functions_assert_statements_test.dart b/tests/language/const_functions/const_functions_assert_statements_test.dart index 1fe02bbe439..86c0fe7d029 100644 --- a/tests/language/const_functions/const_functions_assert_statements_test.dart +++ b/tests/language/const_functions/const_functions_assert_statements_test.dart @@ -17,6 +17,19 @@ int fn() { return x; } +const var2 = fn2(); +// ^^^^^ +// [analyzer] COMPILE_TIME_ERROR.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE +int fn2() { + int x = 0; + assert(() { + var y = x + 1; + return y == 1; + }()); + return x; +} + void main() { Expect.equals(var1, 0); + Expect.equals(var2, 0); } diff --git a/tests/language/const_functions/const_functions_closures_error_test.dart b/tests/language/const_functions/const_functions_closures_error_test.dart new file mode 100644 index 00000000000..13839833ffe --- /dev/null +++ b/tests/language/const_functions/const_functions_closures_error_test.dart @@ -0,0 +1,81 @@ +// 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 erroneous closure situations with const functions. + +// SharedOptions=--enable-experiment=const-functions + +import "package:expect/expect.dart"; + +var varVariable = 1; +const var1 = fn(); +// ^^^^ +// [analyzer] COMPILE_TIME_ERROR.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE +// [cfe] Constant evaluation error: +int fn() { + return varVariable; +} + +final finalVariable = 1; +const var2 = fn2(); +// ^^^^^ +// [analyzer] COMPILE_TIME_ERROR.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE +// [cfe] Constant evaluation error: +int fn2() { + return finalVariable; +} + +const var3 = fn3(); +// ^^^^^ +// [analyzer] COMPILE_TIME_ERROR.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE +int fn3() { + int innerFn() { + return x; + // ^ + // [analyzer] COMPILE_TIME_ERROR.REFERENCED_BEFORE_DECLARATION + } + + const x = 0; + // ^ + // [cfe] Can't declare 'x' because it was already used in this scope. + return innerFn(); +} + +const var4 = fn4(); +// ^^^^^ +// [analyzer] COMPILE_TIME_ERROR.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE +int fn4() { + var a = () { + return x; + // ^ + // [analyzer] COMPILE_TIME_ERROR.REFERENCED_BEFORE_DECLARATION + }; + var x = 1; + // ^ + // [cfe] Can't declare 'x' because it was already used in this scope. + return a(); +} + +const var5 = fn5(1); +// ^^^^^^ +// [analyzer] COMPILE_TIME_ERROR.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE +fn5(a) { + var a = () => a; + // ^ + // [cfe] Can't declare 'a' because it was already used in this scope. + // ^ + // [analyzer] COMPILE_TIME_ERROR.REFERENCED_BEFORE_DECLARATION + return a(); +} + +const x = 0; +void fn6() { + var x = 1; + int a() => x; + const z = a(); + // ^^^ + // [analyzer] COMPILE_TIME_ERROR.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE + // ^ + // [cfe] Constant evaluation error: +} diff --git a/tests/language/const_functions/const_functions_closures_test.dart b/tests/language/const_functions/const_functions_closures_test.dart new file mode 100644 index 00000000000..9cb6dccb06a --- /dev/null +++ b/tests/language/const_functions/const_functions_closures_test.dart @@ -0,0 +1,105 @@ +// 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 closures with const functions. + +// SharedOptions=--enable-experiment=const-functions + +import "package:expect/expect.dart"; + +const var1 = foo(); +// ^^^^^ +// [analyzer] COMPILE_TIME_ERROR.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE +int foo() { + var f = () { + int count = 0; + int baz() { + ++count; + return count; + } + + return baz; + }; + var c1 = f(); + var c2 = f(); + + var c1_val1 = c1(); + assert(c1_val1 == 1); + var c1_val2 = c1(); + assert(c1_val2 == 2); + var c1_val3 = c1(); + assert(c1_val3 == 3); + + var c2_val1 = c2(); + assert(c1_val1 == 1); + var c2_val2 = c2(); + assert(c1_val2 == 2); + var c2_val3 = c2(); + assert(c1_val3 == 3); + + return 0; +} + +const var2 = fn(); +// ^^^^ +// [analyzer] COMPILE_TIME_ERROR.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE +int fn() { + return (() => 0)(); +} + +const y = 1; +const var3 = fn3(); +// ^^^^^ +// [analyzer] COMPILE_TIME_ERROR.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE +int fn3() { + int y = 2; + return y; +} + +const var4 = fn4(); +// ^^^^^ +// [analyzer] COMPILE_TIME_ERROR.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE +int fn4() { + var x = 0; + int innerFn() { + return x; + } + + return innerFn(); +} + +const var5 = fn5(3); +// ^^^^^^ +// [analyzer] COMPILE_TIME_ERROR.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE +int fn5(int a) { + int recurse(int b) { + if (b == 1) return 1; + int result = recurse(b - 1); + return b * result; + } + + return recurse(a); +} + +const var6 = fn6(4); +// ^^^^^^ +// [analyzer] COMPILE_TIME_ERROR.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE +int fn6(int a) { + int recurse() { + a--; + if (a == 1) return 1; + return a * recurse(); + } + + return recurse(); +} + +void main() { + Expect.equals(var1, 0); + Expect.equals(var2, 0); + Expect.equals(var3, 2); + Expect.equals(var4, 0); + Expect.equals(var5, 6); + Expect.equals(var6, 6); +}