blob: 1e1f94dd04b36ed578e517c85c5100cdf19cd15c [file] [log] [blame]
// 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.
// @dart = 2.9
library dev_compiler.test.expression_compiler;
import 'package:test/test.dart';
import 'expression_compiler_e2e_suite.dart';
const simpleClassSource = '''
extension NumberParsing on String {
int parseInt() {
return int.parse(this) + 1;
}
}
class C {
static int staticField = 1;
static int _staticField = 2;
static int _unusedStaticField = 3;
int field;
int _field;
int _unusedField = 4;
C(this.field, this._field) {
int y = 1;
// Breakpoint: constructorBP
var nop;
}
int methodFieldAccess(int x) {
// Breakpoint: methodBP
var inScope = 1;
{
var innerInScope = global + staticField + field;
// Breakpoint: innerScopeBP
var innerNotInScope = 2;
}
var notInScope = 3;
return x + _field + _staticField;
}
Future<int> asyncMethod(int x) async {
return x + _field + _staticField;
}
}
int global = 42;
main() {
int x = 15;
var c = C(5, 6);
// Breakpoint: globalFunctionBP
c.methodFieldAccess(10);
}
''';
void runSharedTests(SetupCompilerOptions setup, TestDriver driver) {
group('Expression compiler scope collection tests', () {
var source = simpleClassSource;
setUpAll(() async {
await driver.initSource(setup, source);
});
tearDownAll(() async {
await driver.cleanupTest();
});
test('local in scope', () async {
await driver.check(
breakpointId: 'innerScopeBP',
expression: 'inScope',
expectedResult: '1');
});
test('local in inner scope', () async {
await driver.check(
breakpointId: 'innerScopeBP',
expression: 'innerInScope',
expectedResult: '48');
});
test('global in scope', () async {
await driver.check(
breakpointId: 'innerScopeBP',
expression: 'global',
expectedResult: '42');
});
test('static field in scope', () async {
await driver.check(
breakpointId: 'innerScopeBP',
expression: 'staticField',
expectedResult: '1');
});
test('field in scope', () async {
await driver.check(
breakpointId: 'innerScopeBP',
expression: 'field',
expectedResult: '5');
});
test('parameter in scope', () async {
await driver.check(
breakpointId: 'innerScopeBP', expression: 'x', expectedResult: '10');
});
test('local not in scope', () async {
await driver.check(
breakpointId: 'innerScopeBP',
expression: 'notInScope',
expectedError: "Error: The getter 'notInScope' isn't defined for the"
" class 'C'.");
});
test('local not in inner scope', () async {
await driver.check(
breakpointId: 'innerScopeBP',
expression: 'innerNotInScope',
expectedError:
"Error: The getter 'innerNotInScope' isn't defined for the"
" class 'C'.");
});
});
group('Expression compiler extension symbols tests', () {
var source = '''
main() {
List<int> list = [];
list.add(0);
// Breakpoint: bp
}
''';
setUpAll(() async {
await driver.initSource(setup, source);
});
tearDownAll(() async {
await driver.cleanupTest();
});
test('extension symbol used only in expression compilation', () async {
await driver.check(
breakpointId: 'bp', expression: 'list.first', expectedResult: '0');
});
test('extension symbol used in original compilation', () async {
await driver.check(
breakpointId: 'bp',
expression: '() { list.add(1); return list.last; }()',
expectedResult: '1');
});
});
group('Expression compiler tests in extension method:', () {
var source = '''
extension NumberParsing on String {
int parseInt() {
var ret = int.parse(this);
// Breakpoint: bp
return ret;
}
}
main() => "1234".parseInt();
''';
setUpAll(() async {
await driver.initSource(setup, source);
});
tearDownAll(() {
driver.cleanupTest();
});
test('compilation error', () async {
await driver.check(
breakpointId: 'bp',
expression: 'typo',
expectedError: "Error: Getter not found: 'typo'");
});
test('local (trimmed scope)', () async {
await driver.check(
breakpointId: 'bp', expression: 'ret', expectedResult: '1234');
});
test('this (full scope)', () async {
// Note: this currently fails due to
// - incremental compiler not mapping 'this' from user input to '#this'
// - incremental compiler not allowing #this as a parameter name
await driver.check(
breakpointId: 'bp',
expression: 'this',
expectedError: "Error: Expected identifier, but got 'this'");
});
});
group('Expression compiler tests in static function:', () {
var source = '''
int foo(int x, {int y: 0}) {
int z = 3;
// Breakpoint: bp
return x + y + z;
}
main() => foo(1, y: 2);
''';
setUpAll(() async {
await driver.initSource(setup, source);
});
tearDownAll(() {
driver.cleanupTest();
});
test('compilation error', () async {
await driver.check(
breakpointId: 'bp',
expression: 'typo',
expectedError: "Getter not found: \'typo\'");
});
test('local', () async {
await driver.check(
breakpointId: 'bp', expression: 'x', expectedResult: '1');
});
test('formal', () async {
await driver.check(
breakpointId: 'bp', expression: 'y', expectedResult: '2');
});
test('named formal', () async {
await driver.check(
breakpointId: 'bp', expression: 'z', expectedResult: '3');
});
test('function', () async {
await driver
.check(breakpointId: 'bp', expression: 'main', expectedResult: '''
function main() {
return test.foo(1, {y: 2});
}''');
});
});
group('Expression compiler tests in method:', () {
var source = simpleClassSource;
setUpAll(() async {
await driver.initSource(setup, source);
});
tearDownAll(() {
driver.cleanupTest();
});
test('compilation error', () async {
await driver.check(
breakpointId: 'methodBP',
expression: 'typo',
expectedError: "The getter 'typo' isn't defined for the class 'C'");
});
test('local', () async {
await driver.check(
breakpointId: 'methodBP', expression: 'x', expectedResult: '10');
});
test('this', () async {
await driver.check(
breakpointId: 'methodBP',
expression: 'this',
expectedResult:
'test.C.new {Symbol(_unusedField): 4, Symbol(C.field): 5,'
' Symbol(_field): 6}');
});
test('expression using locals', () async {
await driver.check(
breakpointId: 'methodBP', expression: 'x + 1', expectedResult: '11');
});
test('expression using static fields', () async {
await driver.check(
breakpointId: 'methodBP',
expression: 'x + staticField',
expectedResult: '11');
});
test('expression using private static fields', () async {
await driver.check(
breakpointId: 'methodBP',
expression: 'x + _staticField',
expectedResult: '12');
});
test('expression using fields', () async {
await driver.check(
breakpointId: 'methodBP',
expression: 'x + field',
expectedResult: '15');
});
test('expression using private fields', () async {
await driver.check(
breakpointId: 'methodBP',
expression: 'x + _field',
expectedResult: '16');
});
test('expression using globals', () async {
await driver.check(
breakpointId: 'methodBP',
expression: 'x + global',
expectedResult: '52');
});
test('expression using fields not referred to in the original code',
() async {
await driver.check(
breakpointId: 'methodBP',
expression: '_unusedField + _unusedStaticField',
expectedResult: '7');
});
test('private field modification', () async {
await driver.check(
breakpointId: 'methodBP',
expression: '_field = 2',
expectedResult: '2');
});
test('field modification', () async {
await driver.check(
breakpointId: 'methodBP',
expression: 'field = 3',
expectedResult: '3');
});
test('private static field modification', () async {
await driver.check(
breakpointId: 'methodBP',
expression: '_staticField = 4',
expectedResult: '4');
});
test('static field modification', () async {
await driver.check(
breakpointId: 'methodBP',
expression: 'staticField = 5',
expectedResult: '5');
});
});
group('Expression compiler tests in global function:', () {
var source = simpleClassSource;
setUpAll(() async {
await driver.initSource(setup, source);
});
tearDownAll(() {
driver.cleanupTest();
});
test('compilation error', () async {
await driver.check(
breakpointId: 'globalFunctionBP',
expression: 'typo',
expectedError: "Getter not found: 'typo'.");
});
test('local with primitive type', () async {
await driver.check(
breakpointId: 'globalFunctionBP',
expression: 'x',
expectedResult: '15');
});
test('local object', () async {
await driver.check(
breakpointId: 'globalFunctionBP',
expression: 'c',
expectedResult:
'test.C.new {Symbol(_unusedField): 4, Symbol(C.field): 5, '
'Symbol(_field): 6}');
});
test('create new object', () async {
await driver.check(
breakpointId: 'globalFunctionBP',
expression: 'C(3, 4)',
expectedResult:
'test.C.new {Symbol(_unusedField): 4, Symbol(C.field): 3, '
'Symbol(_field): 4}');
});
test('access field of new object', () async {
await driver.check(
breakpointId: 'globalFunctionBP',
expression: 'C(3, 4)._field',
expectedResult: '4');
});
test('access static field', () async {
await driver.check(
breakpointId: 'globalFunctionBP',
expression: 'C.staticField',
expectedResult: '1');
});
test('expression using private static fields', () async {
await driver.check(
breakpointId: 'globalFunctionBP',
expression: 'C._staticField',
expectedError: "Error: Getter not found: '_staticField'.");
});
test('access field', () async {
await driver.check(
breakpointId: 'globalFunctionBP',
expression: 'c.field',
expectedResult: '5');
});
test('access private field', () async {
await driver.check(
breakpointId: 'globalFunctionBP',
expression: 'c._field',
expectedResult: '6');
});
test('method call', () async {
await driver.check(
breakpointId: 'globalFunctionBP',
expression: 'c.methodFieldAccess(2)',
expectedResult: '10');
});
test('async method call', () async {
await driver.check(
breakpointId: 'globalFunctionBP',
expression: 'c.asyncMethod(2).runtimeType.toString()',
expectedResult: '_Future<int>');
});
test('extension method call', () async {
await driver.check(
breakpointId: 'globalFunctionBP',
expression: '"1234".parseInt()',
expectedResult: '1235');
});
test('private field modification', () async {
await driver.check(
breakpointId: 'globalFunctionBP',
expression: 'c._field = 10',
expectedResult: '10');
});
test('field modification', () async {
await driver.check(
breakpointId: 'globalFunctionBP',
expression: 'c._field = 11',
expectedResult: '11');
});
test('private static field modification', () async {
await driver.check(
breakpointId: 'globalFunctionBP',
expression: 'C._staticField = 2',
expectedError: "Setter not found: '_staticField'.");
});
test('static field modification', () async {
await driver.check(
breakpointId: 'globalFunctionBP',
expression: 'C.staticField = 20',
expectedResult: '20');
});
test('call global function from core library', () async {
await driver.check(
breakpointId: 'globalFunctionBP',
expression: 'identical(1, 1)',
expectedResult: 'true');
});
});
group('Expression compiler tests in constructor:', () {
var source = simpleClassSource;
setUpAll(() async {
await driver.initSource(setup, source);
});
tearDownAll(() {
driver.cleanupTest();
});
test('compilation error', () async {
await driver.check(
breakpointId: 'constructorBP',
expression: 'typo',
expectedError: "The getter 'typo' isn't defined for the class 'C'");
});
test('local', () async {
await driver.check(
breakpointId: 'constructorBP', expression: 'y', expectedResult: '1');
});
test('this', () async {
await driver.check(
breakpointId: 'constructorBP',
expression: 'this',
expectedResult:
'test.C.new {Symbol(_unusedField): 4, Symbol(C.field): 5,'
' Symbol(_field): 6}');
});
test('expression using locals', () async {
await driver.check(
breakpointId: 'constructorBP',
expression: 'y + 1',
expectedResult: '2');
});
test('expression using static fields', () async {
await driver.check(
breakpointId: 'constructorBP',
expression: 'y + staticField',
expectedResult: '2');
});
test('expression using private static fields', () async {
await driver.check(
breakpointId: 'constructorBP',
expression: 'y + _staticField',
expectedResult: '3');
});
test('expression using fields', () async {
await driver.check(
breakpointId: 'constructorBP',
expression: 'y + field',
expectedResult: '6');
});
test('expression using private fields', () async {
await driver.check(
breakpointId: 'constructorBP',
expression: 'y + _field',
expectedResult: '7');
});
test('expression using globals', () async {
await driver.check(
breakpointId: 'constructorBP',
expression: 'y + global',
expectedResult: '43');
});
test('method call', () async {
await driver.check(
breakpointId: 'constructorBP',
expression: 'methodFieldAccess(2)',
expectedResult: '10');
});
test('async method call', () async {
await driver.check(
breakpointId: 'constructorBP',
expression: 'asyncMethod(2).runtimeType.toString()',
expectedResult: '_Future<int>');
});
test('extension method call', () async {
await driver.check(
breakpointId: 'constructorBP',
expression: '"1234".parseInt()',
expectedResult: '1235');
});
test('private field modification', () async {
await driver.check(
breakpointId: 'constructorBP',
expression: '_field = 2',
expectedResult: '2');
});
test('field modification', () async {
await driver.check(
breakpointId: 'constructorBP',
expression: 'field = 2',
expectedResult: '2');
});
test('private static field modification', () async {
await driver.check(
breakpointId: 'constructorBP',
expression: '_staticField = 2',
expectedResult: '2');
});
test('static field modification', () async {
await driver.check(
breakpointId: 'constructorBP',
expression: 'staticField = 2',
expectedResult: '2');
});
});
group('Expression compiler tests in async method:', () {
var source = '''
class C {
static int staticField = 1;
static int _staticField = 2;
int _field;
int field;
C(this.field, this._field);
Future<int> asyncMethod(int x) async {
// Breakpoint: bp
return x + global + _field + field + staticField + _staticField;
}
}
Future<int> entrypoint() async {
var c = C(5, 7);
// Breakpoint: bp1
return await c.asyncMethod(1);
}
int global = 42;
void main() async {
await entrypoint();
}
''';
setUpAll(() async {
await driver.initSource(setup, source);
});
tearDownAll(() {
driver.cleanupTest();
});
test('compilation error', () async {
await driver.check(
breakpointId: 'bp',
expression: 'typo',
expectedError: "The getter 'typo' isn't defined for the class 'C'");
});
test('local', () async {
await driver.check(
breakpointId: 'bp', expression: 'x', expectedResult: '1');
});
test('this', () async {
await driver.check(
breakpointId: 'bp',
expression: 'this',
expectedResult: 'test.C.new {Symbol(C.field): 5, Symbol(_field): 7}');
});
test('awaited method call', () async {
await driver.check(
breakpointId: 'bp1',
expression: 'c.asyncMethod(1).runtimeType.toString()',
expectedResult: '_Future<int>');
}, skip: "'await' is not yet supported in expression evaluation.");
test('awaited method call', () async {
await driver.check(
breakpointId: 'bp1',
expression: 'await c.asyncMethod(1)',
expectedResult: '58');
}, skip: "'await' is not yet supported in expression evaluation.");
});
group('Expression compiler tests in closures:', () {
var source = '''
void globalFunction() {
int x = 15;
var outerClosure = (int y) {
var closureCaptureInner = (int z) {
// Breakpoint: bp
var temp = x + y + z;
return;
};
closureCaptureInner(0);
};
outerClosure(3);
return;
}
main() => globalFunction();
''';
setUpAll(() async {
await driver.initSource(setup, source);
});
tearDownAll(() {
driver.cleanupTest();
});
test('compilation error', () async {
await driver.check(
breakpointId: 'bp',
expression: 'typo',
expectedError: "Getter not found: 'typo'.");
});
test('expression using captured variables', () async {
await driver.check(
breakpointId: 'bp', expression: r"'$y+$z'", expectedResult: '3+0');
});
test('expression using uncaptured variables', () async {
await driver.check(
breakpointId: 'bp',
expression: r"'$x+$y+$z'",
expectedResult: '15+3+0');
});
});
group('Expression compiler tests in method with no type use:', () {
var source = '''
abstract class Key {
const factory Key(String value) = ValueKey;
const Key.empty();
}
abstract class LocalKey extends Key {
const LocalKey() : super.empty();
}
class ValueKey implements LocalKey {
const ValueKey(this.value);
final String value;
}
class MyClass {
const MyClass(this._t);
final int _t;
}
int bar(int p) {
return p;
}
String baz(String t) {
return t;
}
String main() {
var k = Key('t');
MyClass c = MyClass(0);
int p = 1;
const t = 1;
// Breakpoint: bp
return '\$c, \$k, \$t';
}
''';
setUpAll(() async {
await driver.initSource(setup, source);
});
tearDownAll(() {
driver.cleanupTest();
});
test('call function not using type', () async {
await driver.check(
breakpointId: 'bp', expression: 'bar(p)', expectedResult: '1');
});
test('call function using type', () async {
await driver.check(
breakpointId: 'bp', expression: "baz('\$p')", expectedResult: '1');
});
test('evaluate new const expression', () async {
await driver.check(
breakpointId: 'bp',
expression: 'const MyClass(1)',
expectedResult: 'MyClass {Symbol(MyClass._t): 1}');
});
test('evaluate optimized const expression', () async {
await driver.check(
breakpointId: 'bp', expression: 't', expectedResult: '1');
},
skip: 'Cannot compile constants optimized away by the frontend. '
'Issue: https://github.com/dart-lang/sdk/issues/41999');
test('evaluate factory constructor call', () async {
await driver.check(
breakpointId: 'bp',
expression: "Key('t')",
expectedResult: 'test.ValueKey.new {Symbol(ValueKey.value): t}');
});
test('evaluate const factory constructor call', () async {
await driver.check(
breakpointId: 'bp',
expression: "const Key('t')",
expectedResult: 'ValueKey {Symbol(ValueKey.value): t}');
});
});
group('Expression compiler tests in simple loops:', () {
var source = '''
void globalFunction() {
int x = 15;
for(int i = 0; i < 10; i++) {
// Breakpoint: bp
var calculation = '\$i+\$x';
};
}
main() => globalFunction();
''';
setUpAll(() async {
await driver.initSource(setup, source);
});
tearDownAll(() {
driver.cleanupTest();
});
test('expression using local', () async {
await driver.check(
breakpointId: 'bp', expression: 'x', expectedResult: '15');
});
test('expression using loop variable', () async {
await driver.check(
breakpointId: 'bp', expression: 'i', expectedResult: '0');
});
});
group('Expression compiler tests in conditional:', () {
var source = '''
int globalFunction(int x) {
if (x == 1) {
int y = 3;
// Breakpoint: thenBP
var calculation = '\$y+\$x';
} else {
int z = 4;
// Breakpoint: elseBP
var calculation = '\$z+\$x';
}
// Breakpoint: postBP
return 0;
}
void main() {
globalFunction(1);
globalFunction(2);
}
''';
setUpAll(() async {
await driver.initSource(setup, source);
});
tearDownAll(() {
driver.cleanupTest();
});
test('(then) expression using local', () async {
await driver.check(
breakpointId: 'thenBP', expression: 'y', expectedResult: '3');
});
test('(then) expression using local out of scope', () async {
await driver.check(
breakpointId: 'thenBP',
expression: 'z',
expectedError: "Error: Getter not found: 'z'");
});
test('(else) expression using local', () async {
await driver.check(
breakpointId: 'elseBP', expression: 'z', expectedResult: '4');
});
test('(else) expression using local out of scope', () async {
await driver.check(
breakpointId: 'elseBP',
expression: 'y',
expectedError: "Error: Getter not found: 'y'");
});
test('(post) expression using local', () async {
await driver.check(
breakpointId: 'postBP', expression: 'x', expectedResult: '1');
});
test('(post) expression using local out of scope', () async {
await driver.check(
breakpointId: 'postBP',
expression: 'z',
expectedError: "Error: Getter not found: 'z'");
});
test('(post) expression using local out of scope', () async {
await driver.check(
breakpointId: 'postBP',
expression: 'y',
expectedError: "Error: Getter not found: 'y'");
});
});
group('Expression compiler tests in iterator loops:', () {
var source = '''
int globalFunction() {
var l = <String>['1', '2', '3'];
for (var e in l) {
// Breakpoint: bp
var calculation = '\$e';
};
return 0;
}
main() => globalFunction();
''';
setUpAll(() async {
await driver.initSource(setup, source);
});
tearDownAll(() {
driver.cleanupTest();
});
test('expression loop variable', () async {
await driver.check(
breakpointId: 'bp', expression: 'e', expectedResult: '1');
});
});
group('Expression compiler tests in generic method:', () {
var source = '''
class C<T1> {
void generic<T2>(T1 a, T2 b) {
// Breakpoint: bp
print(a);
print(b);
}
}
void main() => C<int>().generic<String>(0, 'hi');
''';
setUpAll(() async {
await driver.initSource(setup, source);
});
tearDownAll(() {
driver.cleanupTest();
});
test('evaluate formals', () async {
await driver.check(
breakpointId: 'bp',
expression: "'\${a} \$b'",
expectedResult: '0 hi');
});
test('evaluate class type parameters', () async {
await driver.check(
breakpointId: 'bp', expression: "'\$T1'", expectedResult: 'int');
});
test('evaluate method type parameters', () async {
await driver.check(
breakpointId: 'bp', expression: "'\$T2'", expectedResult: 'String');
});
});
group('Expression compiler tests for interactions with module containers:',
() {
var source = '''
class A {
const A();
}
class B {
const B();
}
void foo() {
const a = A();
var check = a is int;
// Breakpoint: bp
return;
}
void main() => foo();
''';
setUpAll(() async {
await driver.initSource(setup, source);
});
tearDownAll(() {
driver.cleanupTest();
});
test('evaluation that non-destructively appends to the type container',
() async {
await driver.check(
breakpointId: 'bp',
expression: 'a is String',
expectedResult: 'false');
});
test('evaluation that reuses the type container', () async {
await driver.check(
breakpointId: 'bp', expression: 'a is int', expectedResult: 'false');
});
test('evaluation that non-destructively appends to the constant container',
() async {
await driver.check(
breakpointId: 'bp',
expression: 'const B() == const B()',
expectedResult: 'true');
});
test('evaluation that properly canonicalizes constants', () async {
await driver.check(
breakpointId: 'bp',
expression: 'a == const A()',
expectedResult: 'true');
});
});
}