Files
sdk/pkg/dev_compiler/test/nullable_inference_test.dart
T
Jenny Messerly 29afbf8f12 Implement constant evaluation for dartdevk
This builds on the VM's support for constants, and brings dartdevk to
parity with dartdevc. Also, constants are computed with arithmetic
operations that match runtime behavior (JS numbers).

Constants are limited in some cases right now: when building outline
kernel files (via pkg/dev_compiler/tool/build_pkgs.dart), constant field
initializers are not preserved, and this fact is not recorded, making
these fields appear to be implicitly null. dartdevk will recognize this
and avoid inlining the constant in these cases.

Change-Id: I5e6d95ccc32799da3bec1667ed16078a1a87d0bd
Reviewed-on: https://dart-review.googlesource.com/53121
Commit-Queue: Jenny Messerly <jmesserly@google.com>
Reviewed-by: Vijay Menon <vsm@google.com>
2018-05-15 06:04:18 +00:00

585 lines
18 KiB
Dart

// Copyright (c) 2018, 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.
import 'dart:async';
import 'dart:io';
import 'package:front_end/src/api_prototype/memory_file_system.dart';
import 'package:front_end/src/api_unstable/ddc.dart' as fe;
import 'package:front_end/src/fasta/type_inference/type_schema_environment.dart';
import 'package:kernel/core_types.dart';
import 'package:kernel/kernel.dart';
import 'package:kernel/class_hierarchy.dart';
import 'package:test/test.dart';
import 'package:dev_compiler/src/kernel/command.dart';
import 'package:dev_compiler/src/kernel/nullable_inference.dart';
import 'package:dev_compiler/src/kernel/js_typerep.dart';
import 'package:dev_compiler/src/kernel/target.dart';
void main() {
test('empty main', () async {
await expectNotNull('main() {}', '');
});
group('literal', () {
test('null', () async {
await expectNotNull('main() { print(null); }', '');
});
test('bool', () async {
await expectNotNull('main() { print(false); }', 'false');
});
test('int', () async {
await expectNotNull('main() { print(42); }', '42');
});
test('double', () async {
await expectNotNull('main() { print(123.0); }', '123.0');
});
test('String', () async {
await expectNotNull('main() { print("hi"); }', '"hi"');
});
test('List', () async {
await expectNotNull(
'main() { print([42, null]); }', '<dart.core::int>[42, null], 42');
});
test('Map', () async {
await expectNotNull('main() { print({"x": null}); }',
'<dart.core::String, dart.core::Null>{"x": null}, "x"');
});
test('Symbol', () async {
await expectNotNull('main() { print(#hi); }', '#hi');
});
test('Type', () async {
await expectNotNull('main() { print(Object); }', 'dart.core::Object');
});
});
test('this', () async {
await expectNotNull('class C { m() { return this; } }', 'this');
});
test('is', () async {
await expectNotNull('main() { 42 is int; null is int; }',
'42 is dart.core::int, 42, null is dart.core::int');
});
test('as', () async {
await expectNotNull(
'main() { 42 as int; null as int; }', '42 as dart.core::int, 42');
});
test('constructor', () async {
await expectNotNull(
'library a; class C {} main() { new C(); }', 'new a::C::•()');
});
group('operator', () {
test('==', () async {
// This is not a correct non-null assumption when user-defined operators,
// are present, see: https://github.com/dart-lang/sdk/issues/31854
await expectAllNotNull('main() { 1 == 1; }');
});
test('!', () async {
await expectAllNotNull('main() { !false; }');
});
test('!=', () async {
await expectAllNotNull('main() { 1 != 2; }');
});
test('&&', () async {
await expectAllNotNull('main() { true && true; }');
});
test('||', () async {
await expectAllNotNull('main() { true || true; }');
});
test('? :', () async {
await expectAllNotNull('main() { true ? true : false; }');
});
});
test('bool', () async {
await expectAllNotNull('main() { true.toString(); false.hashCode; }');
});
group('int', () {
test('arithmetic', () async {
await expectAllNotNull(
'main() { -0; 1 + 2; 3 - 4; 5 * 6; 7 / 8; 9 % 10; 11 ~/ 12; }');
});
test('bitwise', () async {
await expectAllNotNull(
'main() { 1 & 2; 3 | 4; 5 ^ 6; ~7; 8 << 9; 10 >> 11; }');
});
test('comparison', () async {
await expectAllNotNull('main() { 1 < 2; 3 > 4; 5 <= 6; 7 >= 8; }');
});
test('getters', () async {
await expectAllNotNull(
'main() { 1.isOdd; 1.isEven; 1.isNegative; 1.isNaN; 1.isInfinite; '
'1.isFinite; 1.sign; 1.bitLength; 1.hashCode; }');
});
test('methods', () async {
await expectAllNotNull(
'main() { 1.compareTo(2); 1.remainder(2); 1.abs(); 1.toInt(); '
'1.ceil(); 1.floor(); 1.truncate(); 1.round(); 1.ceilToDouble(); '
'1.floorToDouble(); 1.truncateToDouble(); 1.roundToDouble(); '
'1.toDouble(); 1.clamp(2, 2); 1.toStringAsFixed(2); '
'1.toStringAsExponential(); 1.toStringAsPrecision(2); 1.toString(); '
'1.toRadixString(2); 1.toUnsigned(2); 1.toSigned(2); 1.modPow(2, 2); '
'1.modInverse(2); 1.gcd(2); }');
});
});
group('double', () {
test('arithmetic', () async {
await expectAllNotNull(
'main() { -0.0; 1.0 + 2.0; 3.0 - 4.0; 5.0 * 6.0; 7.0 / 8.0; '
'9.0 % 10.0; 11.0 ~/ 12.0; }');
});
test('comparison', () async {
await expectAllNotNull(
'main() { 1.0 < 2.0; 3.0 > 4.0; 5.0 <= 6.0; 7.0 >= 8.0; }');
});
test('getters', () async {
await expectAllNotNull(
'main() { (1.0).isNegative; (1.0).isNaN; (1.0).isInfinite; '
'(1.0).isFinite; (1.0).sign; (1.0).hashCode; }');
});
test('methods', () async {
await expectAllNotNull(
'main() { (1.0).compareTo(2.0); (1.0).remainder(2.0); (1.0).abs(); '
'(1.0).toInt(); (1.0).ceil(); (1.0).floor(); (1.0).truncate(); '
'(1.0).round(); (1.0).ceilToDouble(); (1.0).floorToDouble(); '
'(1.0).truncateToDouble(); (1.0).roundToDouble(); (1.0).toDouble(); '
'(1.0).clamp(2.0, 2.0); (1.0).toStringAsFixed(2); (1.0).toString(); '
'(1.0).toStringAsExponential(); (1.0).toStringAsPrecision(2); }');
});
});
group('num', () {
test('arithmetic', () async {
await expectAllNotNull(
'main() { num n = 1; -n; n + n; n - n; n * n; n / n; n % n; n % n; '
'n ~/ n; }');
});
test('comparison', () async {
await expectAllNotNull(
'main() { num n = 1; n < n; n > n; n <= n; n >= n; }');
});
test('getters', () async {
await expectAllNotNull(
'main() { num n = 1; n.isNegative; n.isNaN; n.isInfinite; '
'n.isFinite; n.sign; n.hashCode; }');
});
test('methods', () async {
await expectAllNotNull(
'main() { num n = 1; n.compareTo(n); n.remainder(n); n.abs(); '
'n.toInt(); n.ceil(); n.floor(); n.truncate(); '
'n.round(); n.ceilToDouble(); n.floorToDouble(); '
'n.truncateToDouble(); n.roundToDouble(); n.toDouble(); '
'n.clamp(n, n); n.toStringAsFixed(n); n.toString(); '
'n.toStringAsExponential(); n.toStringAsPrecision(n); }');
});
});
group('String', () {
test('concatenation', () async {
await expectAllNotNull('main() { "1" "2"; }');
});
test('interpolation', () async {
await expectAllNotNull('main() { "1${2}"; }');
});
test('getters', () async {
await expectAllNotNull(
'main() { "".codeUnits; "".hashCode; "".isEmpty; "".isNotEmpty; '
'"".length; "".runes; }');
});
test('operators', () async {
await expectAllNotNull('main() { "" + ""; "" * 2; "" == ""; "x"[0]; }');
});
test('methods', () async {
await expectAllNotNull('''main() {
String s = '';
s.codeUnitAt(0);
s.contains(s);
s.endsWith(s);
s.indexOf(s);
s.lastIndexOf(s);
s.padLeft(1);
s.padRight(1);
s.replaceAll(s, s);
s.replaceAllMapped(s, (_) => s);
s.replaceFirst(s, s);
s.replaceFirstMapped(s, (_) => s);
s.replaceRange(1, 2, s);
s.split(s);
s.splitMapJoin(s, onMatch: (_) => s, onNonMatch: (_) => s);
s.startsWith(s);
s.substring(1);
s.toLowerCase();
s.toUpperCase();
s.trim();
s.trimLeft();
s.trimRight();
// compareTo relies on the interface target being String.compareTo
// except that method does not exist unless we insert too many
// forwarding stubs.
//
// s.compareTo(s);
s.toString();
// Pattern methods (allMatches, matchAsPrefix) are not recognized.
}''');
});
});
test('identical', () async {
await expectNotNull('main() { identical(null, null); }',
'dart.core::identical(null, null)');
});
test('throw', () async {
await expectNotNull('main() { print(throw null); }', 'throw null');
});
test('rethrow', () async {
await expectNotNull('main() { try {} catch (e) { rethrow; } }', 'rethrow');
});
test('function expression', () async {
await expectNotNull(
'main() { () => null; f() {}; f; }', '() → dart.core::Null => null, f');
});
test('cascades (kernel let)', () async {
// `null..toString()` evaluates to `null` so it is nullable.
await expectNotNull('main() { null..toString(); }', '');
await expectAllNotNull('main() { 1..toString(); }');
});
group('variable', () {
test('declaration not-null', () async {
await expectNotNull('main() { var x = 42; print(x); }', '42, x');
});
test('declaration null', () async {
await expectNotNull('main() { var x = null; print(x); }', '');
});
test('declaration without initializer', () async {
await expectNotNull('main() { var x; x = 1; print(x); }', 'x = 1, 1');
});
test('assignment non-null', () async {
await expectNotNull(
'main() { var x = 42; x = 1; print(x); }', '42, x = 1, 1, x');
});
test('assignment null', () async {
await expectNotNull('main() { var x = 42; x = null; print(x); }', '42');
});
test('flow insensitive', () async {
await expectNotNull('''main() {
var x = 1;
if (true) {
print(x);
} else {
x = null;
print(x);
}
}''', '1, true');
});
test('declaration from variable', () async {
await expectNotNull('''main() {
var x = 1;
var y = x;
print(y);
x = null;
}''', '1');
});
test('declaration from variable nested', () async {
await expectNotNull('''main() {
var x = 1;
var y = (x = null) == null;
print(x);
print(y);
}''', '1, (x = null).{dart.core::Object::==}(null), y');
});
test('declaration from variable transitive', () async {
await expectNotNull('''main() {
var x = 1;
var y = x;
var z = y;
print(z);
x = null;
}''', '1');
});
test('declaration between variable transitive nested', () async {
await expectNotNull('''main() {
var x = 1;
var y = 1;
var z = y = x;
print(z);
x = null;
}''', '1, 1');
});
test('for not-null', () async {
await expectAllNotNull('''main() {
for (var i = 0; i < 10; i++) {
i;
}
}''');
});
test('for nullable', () async {
await expectNotNull(
'''main() {
for (var i = 0; i < 10; i++) {
if (i >= 10) i = null;
}
}''',
// arithmetic operation results on `i` are themselves not null, even
// though `i` is nullable.
'0, i.{dart.core::num::<}(10), 10, i = i.{dart.core::num::+}(1), '
'i.{dart.core::num::+}(1), 1, i.{dart.core::num::>=}(10), 10');
});
test('for-in', () async {
await expectNotNull('''main() {
for (var i in []) {
print(i);
}
}''', '<dynamic>[]');
});
test('inner functions', () async {
await expectNotNull('''main() {
var y = 0;
f(x) {
var g = () => print('g');
g();
print(x);
print(y);
var z = 1;
print(z);
}
f(42);
}''', '0, () → void => dart.core::print("g"), "g", g, y, 1, z, f, 42');
});
test('assignment to closure variable', () async {
await expectNotNull('''main() {
var y = 0;
f(x) {
y = x;
}
f(42);
print(y);
}''', '0, f, 42');
});
test('declaration visits initializer', () async {
await expectAllNotNull('''main() {
var x = () { var y = 1; return y; };
x;
}''');
});
test('assignment visits value', () async {
await expectAllNotNull('''main() {
var x = () => 42;
x = () { var y = 1; return y; };
}''');
});
test('assignment visits value with closure variable set', () async {
await expectNotNull('''main() {
var x = () => 42;
var y = (() => x = null);
}''', '() → dart.core::int => 42, 42, () → dart.core::Null => x = null');
});
test('do not depend on unrelated variables', () async {
await expectNotNull('''main() {
var x;
var y = identical(x, null);
y; // this is still non-null even though `x` is nullable
}''', 'dart.core::identical(x, null), y');
});
test('do not depend on unrelated variables updated later', () async {
await expectNotNull('''main() {
var x = 1;
var y = identical(x, 1);
x = null;
y; // this is still non-null even though `x` is nullable
}''', '1, dart.core::identical(x, 1), 1, y');
});
});
group('notNull', () {
setUp(() {
useAnnotations = true;
});
tearDown(() {
useAnnotations = false;
});
var imports = "import 'package:meta/meta.dart';";
group('(previously known kernel annotation bug)', () {
test('variable without initializer', () async {
await expectNotNull(
'$imports main() { @notNull var x; print(x); }', 'x');
});
test('variable with initializer', () async {
// TODO(jmesserly): this does not work in the Analyzer backend.
await expectNotNull(
'$imports main() { @notNull var x = null; print(x); }', 'x');
});
test('parameters', () async {
await expectNotNull(
'$imports f(@notNull x, [@notNull y, @notNull z = 42]) '
'{ x; y; z; }',
'42, x, y, z');
});
test('named parameters', () async {
await expectNotNull(
'$imports f({@notNull x, @notNull y: 42}) { x; y; }', '42, x, y');
});
});
test('top-level field', () async {
await expectNotNull(
'library a; $imports @notNull int x; main() { x; }', 'a::x');
});
test('getter', () async {
await expectNotNull(
'library b; $imports @notNull get x => null; main() { x; }', 'b::x');
});
test('function', () async {
await expectNotNull(
'library a; $imports @notNull f() {} main() { f(); }', 'a::f()');
});
test('method', () async {
await expectNotNull(
'library b; $imports class C { @notNull m() {} } '
'main() { var c = new C(); c.m(); }',
'new b::C::•(), c.{b::C::m}(), c');
});
});
}
/// Given the Dart [code], expects the [expectedNotNull] kernel expression list
/// to be produced in the set of expressions that cannot be null by DDC's null
/// inference.
Future expectNotNull(String code, String expectedNotNull) async {
var component = await kernelCompile(code);
var collector = new NotNullCollector();
component.accept(collector);
var actualNotNull =
collector.notNullExpressions.map((e) => e.toString()).join(', ');
expect(actualNotNull, equals(expectedNotNull));
}
/// Given the Dart [code], expects all the expressions inferred to be not-null.
Future expectAllNotNull(String code) async {
(await kernelCompile(code)).accept(new ExpectAllNotNull());
}
bool useAnnotations = false;
NullableInference inference;
class _TestRecursiveVisitor extends RecursiveVisitor<void> {
int _functionNesting = 0;
@override
visitComponent(Component node) {
inference ??= new NullableInference(new JSTypeRep(
new TypeSchemaEnvironment(
new CoreTypes(node), new ClassHierarchy(node), true),
));
if (useAnnotations) {
inference.allowNotNullDeclarations = useAnnotations;
inference.allowPackageMetaAnnotations = useAnnotations;
}
super.visitComponent(node);
}
@override
visitLibrary(Library node) {
if (node.isExternal ||
node.importUri.scheme == 'package' &&
node.importUri.pathSegments[0] == 'meta') {
return;
}
super.visitLibrary(node);
}
@override
visitFunctionNode(FunctionNode node) {
_functionNesting++;
if (_functionNesting == 1) inference.enterFunction(node);
super.visitFunctionNode(node);
if (_functionNesting == 1) inference.exitFunction(node);
_functionNesting--;
}
}
class NotNullCollector extends _TestRecursiveVisitor {
final notNullExpressions = <Expression>[];
@override
defaultExpression(Expression node) {
if (!inference.isNullable(node)) {
notNullExpressions.add(node);
}
super.defaultExpression(node);
}
}
class ExpectAllNotNull extends _TestRecursiveVisitor {
@override
defaultExpression(Expression node) {
expect(inference.isNullable(node), false,
reason: 'expression `$node` should be inferred as not-null');
super.defaultExpression(node);
}
}
fe.InitializedCompilerState _compilerState;
final _fileSystem = new MemoryFileSystem(new Uri.file('/memory/'));
Future<Component> kernelCompile(String code) async {
var succeeded = true;
void errorHandler(fe.CompilationMessage error) {
if (error.severity == fe.Severity.error) {
succeeded = false;
}
}
var sdkUri = new Uri.file('/memory/dart_sdk.dill');
var sdkFile = _fileSystem.entityForUri(sdkUri);
if (!await sdkFile.exists()) {
sdkFile.writeAsBytesSync(new File(defaultSdkSummaryPath).readAsBytesSync());
}
var packagesUri = new Uri.file('/memory/.packages');
var packagesFile = _fileSystem.entityForUri(packagesUri);
if (!await packagesFile.exists()) {
packagesFile.writeAsStringSync('meta:/memory/meta/lib');
_fileSystem
.entityForUri(new Uri.file('/memory/meta/lib/meta.dart'))
.writeAsStringSync('''
class _NotNull { const _NotNull(); }
const notNull = const _NotNull();
class _NullCheck { const _NullCheck(); }
const nullCheck = const _NullCheck();
''');
}
var mainUri = new Uri.file('/memory/test.dart');
_fileSystem.entityForUri(mainUri).writeAsStringSync(code);
_compilerState = await fe.initializeCompiler(
_compilerState, sdkUri, packagesUri, [], new DevCompilerTarget(),
fileSystem: _fileSystem);
fe.DdcResult result =
await fe.compile(_compilerState, [mainUri], errorHandler);
expect(succeeded, true);
return result.component;
}