blob: dee8ee5c13fea31ca07f8acec499d7b848b9aad6 [file] [log] [blame]
// Copyright (c) 2015, 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.
library constant_expression_test;
import 'dart:async';
import 'package:async_helper/async_helper.dart';
import 'package:expect/expect.dart';
import 'package:compiler/src/common_elements.dart' show KElementEnvironment;
import 'package:compiler/src/compiler.dart';
import 'package:compiler/src/constants/expressions.dart';
import 'package:compiler/src/kernel/element_map_impl.dart';
import 'package:compiler/src/elements/entities.dart';
import '../helpers/memory_compiler.dart';
import 'constant_expression_evaluate_test.dart' show MemoryEnvironment;
class TestData {
/// Declarations needed for the [constants].
final String declarations;
/// Tested constants.
final List<ConstantData> constants;
const TestData(this.declarations, this.constants);
}
class ConstantData {
/// Source code for the constant expression.
final String code;
/// The expected constant expression kind.
final ConstantExpressionKind kind;
/// ConstantExpression.getText() result if different from [code].
final String text;
/// The expected instance type for ConstructedConstantExpression.
final String type;
/// The expected instance fields for ConstructedConstantExpression.
final Map<String, String> fields;
const ConstantData(String code, this.kind,
{String text, this.type, this.fields})
: this.code = code,
this.text = text ?? code;
}
const List<TestData> DATA = const [
const TestData('''
class Class<T, S> {
final a;
final b;
final c;
const Class(this.a, {this.b, this.c: true});
const Class.named([this.a, this.b = 0, this.c = 2]);
static const staticConstant = 0;
static staticFunction() {}
}
const t = true;
const f = false;
const toplevelConstant = 0;
toplevelFunction() {}
''', const [
const ConstantData('null', ConstantExpressionKind.NULL),
const ConstantData('false', ConstantExpressionKind.BOOL),
const ConstantData('true', ConstantExpressionKind.BOOL),
const ConstantData('0', ConstantExpressionKind.INT),
const ConstantData('0.0', ConstantExpressionKind.DOUBLE),
const ConstantData('"foo"', ConstantExpressionKind.STRING),
const ConstantData('1 + 2', ConstantExpressionKind.BINARY),
const ConstantData('1 == 2', ConstantExpressionKind.BINARY),
const ConstantData('1 != 2', ConstantExpressionKind.UNARY,
// a != b is encoded as !(a == b) by CFE.
text: '!(1 == 2)'),
const ConstantData('1 ?? 2', ConstantExpressionKind.BINARY),
const ConstantData('-(1)', ConstantExpressionKind.UNARY, text: '-1'),
const ConstantData('"foo".length', ConstantExpressionKind.STRING_LENGTH),
const ConstantData('identical(0, 1)', ConstantExpressionKind.IDENTICAL),
const ConstantData('"a" "b"', ConstantExpressionKind.CONCATENATE,
text: '"ab"'),
const ConstantData('identical', ConstantExpressionKind.FUNCTION),
const ConstantData('true ? 0 : 1', ConstantExpressionKind.CONDITIONAL),
const ConstantData('proxy', ConstantExpressionKind.FIELD),
const ConstantData('Object', ConstantExpressionKind.TYPE),
const ConstantData('#name', ConstantExpressionKind.SYMBOL),
const ConstantData('const []', ConstantExpressionKind.LIST),
const ConstantData('const [0, 1]', ConstantExpressionKind.LIST,
text: 'const <int>[0, 1]'),
const ConstantData('const <int>[0, 1]', ConstantExpressionKind.LIST),
const ConstantData('const <dynamic>[0, 1]', ConstantExpressionKind.LIST,
text: 'const [0, 1]'),
const ConstantData('const {}', ConstantExpressionKind.MAP),
const ConstantData('const {0: 1, 2: 3}', ConstantExpressionKind.MAP,
text: 'const <int, int>{0: 1, 2: 3}'),
const ConstantData(
'const <int, int>{0: 1, 2: 3}', ConstantExpressionKind.MAP),
const ConstantData(
'const <String, int>{"0": 1, "2": 3}', ConstantExpressionKind.MAP),
const ConstantData(
'const <String, dynamic>{"0": 1, "2": 3}', ConstantExpressionKind.MAP),
const ConstantData(
'const <dynamic, dynamic>{"0": 1, "2": 3}', ConstantExpressionKind.MAP,
text: 'const {"0": 1, "2": 3}'),
const ConstantData('const bool.fromEnvironment("foo", defaultValue: false)',
ConstantExpressionKind.BOOL_FROM_ENVIRONMENT),
const ConstantData('const int.fromEnvironment("foo", defaultValue: 42)',
ConstantExpressionKind.INT_FROM_ENVIRONMENT),
const ConstantData(
'const String.fromEnvironment("foo", defaultValue: "bar")',
ConstantExpressionKind.STRING_FROM_ENVIRONMENT),
const ConstantData('const Class(0)', ConstantExpressionKind.CONSTRUCTED),
const ConstantData(
'const Class(0, b: 1)', ConstantExpressionKind.CONSTRUCTED),
const ConstantData(
'const Class(0, c: 2)', ConstantExpressionKind.CONSTRUCTED),
const ConstantData(
'const Class(0, b: 3, c: 4)', ConstantExpressionKind.CONSTRUCTED),
const ConstantData(
'const Class.named()', ConstantExpressionKind.CONSTRUCTED),
const ConstantData(
'const Class.named(0)', ConstantExpressionKind.CONSTRUCTED),
const ConstantData(
'const Class.named(0, 1)', ConstantExpressionKind.CONSTRUCTED),
const ConstantData(
'const Class.named(0, 1, 2)', ConstantExpressionKind.CONSTRUCTED),
const ConstantData(
'const Class<String, int>(0)', ConstantExpressionKind.CONSTRUCTED),
const ConstantData(
'const Class<String, dynamic>(0)', ConstantExpressionKind.CONSTRUCTED),
const ConstantData(
'const Class<dynamic, String>(0)', ConstantExpressionKind.CONSTRUCTED),
const ConstantData(
'const Class<dynamic, dynamic>(0)', ConstantExpressionKind.CONSTRUCTED,
text: 'const Class(0)'),
const ConstantData('toplevelConstant', ConstantExpressionKind.FIELD),
const ConstantData('toplevelFunction', ConstantExpressionKind.FUNCTION),
const ConstantData('Class.staticConstant', ConstantExpressionKind.FIELD),
const ConstantData('Class.staticFunction', ConstantExpressionKind.FUNCTION),
const ConstantData('1 + 2', ConstantExpressionKind.BINARY),
const ConstantData('1 + 2 + 3', ConstantExpressionKind.BINARY),
const ConstantData('1 + -2', ConstantExpressionKind.BINARY),
const ConstantData('-1 + 2', ConstantExpressionKind.BINARY),
const ConstantData('(1 + 2) + 3', ConstantExpressionKind.BINARY,
text: '1 + 2 + 3'),
const ConstantData('1 + (2 + 3)', ConstantExpressionKind.BINARY,
text: '1 + 2 + 3'),
const ConstantData('1 * 2', ConstantExpressionKind.BINARY),
const ConstantData('1 * 2 + 3', ConstantExpressionKind.BINARY),
const ConstantData('1 * (2 + 3)', ConstantExpressionKind.BINARY),
const ConstantData('1 + 2 * 3', ConstantExpressionKind.BINARY),
const ConstantData('(1 + 2) * 3', ConstantExpressionKind.BINARY),
const ConstantData(
'false || identical(0, 1)', ConstantExpressionKind.BINARY),
const ConstantData('!identical(0, 1)', ConstantExpressionKind.UNARY),
const ConstantData(
'!identical(0, 1) || false', ConstantExpressionKind.BINARY),
const ConstantData(
'!(identical(0, 1) || false)', ConstantExpressionKind.UNARY),
const ConstantData('identical(0, 1) ? 3 * 4 + 5 : 6 + 7 * 8',
ConstantExpressionKind.CONDITIONAL),
const ConstantData('t ? f ? 0 : 1 : 2', ConstantExpressionKind.CONDITIONAL),
const ConstantData(
'(t ? t : f) ? f ? 0 : 1 : 2', ConstantExpressionKind.CONDITIONAL),
const ConstantData(
't ? t : f ? f ? 0 : 1 : 2', ConstantExpressionKind.CONDITIONAL),
const ConstantData(
't ? t ? t : t : t ? t : t', ConstantExpressionKind.CONDITIONAL),
const ConstantData(
't ? (t ? t : t) : (t ? t : t)', ConstantExpressionKind.CONDITIONAL,
text: 't ? t ? t : t : t ? t : t'),
const ConstantData(
'const [const <dynamic, dynamic>{0: true, "1": "c" "d"}, '
'const Class(const Class<dynamic, dynamic>(toplevelConstant))]',
ConstantExpressionKind.LIST,
text: 'const <Object>[const {0: true, "1": "cd"}, '
'const Class(const Class(toplevelConstant))]'),
]),
const TestData('''
class A {
const A();
}
class B {
final field1;
const B(this.field1);
}
class C extends B {
final field2;
const C({field1: 42, this.field2: false}) : super(field1);
const C.named([field = false]) : this(field1: field, field2: field);
}
''', const [
const ConstantData('const Object()', ConstantExpressionKind.CONSTRUCTED,
type: 'Object', fields: const {}),
const ConstantData('const A()', ConstantExpressionKind.CONSTRUCTED,
type: 'A', fields: const {}),
const ConstantData('const B(0)', ConstantExpressionKind.CONSTRUCTED,
type: 'B', fields: const {'field(B#field1)': '0'}),
const ConstantData('const B(const A())', ConstantExpressionKind.CONSTRUCTED,
type: 'B', fields: const {'field(B#field1)': 'const A()'}),
const ConstantData('const C()', ConstantExpressionKind.CONSTRUCTED,
type: 'C',
fields: const {
'field(B#field1)': '42',
'field(C#field2)': 'false',
}),
const ConstantData(
'const C(field1: 87)', ConstantExpressionKind.CONSTRUCTED,
type: 'C',
fields: const {
'field(B#field1)': '87',
'field(C#field2)': 'false',
}),
const ConstantData(
'const C(field2: true)', ConstantExpressionKind.CONSTRUCTED,
type: 'C',
fields: const {
'field(B#field1)': '42',
'field(C#field2)': 'true',
}),
const ConstantData('const C.named()', ConstantExpressionKind.CONSTRUCTED,
type: 'C',
fields: const {
'field(B#field1)': 'false',
'field(C#field2)': 'false',
}),
const ConstantData('const C.named(87)', ConstantExpressionKind.CONSTRUCTED,
type: 'C',
fields: const {
'field(B#field1)': '87',
'field(C#field2)': '87',
}),
]),
const TestData('''
class A<T> implements B<Null> {
final field1;
const A({this.field1:42});
}
class B<S> implements C<Null> {
const factory B({field1}) = A<B<S>>;
const factory B.named() = A<S>;
}
class C<U> {
const factory C({field1}) = A<B<double>>;
}
''', const [
const ConstantData('const A()', ConstantExpressionKind.CONSTRUCTED,
type: 'A<dynamic>', fields: const {'field(A#field1)': '42'}),
const ConstantData(
'const A<int>(field1: 87)', ConstantExpressionKind.CONSTRUCTED,
type: 'A<int>', fields: const {'field(A#field1)': '87'}),
const ConstantData('const B()', ConstantExpressionKind.CONSTRUCTED,
type: 'A<B<dynamic>>',
fields: const {
'field(A#field1)': '42',
},
// Redirecting factories are replaced by their effective targets by CFE.
text: 'const A<B<dynamic>>()'),
const ConstantData('const B<int>()', ConstantExpressionKind.CONSTRUCTED,
type: 'A<B<int>>',
fields: const {
'field(A#field1)': '42',
},
// Redirecting factories are replaced by their effective targets by CFE.
text: 'const A<B<int>>()'),
const ConstantData(
'const B<int>(field1: 87)', ConstantExpressionKind.CONSTRUCTED,
type: 'A<B<int>>',
fields: const {
'field(A#field1)': '87',
},
// Redirecting factories are replaced by their effective targets by CFE.
text: 'const A<B<int>>(field1: 87)'),
const ConstantData(
'const C<int>(field1: 87)', ConstantExpressionKind.CONSTRUCTED,
type: 'A<B<double>>',
fields: const {
'field(A#field1)': '87',
},
// Redirecting factories are replaced by their effective targets by CFE.
text: 'const A<B<double>>(field1: 87)'),
const ConstantData(
'const B<int>.named()', ConstantExpressionKind.CONSTRUCTED,
type: 'A<int>',
fields: const {
'field(A#field1)': '42',
},
// Redirecting factories are replaced by their effective targets by CFE.
text: 'const A<int>()'),
]),
const TestData('''
T identity<T>(T t) => t;
class C<T> {
final T defaultValue;
final T Function(T t) identityFunction;
const C(this.defaultValue, this.identityFunction);
}
''', const <ConstantData>[
const ConstantData('identity', ConstantExpressionKind.FUNCTION),
const ConstantData(
'const C<int>(0, identity)', ConstantExpressionKind.CONSTRUCTED,
type: 'C<int>', text: 'const C<int>(0, <int>(identity))'),
const ConstantData(
'const C<double>(0.5, identity)', ConstantExpressionKind.CONSTRUCTED,
type: 'C<double>', text: 'const C<double>(0.5, <double>(identity))'),
])
];
main() {
asyncTest(() async {
await runTest();
});
}
Future runTest() async {
for (TestData data in DATA) {
await testData(data);
}
}
Future testData(TestData data) async {
StringBuffer sb = new StringBuffer();
sb.writeln('${data.declarations}');
Map<String, ConstantData> constants = {};
List<String> names = <String>[];
data.constants.forEach((ConstantData constantData) {
String name = 'c${constants.length}';
names.add(name);
sb.writeln('const $name = ${constantData.code};');
constants[name] = constantData;
});
sb.writeln('main() {');
for (String name in names) {
sb.writeln(' print($name);');
}
sb.writeln('}');
String source = sb.toString();
CompilationResult result =
await runCompiler(memorySourceFiles: {'main.dart': source});
Compiler compiler = result.compiler;
KElementEnvironment elementEnvironment =
compiler.frontendStrategy.elementEnvironment;
MemoryEnvironment environment = new MemoryEnvironment(
new KernelEvaluationEnvironment(
(compiler.frontendStrategy as dynamic).elementMap,
compiler.environment,
null));
dynamic library = elementEnvironment.mainLibrary;
constants.forEach((String name, ConstantData data) {
FieldEntity field = elementEnvironment.lookupLibraryMember(library, name);
dynamic constant = elementEnvironment.getFieldConstantForTesting(field);
Expect.equals(
data.kind,
constant.kind,
"Unexpected kind '${constant.kind}' for constant "
"`${constant.toDartText()}`, expected '${data.kind}'.");
String text = data.text;
Expect.equals(
text,
constant.toDartText(),
"Unexpected text '${constant.toDartText()}' for constant, "
"expected '${text}'.");
if (data.type != null) {
String instanceType =
constant.computeInstanceType(environment).toString();
Expect.equals(
data.type,
instanceType,
"Unexpected type '$instanceType' for constant "
"`${constant.toDartText()}`, expected '${data.type}'.");
}
if (data.fields != null) {
Map instanceFields = constant.computeInstanceData(environment).fieldMap;
Expect.equals(
data.fields.length,
instanceFields.length,
"Unexpected field count ${instanceFields.length} for constant "
"`${constant.toDartText()}`, expected '${data.fields.length}'.");
instanceFields.forEach((field, expression) {
String name = '$field';
Expect.isTrue(name.startsWith('k:'));
name = name.substring(2).replaceAll('.', "#");
String expression = instanceFields[field].toDartText();
String expected = data.fields[name];
Expect.equals(
expected,
expression,
"Unexpected field expression ${expression} for field '$name' in "
"constant `${constant.toDartText()}`, expected '${expected}'.");
});
}
});
}