[cfe] Handle structurally constant record literals

Closes #50022

Change-Id: I608ce2cd66a5d88c0c69636350b779c32569b056
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/260362
Commit-Queue: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Chloe Stefantsova <cstefantsova@google.com>
This commit is contained in:
Johnni Winther 2022-09-21 14:19:16 +00:00 committed by Commit Bot
parent 24c8a19439
commit 3e6050a158
19 changed files with 239 additions and 70 deletions

View file

@ -803,8 +803,42 @@ class ConstantsTransformer extends RemovingTransformer {
TreeNode visitRecordLiteral(RecordLiteral node, TreeNode? removalSentinel) {
if (node.isConst) {
return evaluateAndTransformWithContext(node, node);
} else {
// A record literal is a compile-time constant expression if and only
// if all its field expressions are compile-time constant expressions.
bool allConstant = true;
List<Constant> positional = [];
for (int i = 0; i < node.positional.length; i++) {
Expression result = transform(node.positional[i]);
node.positional[i] = result..parent = node;
if (allConstant && result is ConstantExpression) {
positional.add(result.constant);
} else {
allConstant = false;
}
}
Map<String, Constant> named = {};
for (NamedExpression expression in node.named) {
Expression result = transform(expression.value);
expression.value = result..parent = expression;
if (allConstant && result is ConstantExpression) {
named[expression.name] = result.constant;
} else {
allConstant = false;
}
}
if (allConstant) {
Constant constant = constantEvaluator.canonicalize(
new RecordConstant(positional, named, node.recordType));
return makeConstantExpression(constant, node);
}
return node;
}
return super.visitRecordLiteral(node, removalSentinel);
}
@override
@ -1505,12 +1539,12 @@ class ConstantEvaluator implements ExpressionVisitor<Constant> {
@override
Constant visitRecordLiteral(RecordLiteral node) {
if (!node.isConst) {
return createExpressionErrorConstant(
node,
templateNotConstantExpression
.withArguments('Non-constant record literal'));
}
// A record literal is a compile-time constant expression if and only
// if all its field expressions are compile-time constant expressions.
//
// This visitor is called when the context requires the literal to be
// constant, so we report an error on the expressions when these are not
// constants.
List<Constant>? positional = _evaluatePositionalArguments(node.positional);
if (positional == null) {

View file

@ -10,3 +10,7 @@ constants {
#C1 = 42
#C2 = <core::int>[#C1]
}
Extra constant evaluation status:
Evaluated: RecordLiteral @ org-dartlang-testcase:///issue50004.dart:6:7 -> RecordConstant(const (1, const <int>[42]))
Extra constant evaluation: evaluated: 1, effectively constant: 1

View file

@ -10,3 +10,7 @@ constants {
#C1 = 42
#C2 = <core::int*>[#C1]
}
Extra constant evaluation status:
Evaluated: RecordLiteral @ org-dartlang-testcase:///issue50004.dart:6:7 -> RecordConstant(const (1, const <int*>[42]))
Extra constant evaluation: evaluated: 1, effectively constant: 1

View file

@ -9,3 +9,12 @@ static method method() → dynamic {
(0, {a: "", b: true}).a{core::String};
(0, {a: "", b: true}).b{core::bool};
}
Extra constant evaluation status:
Evaluated: RecordLiteral @ org-dartlang-testcase:///record_get.dart:6:11 -> RecordConstant(const (0, ""))
Evaluated: RecordLiteral @ org-dartlang-testcase:///record_get.dart:7:11 -> RecordConstant(const (0, ""))
Evaluated: RecordLiteral @ org-dartlang-testcase:///record_get.dart:8:23 -> RecordConstant(const (0, {a: "", b: true}))
Evaluated: RecordLiteral @ org-dartlang-testcase:///record_get.dart:9:23 -> RecordConstant(const (0, {a: "", b: true}))
Evaluated: RecordLiteral @ org-dartlang-testcase:///record_get.dart:10:23 -> RecordConstant(const (0, {a: "", b: true}))
Extra constant evaluation: evaluated: 10, effectively constant: 5

View file

@ -9,3 +9,12 @@ static method method() → dynamic {
(0, {a: "", b: true}).a{core::String};
(0, {a: "", b: true}).b{core::bool};
}
Extra constant evaluation status:
Evaluated: RecordLiteral @ org-dartlang-testcase:///record_get.dart:6:11 -> RecordConstant(const (0, ""))
Evaluated: RecordLiteral @ org-dartlang-testcase:///record_get.dart:7:11 -> RecordConstant(const (0, ""))
Evaluated: RecordLiteral @ org-dartlang-testcase:///record_get.dart:8:23 -> RecordConstant(const (0, {a: "", b: true}))
Evaluated: RecordLiteral @ org-dartlang-testcase:///record_get.dart:9:23 -> RecordConstant(const (0, {a: "", b: true}))
Evaluated: RecordLiteral @ org-dartlang-testcase:///record_get.dart:10:23 -> RecordConstant(const (0, {a: "", b: true}))
Extra constant evaluation: evaluated: 10, effectively constant: 5

View file

@ -26,7 +26,7 @@ static method method() → dynamic {
(0, {b: 1});
({a: 0, b: 1});
let final core::int #t1 = 0 in (1, {a: #t1});
(#C1, #C2);
#C3;
}
static method sorting() → dynamic {
({a: 0, b: 1, c: 2, d: 3});
@ -44,4 +44,5 @@ static method sorting() → dynamic {
constants {
#C1 = TypeLiteralConstant(core::int)
#C2 = TypeLiteralConstant(core::String)
const (#C1, #C2)
}

View file

@ -26,7 +26,7 @@ static method method() → dynamic {
(0, {b: 1});
({a: 0, b: 1});
let final core::int #t1 = 0 in (1, {a: #t1});
(#C1, #C2);
#C3;
}
static method sorting() → dynamic {
({a: 0, b: 1, c: 2, d: 3});
@ -44,35 +44,22 @@ static method sorting() → dynamic {
constants {
#C1 = TypeLiteralConstant(core::int)
#C2 = TypeLiteralConstant(core::String)
const (#C1, #C2)
}
Extra constant evaluation status:
Evaluated: VariableGet @ org-dartlang-testcase:///record_literal.dart:10:7 -> IntConstant(0)
Evaluated: VariableGet @ org-dartlang-testcase:///record_literal.dart:16:7 -> IntConstant(0)
Evaluated: VariableGet @ org-dartlang-testcase:///record_literal.dart:16:13 -> IntConstant(1)
Evaluated: VariableGet @ org-dartlang-testcase:///record_literal.dart:16:19 -> IntConstant(2)
Evaluated: VariableGet @ org-dartlang-testcase:///record_literal.dart:17:7 -> IntConstant(0)
Evaluated: VariableGet @ org-dartlang-testcase:///record_literal.dart:17:19 -> IntConstant(2)
Evaluated: VariableGet @ org-dartlang-testcase:///record_literal.dart:17:13 -> IntConstant(1)
Evaluated: VariableGet @ org-dartlang-testcase:///record_literal.dart:18:13 -> IntConstant(1)
Evaluated: VariableGet @ org-dartlang-testcase:///record_literal.dart:18:19 -> IntConstant(2)
Evaluated: VariableGet @ org-dartlang-testcase:///record_literal.dart:18:7 -> IntConstant(0)
Evaluated: VariableGet @ org-dartlang-testcase:///record_literal.dart:20:4 -> IntConstant(0)
Evaluated: VariableGet @ org-dartlang-testcase:///record_literal.dart:20:7 -> IntConstant(1)
Evaluated: VariableGet @ org-dartlang-testcase:///record_literal.dart:20:13 -> IntConstant(2)
Evaluated: VariableGet @ org-dartlang-testcase:///record_literal.dart:21:4 -> IntConstant(0)
Evaluated: VariableGet @ org-dartlang-testcase:///record_literal.dart:21:7 -> IntConstant(1)
Evaluated: VariableGet @ org-dartlang-testcase:///record_literal.dart:21:13 -> IntConstant(2)
Evaluated: VariableGet @ org-dartlang-testcase:///record_literal.dart:21:19 -> IntConstant(3)
Evaluated: VariableGet @ org-dartlang-testcase:///record_literal.dart:22:4 -> IntConstant(0)
Evaluated: VariableGet @ org-dartlang-testcase:///record_literal.dart:22:7 -> IntConstant(1)
Evaluated: VariableGet @ org-dartlang-testcase:///record_literal.dart:22:13 -> IntConstant(2)
Evaluated: VariableGet @ org-dartlang-testcase:///record_literal.dart:22:19 -> IntConstant(3)
Evaluated: VariableGet @ org-dartlang-testcase:///record_literal.dart:22:25 -> IntConstant(4)
Evaluated: VariableGet @ org-dartlang-testcase:///record_literal.dart:23:4 -> IntConstant(0)
Evaluated: VariableGet @ org-dartlang-testcase:///record_literal.dart:23:13 -> IntConstant(2)
Evaluated: VariableGet @ org-dartlang-testcase:///record_literal.dart:23:10 -> IntConstant(1)
Evaluated: VariableGet @ org-dartlang-testcase:///record_literal.dart:24:10 -> IntConstant(1)
Evaluated: VariableGet @ org-dartlang-testcase:///record_literal.dart:24:13 -> IntConstant(2)
Evaluated: VariableGet @ org-dartlang-testcase:///record_literal.dart:24:7 -> IntConstant(0)
Extra constant evaluation: evaluated: 73, effectively constant: 28
Evaluated: RecordLiteral @ org-dartlang-testcase:///record_literal.dart:7:9 -> RecordConstant(const (0, 1))
Evaluated: RecordLiteral @ org-dartlang-testcase:///record_literal.dart:8:12 -> RecordConstant(const (0, {b: 1}))
Evaluated: RecordLiteral @ org-dartlang-testcase:///record_literal.dart:9:15 -> RecordConstant(const ({a: 0, b: 1}))
Evaluated: Let @ org-dartlang-testcase:///record_literal.dart:10:7 -> RecordConstant(const (1, {a: 0}))
Evaluated: RecordLiteral @ org-dartlang-testcase:///record_literal.dart:15:27 -> RecordConstant(const ({a: 0, b: 1, c: 2, d: 3}))
Evaluated: Let @ org-dartlang-testcase:///record_literal.dart:16:7 -> RecordConstant(const ({a: 0, b: 1, c: 3, d: 2}))
Evaluated: Let @ org-dartlang-testcase:///record_literal.dart:17:7 -> RecordConstant(const ({a: 0, b: 2, c: 3, d: 1}))
Evaluated: Let @ org-dartlang-testcase:///record_literal.dart:18:7 -> RecordConstant(const ({a: 1, b: 2, c: 3, d: 0}))
Evaluated: RecordLiteral @ org-dartlang-testcase:///record_literal.dart:19:30 -> RecordConstant(const (0, 1, 2, {a: 3, b: 4, c: 5}))
Evaluated: Let @ org-dartlang-testcase:///record_literal.dart:20:4 -> RecordConstant(const (0, 1, 3, {a: 2, b: 4, c: 5}))
Evaluated: Let @ org-dartlang-testcase:///record_literal.dart:21:4 -> RecordConstant(const (0, 1, 4, {a: 2, b: 3, c: 5}))
Evaluated: Let @ org-dartlang-testcase:///record_literal.dart:22:4 -> RecordConstant(const (0, 1, 5, {a: 2, b: 3, c: 4}))
Evaluated: Let @ org-dartlang-testcase:///record_literal.dart:23:4 -> RecordConstant(const (0, 2, 3, {a: 1, b: 4, c: 5}))
Evaluated: Let @ org-dartlang-testcase:///record_literal.dart:24:7 -> RecordConstant(const (1, 2, 3, {a: 0, b: 4, c: 5}))
Extra constant evaluation: evaluated: 16, effectively constant: 14

View file

@ -26,7 +26,7 @@ static method method() → dynamic {
(0, {b: 1});
({a: 0, b: 1});
let final core::int #t1 = 0 in (1, {a: #t1});
(#C1, #C2);
#C3;
}
static method sorting() → dynamic {
({a: 0, b: 1, c: 2, d: 3});
@ -44,4 +44,5 @@ static method sorting() → dynamic {
constants {
#C1 = TypeLiteralConstant(core::int*)
#C2 = TypeLiteralConstant(core::String*)
const (#C1, #C2)
}

View file

@ -26,7 +26,7 @@ static method method() → dynamic {
(0, {b: 1});
({a: 0, b: 1});
let final core::int #t1 = 0 in (1, {a: #t1});
(#C1, #C2);
#C3;
}
static method sorting() → dynamic {
({a: 0, b: 1, c: 2, d: 3});
@ -44,4 +44,5 @@ static method sorting() → dynamic {
constants {
#C1 = TypeLiteralConstant(core::int*)
#C2 = TypeLiteralConstant(core::String*)
const (#C1, #C2)
}

View file

@ -26,7 +26,7 @@ static method method() → dynamic {
(0, {b: 1});
({a: 0, b: 1});
let final core::int #t1 = 0 in (1, {a: #t1});
(#C1, #C2);
#C3;
}
static method sorting() → dynamic {
({a: 0, b: 1, c: 2, d: 3});
@ -44,35 +44,22 @@ static method sorting() → dynamic {
constants {
#C1 = TypeLiteralConstant(core::int*)
#C2 = TypeLiteralConstant(core::String*)
const (#C1, #C2)
}
Extra constant evaluation status:
Evaluated: VariableGet @ org-dartlang-testcase:///record_literal.dart:10:7 -> IntConstant(0)
Evaluated: VariableGet @ org-dartlang-testcase:///record_literal.dart:16:7 -> IntConstant(0)
Evaluated: VariableGet @ org-dartlang-testcase:///record_literal.dart:16:13 -> IntConstant(1)
Evaluated: VariableGet @ org-dartlang-testcase:///record_literal.dart:16:19 -> IntConstant(2)
Evaluated: VariableGet @ org-dartlang-testcase:///record_literal.dart:17:7 -> IntConstant(0)
Evaluated: VariableGet @ org-dartlang-testcase:///record_literal.dart:17:19 -> IntConstant(2)
Evaluated: VariableGet @ org-dartlang-testcase:///record_literal.dart:17:13 -> IntConstant(1)
Evaluated: VariableGet @ org-dartlang-testcase:///record_literal.dart:18:13 -> IntConstant(1)
Evaluated: VariableGet @ org-dartlang-testcase:///record_literal.dart:18:19 -> IntConstant(2)
Evaluated: VariableGet @ org-dartlang-testcase:///record_literal.dart:18:7 -> IntConstant(0)
Evaluated: VariableGet @ org-dartlang-testcase:///record_literal.dart:20:4 -> IntConstant(0)
Evaluated: VariableGet @ org-dartlang-testcase:///record_literal.dart:20:7 -> IntConstant(1)
Evaluated: VariableGet @ org-dartlang-testcase:///record_literal.dart:20:13 -> IntConstant(2)
Evaluated: VariableGet @ org-dartlang-testcase:///record_literal.dart:21:4 -> IntConstant(0)
Evaluated: VariableGet @ org-dartlang-testcase:///record_literal.dart:21:7 -> IntConstant(1)
Evaluated: VariableGet @ org-dartlang-testcase:///record_literal.dart:21:13 -> IntConstant(2)
Evaluated: VariableGet @ org-dartlang-testcase:///record_literal.dart:21:19 -> IntConstant(3)
Evaluated: VariableGet @ org-dartlang-testcase:///record_literal.dart:22:4 -> IntConstant(0)
Evaluated: VariableGet @ org-dartlang-testcase:///record_literal.dart:22:7 -> IntConstant(1)
Evaluated: VariableGet @ org-dartlang-testcase:///record_literal.dart:22:13 -> IntConstant(2)
Evaluated: VariableGet @ org-dartlang-testcase:///record_literal.dart:22:19 -> IntConstant(3)
Evaluated: VariableGet @ org-dartlang-testcase:///record_literal.dart:22:25 -> IntConstant(4)
Evaluated: VariableGet @ org-dartlang-testcase:///record_literal.dart:23:4 -> IntConstant(0)
Evaluated: VariableGet @ org-dartlang-testcase:///record_literal.dart:23:13 -> IntConstant(2)
Evaluated: VariableGet @ org-dartlang-testcase:///record_literal.dart:23:10 -> IntConstant(1)
Evaluated: VariableGet @ org-dartlang-testcase:///record_literal.dart:24:10 -> IntConstant(1)
Evaluated: VariableGet @ org-dartlang-testcase:///record_literal.dart:24:13 -> IntConstant(2)
Evaluated: VariableGet @ org-dartlang-testcase:///record_literal.dart:24:7 -> IntConstant(0)
Extra constant evaluation: evaluated: 73, effectively constant: 28
Evaluated: RecordLiteral @ org-dartlang-testcase:///record_literal.dart:7:9 -> RecordConstant(const (0, 1))
Evaluated: RecordLiteral @ org-dartlang-testcase:///record_literal.dart:8:12 -> RecordConstant(const (0, {b: 1}))
Evaluated: RecordLiteral @ org-dartlang-testcase:///record_literal.dart:9:15 -> RecordConstant(const ({a: 0, b: 1}))
Evaluated: Let @ org-dartlang-testcase:///record_literal.dart:10:7 -> RecordConstant(const (1, {a: 0}))
Evaluated: RecordLiteral @ org-dartlang-testcase:///record_literal.dart:15:27 -> RecordConstant(const ({a: 0, b: 1, c: 2, d: 3}))
Evaluated: Let @ org-dartlang-testcase:///record_literal.dart:16:7 -> RecordConstant(const ({a: 0, b: 1, c: 3, d: 2}))
Evaluated: Let @ org-dartlang-testcase:///record_literal.dart:17:7 -> RecordConstant(const ({a: 0, b: 2, c: 3, d: 1}))
Evaluated: Let @ org-dartlang-testcase:///record_literal.dart:18:7 -> RecordConstant(const ({a: 1, b: 2, c: 3, d: 0}))
Evaluated: RecordLiteral @ org-dartlang-testcase:///record_literal.dart:19:30 -> RecordConstant(const (0, 1, 2, {a: 3, b: 4, c: 5}))
Evaluated: Let @ org-dartlang-testcase:///record_literal.dart:20:4 -> RecordConstant(const (0, 1, 3, {a: 2, b: 4, c: 5}))
Evaluated: Let @ org-dartlang-testcase:///record_literal.dart:21:4 -> RecordConstant(const (0, 1, 4, {a: 2, b: 3, c: 5}))
Evaluated: Let @ org-dartlang-testcase:///record_literal.dart:22:4 -> RecordConstant(const (0, 1, 5, {a: 2, b: 3, c: 4}))
Evaluated: Let @ org-dartlang-testcase:///record_literal.dart:23:4 -> RecordConstant(const (0, 2, 3, {a: 1, b: 4, c: 5}))
Evaluated: Let @ org-dartlang-testcase:///record_literal.dart:24:7 -> RecordConstant(const (1, 2, 3, {a: 0, b: 4, c: 5}))
Extra constant evaluation: evaluated: 16, effectively constant: 14

View file

@ -0,0 +1,12 @@
// Copyright (c) 2022, 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.
void method1([a = (0, 1), b = (const <String>[], c: 'foo')]) {
(0, 1); // Const
(const <String>[], c: 'foo'); // Const
}
void method2({a = (0, 1), b = (const <String>[], c: 'foo')}) {
(<String>[], c: 'foo'); // Non-const
}

View file

@ -0,0 +1,20 @@
library /*isNonNullableByDefault*/;
import self as self;
import "dart:core" as core;
static method method1([dynamic a = #C3, dynamic b = #C6]) → void {
(0, 1);
(#C4, {c: "foo"});
}
static method method2({dynamic a = #C3, dynamic b = #C6}) → void {
(<core::String>[], {c: "foo"});
}
constants {
#C1 = 0
#C2 = 1
const (#C1, #C2)
#C4 = <core::String>[]
#C5 = "foo"
const (#C4, {c:#C5})
}

View file

@ -0,0 +1,25 @@
library /*isNonNullableByDefault*/;
import self as self;
import "dart:core" as core;
static method method1([dynamic a = #C3, dynamic b = #C6]) → void {
(0, 1);
(#C4, {c: "foo"});
}
static method method2({dynamic a = #C3, dynamic b = #C6}) → void {
(core::_GrowableList::•<core::String>(0), {c: "foo"});
}
constants {
#C1 = 0
#C2 = 1
const (#C1, #C2)
#C4 = <core::String>[]
#C5 = "foo"
const (#C4, {c:#C5})
}
Extra constant evaluation status:
Evaluated: RecordLiteral @ org-dartlang-testcase:///structurally_constant.dart:6:9 -> RecordConstant(const (0, 1))
Evaluated: RecordLiteral @ org-dartlang-testcase:///structurally_constant.dart:7:31 -> RecordConstant(const (const <String>[], {c: "foo"}))
Extra constant evaluation: evaluated: 4, effectively constant: 2

View file

@ -0,0 +1,2 @@
void method1([a = (0, 1), b = (const <String>[], c: 'foo')]) {}
void method2({a = (0, 1), b = (const <String>[], c: 'foo')}) {}

View file

@ -0,0 +1,20 @@
library /*isNonNullableByDefault*/;
import self as self;
import "dart:core" as core;
static method method1([dynamic a = #C3, dynamic b = #C6]) → void {
(0, 1);
(#C4, {c: "foo"});
}
static method method2({dynamic a = #C3, dynamic b = #C6}) → void {
(<core::String>[], {c: "foo"});
}
constants {
#C1 = 0
#C2 = 1
const (#C1, #C2)
#C4 = <core::String*>[]
#C5 = "foo"
const (#C4, {c:#C5})
}

View file

@ -0,0 +1,20 @@
library /*isNonNullableByDefault*/;
import self as self;
import "dart:core" as core;
static method method1([dynamic a = #C3, dynamic b = #C6]) → void {
(0, 1);
(#C4, {c: "foo"});
}
static method method2({dynamic a = #C3, dynamic b = #C6}) → void {
(<core::String>[], {c: "foo"});
}
constants {
#C1 = 0
#C2 = 1
const (#C1, #C2)
#C4 = <core::String*>[]
#C5 = "foo"
const (#C4, {c:#C5})
}

View file

@ -0,0 +1,7 @@
library /*isNonNullableByDefault*/;
import self as self;
static method method1([has-declared-initializer dynamic a, has-declared-initializer dynamic b]) → void
;
static method method2({has-declared-initializer dynamic a, has-declared-initializer dynamic b}) → void
;

View file

@ -0,0 +1,25 @@
library /*isNonNullableByDefault*/;
import self as self;
import "dart:core" as core;
static method method1([dynamic a = #C3, dynamic b = #C6]) → void {
(0, 1);
(#C4, {c: "foo"});
}
static method method2({dynamic a = #C3, dynamic b = #C6}) → void {
(core::_GrowableList::•<core::String>(0), {c: "foo"});
}
constants {
#C1 = 0
#C2 = 1
const (#C1, #C2)
#C4 = <core::String*>[]
#C5 = "foo"
const (#C4, {c:#C5})
}
Extra constant evaluation status:
Evaluated: RecordLiteral @ org-dartlang-testcase:///structurally_constant.dart:6:9 -> RecordConstant(const (0, 1))
Evaluated: RecordLiteral @ org-dartlang-testcase:///structurally_constant.dart:7:31 -> RecordConstant(const (const <String*>[], {c: "foo"}))
Extra constant evaluation: evaluated: 4, effectively constant: 2

View file

@ -149,6 +149,7 @@ records/record_literal_errors: FormatterCrash
records/record_type: FormatterCrash
records/record_type_errors: FormatterCrash
records/record_type_unsupported: FormatterCrash
records/structurally_constant: FormatterCrash
regress/ambiguous_builder_01: FormatterCrash
regress/issue_29942: FormatterCrash
regress/issue_29944: FormatterCrash