blob: 742508b7485e3e3a4206b6552e951b3d041656bd [file] [log] [blame]
// 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/library_index.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();
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('(kernel annotation bug)', () {
test('variable wihout initializer', () async {
await expectNotNull('$imports main() { @notNull var x; print(x); }',
''); // should be: '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); }',
''); // should be: 'x'
});
test('parameters', () async {
await expectNotNull(
'$imports f(@notNull x, [@notNull y, @notNull z = 42]) '
'{ x; y; z; }',
'42, z'); // should be: '42, x, y, z'
});
test('named parameters', () async {
await expectNotNull(
'$imports f({@notNull x, @notNull y: 42}) { x; y; }',
'42, y'); // should be: '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),
new LibraryIndex.coreLibraries(node)));
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;
}