e36fbfd978
Module symbol information now includes basic details the classes compiled in the module. Add tests and refactor to share some of the options for running the incremental frontend compiler and ddc. Change-Id: I6d3eb16bd6fb70c4c0af46de93a0f7bf96151c4f Issue: https://github.com/dart-lang/sdk/issues/40273 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/202865 Commit-Queue: Nicholas Shahan <nshahan@google.com> Reviewed-by: Sigmund Cherem <sigmund@google.com> Reviewed-by: Anna Gringauze <annagrin@google.com>
638 lines
20 KiB
Dart
638 lines
20 KiB
Dart
// Copyright (c) 2020, 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
|
|
|
|
import 'dart:io' show Directory, File;
|
|
|
|
import 'package:dev_compiler/dev_compiler.dart';
|
|
import 'package:dev_compiler/src/compiler/js_names.dart';
|
|
import 'package:dev_compiler/src/compiler/module_builder.dart';
|
|
import 'package:dev_compiler/src/js_ast/js_ast.dart';
|
|
import 'package:kernel/ast.dart' show Component, Library;
|
|
import 'package:test/test.dart';
|
|
import 'package:vm/transformations/type_flow/utils.dart';
|
|
|
|
import '../shared_test_options.dart';
|
|
|
|
// TODO(annagrin): Replace javascript matching in tests below with evaluating
|
|
// the javascript and checking the result.
|
|
// See https://github.com/dart-lang/sdk/issues/41959
|
|
|
|
/// Convenience class describing JavaScript module
|
|
/// to ensure we have normalized module names
|
|
class Module {
|
|
/// variable name used in JavaScript output to load the module
|
|
/// example: file
|
|
final String name;
|
|
|
|
/// JavaScript module name used in trackLibraries
|
|
/// example: packages/package/file.dart
|
|
final String path;
|
|
|
|
/// URI where the contents of the library that produces this module
|
|
/// can be found
|
|
/// example: /Users/../package/file.dart
|
|
final Uri fileUri;
|
|
|
|
/// Import URI for the library that generates this module.
|
|
/// example: packages:package/file.dart
|
|
final Uri importUri;
|
|
|
|
Module(this.importUri, this.fileUri)
|
|
: name = libraryUriToJsIdentifier(importUri),
|
|
path = importUri.scheme == 'package'
|
|
? 'packages/${importUri.path}'
|
|
: importUri.path;
|
|
|
|
String get package => importUri.toString();
|
|
String get file => fileUri.path;
|
|
|
|
@override
|
|
String toString() =>
|
|
'Name: \$name, File: \$file, Package: \$package, path: \$path';
|
|
}
|
|
|
|
class TestCompilationResult {
|
|
final String result;
|
|
final bool isSuccess;
|
|
|
|
TestCompilationResult(this.result, this.isSuccess);
|
|
}
|
|
|
|
class TestCompiler {
|
|
final SetupCompilerOptions setup;
|
|
|
|
TestCompiler(this.setup);
|
|
|
|
Future<TestCompilationResult> compile(
|
|
{Uri input,
|
|
Uri packages,
|
|
int line,
|
|
int column,
|
|
Map<String, String> scope,
|
|
String expression}) async {
|
|
// initialize incremental compiler and create component
|
|
setup.options.packagesFileUri = packages;
|
|
var compiler = DevelopmentIncrementalCompiler(setup.options, input);
|
|
var component = await compiler.computeDelta();
|
|
component.computeCanonicalNames();
|
|
|
|
// initialize ddc
|
|
var moduleName = 'foo.dart';
|
|
var classHierarchy = compiler.getClassHierarchy();
|
|
var compilerOptions = SharedCompilerOptions(
|
|
replCompile: true,
|
|
moduleName: moduleName,
|
|
soundNullSafety: setup.soundNullSafety,
|
|
moduleFormats: [setup.moduleFormat]);
|
|
var coreTypes = compiler.getCoreTypes();
|
|
|
|
final importToSummary = Map<Library, Component>.identity();
|
|
final summaryToModule = Map<Component, String>.identity();
|
|
for (var lib in component.libraries) {
|
|
importToSummary[lib] = component;
|
|
}
|
|
summaryToModule[component] = moduleName;
|
|
|
|
var kernel2jsCompiler = ProgramCompiler(component, classHierarchy,
|
|
compilerOptions, importToSummary, summaryToModule,
|
|
coreTypes: coreTypes);
|
|
var moduleTree = kernel2jsCompiler.emitModule(component);
|
|
|
|
{
|
|
var opts = JavaScriptPrintingOptions(
|
|
allowKeywordsInProperties: true, allowSingleLineIfStatements: true);
|
|
var printer = SimpleJavaScriptPrintingContext();
|
|
|
|
var tree = transformModuleFormat(setup.moduleFormat, moduleTree);
|
|
tree.accept(Printer(opts, printer, localNamer: TemporaryNamer(tree)));
|
|
var printed = printer.getText();
|
|
debugPrint(printed);
|
|
}
|
|
|
|
// create expression compiler
|
|
var evaluator = ExpressionCompiler(
|
|
setup.options,
|
|
setup.moduleFormat,
|
|
setup.errors,
|
|
compiler,
|
|
kernel2jsCompiler,
|
|
component,
|
|
);
|
|
|
|
// collect all module names and paths
|
|
var moduleInfo = _collectModules(component);
|
|
var module = moduleInfo[input];
|
|
|
|
setup.errors.clear();
|
|
|
|
// compile
|
|
var jsExpression = await evaluator.compileExpressionToJs(
|
|
module.package, line, column, scope, expression);
|
|
|
|
if (setup.errors.isNotEmpty) {
|
|
jsExpression = setup.errors.toString().replaceAll(
|
|
RegExp(
|
|
r'org-dartlang-debug:synthetic_debug_expression:[0-9]*:[0-9]*:'),
|
|
'');
|
|
|
|
return TestCompilationResult(jsExpression, false);
|
|
}
|
|
|
|
return TestCompilationResult(jsExpression, true);
|
|
}
|
|
|
|
Map<Uri, Module> _collectModules(Component component) {
|
|
var modules = <Uri, Module>{};
|
|
for (var library in component.libraries) {
|
|
modules[library.fileUri] = Module(library.importUri, library.fileUri);
|
|
}
|
|
|
|
return modules;
|
|
}
|
|
}
|
|
|
|
class TestDriver {
|
|
final SetupCompilerOptions options;
|
|
Directory tempDir;
|
|
final String source;
|
|
Uri input;
|
|
Uri packages;
|
|
File file;
|
|
int line;
|
|
|
|
TestDriver(this.options, this.source) {
|
|
var systemTempDir = Directory.systemTemp;
|
|
tempDir = systemTempDir.createTempSync('foo bar');
|
|
|
|
line = _getEvaluationLine(source);
|
|
input = tempDir.uri.resolve('foo.dart');
|
|
file = File.fromUri(input)..createSync();
|
|
file.writeAsStringSync(source);
|
|
|
|
packages = tempDir.uri.resolve('package_config.json');
|
|
file = File.fromUri(packages)..createSync();
|
|
file.writeAsStringSync('''
|
|
{
|
|
"configVersion": 2,
|
|
"packages": [
|
|
{
|
|
"name": "foo",
|
|
"rootUri": "./",
|
|
"packageUri": "./"
|
|
}
|
|
]
|
|
}
|
|
''');
|
|
}
|
|
|
|
void delete() {
|
|
tempDir.delete(recursive: true);
|
|
}
|
|
|
|
Future<void> check(
|
|
{Map<String, String> scope,
|
|
String expression,
|
|
String expectedError,
|
|
String expectedResult}) async {
|
|
var result = await TestCompiler(options).compile(
|
|
input: input,
|
|
packages: packages,
|
|
line: line,
|
|
column: 1,
|
|
scope: scope,
|
|
expression: expression);
|
|
|
|
var success = expectedError == null;
|
|
var message = success ? expectedResult : expectedError;
|
|
|
|
expect(
|
|
result,
|
|
const TypeMatcher<TestCompilationResult>()
|
|
.having((r) => _normalize(r.result), 'result', _matches(message))
|
|
.having((r) => r.isSuccess, 'isSuccess', success));
|
|
}
|
|
|
|
String _normalize(String text) {
|
|
return text
|
|
.replaceAll(RegExp('\'.*foo.dart\''), '\'foo.dart\'')
|
|
.replaceAll(RegExp('\".*foo.dart\"'), '\'foo.dart\'');
|
|
}
|
|
|
|
Matcher _matches(String text) {
|
|
var unindented = RegExp.escape(text).replaceAll(RegExp('[ ]+'), '[ ]*');
|
|
return matches(RegExp(unindented, multiLine: true));
|
|
}
|
|
|
|
int _getEvaluationLine(String source) {
|
|
var placeholderRegExp = RegExp(r'/\* evaluation placeholder \*/');
|
|
|
|
var lines = source.split('\n');
|
|
for (var line = 0; line < lines.length; line++) {
|
|
var content = lines[line];
|
|
if (placeholderRegExp.firstMatch(content) != null) {
|
|
return line + 1;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
void main() {
|
|
for (var moduleFormat in [ModuleFormat.amd, ModuleFormat.ddc]) {
|
|
group('Module format: $moduleFormat', () {
|
|
group('Unsound null safety:', () {
|
|
var options = SetupCompilerOptions(
|
|
soundNullSafety: false, moduleFormat: moduleFormat);
|
|
|
|
group('Expression compiler import tests', () {
|
|
var source = '''
|
|
${options.dartLangComment}
|
|
import 'dart:io' show Directory;
|
|
import 'dart:io' as p;
|
|
import 'dart:convert' as p;
|
|
|
|
main() {
|
|
print(Directory.systemTemp);
|
|
print(p.Directory.systemTemp);
|
|
print(p.utf.decoder);
|
|
}
|
|
|
|
void foo() {
|
|
/* evaluation placeholder */
|
|
}
|
|
''';
|
|
|
|
TestDriver driver;
|
|
|
|
setUp(() {
|
|
driver = TestDriver(options, source);
|
|
});
|
|
|
|
tearDown(() {
|
|
driver.delete();
|
|
});
|
|
|
|
test('expression referencing unnamed import', () async {
|
|
await driver.check(
|
|
scope: <String, String>{},
|
|
expression: 'Directory.systemTemp',
|
|
expectedResult: '''
|
|
(function() {
|
|
const dart_sdk = ${options.loadModule}(\'dart_sdk\');
|
|
const io = dart_sdk.io;
|
|
return io.Directory.systemTemp;
|
|
}(
|
|
|
|
))
|
|
''');
|
|
});
|
|
|
|
test('expression referencing named import', () async {
|
|
await driver.check(
|
|
scope: <String, String>{},
|
|
expression: 'p.Directory.systemTemp',
|
|
expectedResult: '''
|
|
(function() {
|
|
const dart_sdk = ${options.loadModule}(\'dart_sdk\');
|
|
const io = dart_sdk.io;
|
|
return io.Directory.systemTemp;
|
|
}(
|
|
|
|
))
|
|
''');
|
|
});
|
|
|
|
test(
|
|
'expression referencing another library with the same named import',
|
|
() async {
|
|
await driver.check(
|
|
scope: <String, String>{},
|
|
expression: 'p.utf8.decoder',
|
|
expectedResult: '''
|
|
(function() {
|
|
const dart_sdk = ${options.loadModule}(\'dart_sdk\');
|
|
const convert = dart_sdk.convert;
|
|
return convert.utf8.decoder;
|
|
}(
|
|
|
|
))
|
|
''');
|
|
});
|
|
});
|
|
|
|
group(
|
|
'Expression compiler tests for interactions with module containers:',
|
|
() {
|
|
var source = '''
|
|
${options.dartLangComment}
|
|
class A {
|
|
const A();
|
|
}
|
|
class B {
|
|
const B();
|
|
}
|
|
void foo() {
|
|
const a = A();
|
|
var check = a is int;
|
|
/* evaluation placeholder */
|
|
return;
|
|
}
|
|
|
|
void main() => foo();
|
|
''';
|
|
|
|
TestDriver driver;
|
|
setUp(() {
|
|
driver = TestDriver(options, source);
|
|
});
|
|
|
|
tearDown(() {
|
|
driver.delete();
|
|
});
|
|
|
|
test(
|
|
'evaluation that non-destructively appends to the type container',
|
|
() async {
|
|
await driver.check(
|
|
scope: <String, String>{'a': 'null', 'check': 'null'},
|
|
expression: 'a is String',
|
|
expectedResult: '''
|
|
(function(a, check) {
|
|
const dart_sdk = ${options.loadModule}('dart_sdk');
|
|
const core = dart_sdk.core;
|
|
const dart = dart_sdk.dart;
|
|
var T = {
|
|
StringL: () => (T.StringL = dart.constFn(dart.legacy(core.String)))()
|
|
};
|
|
return T.StringL().is(a);
|
|
}(
|
|
null,
|
|
null
|
|
))
|
|
''');
|
|
});
|
|
|
|
test('evaluation that reuses the type container', () async {
|
|
await driver.check(
|
|
scope: <String, String>{'a': 'null', 'check': 'null'},
|
|
expression: 'a is int',
|
|
expectedResult: '''
|
|
(function(a, check) {
|
|
const dart_sdk = ${options.loadModule}('dart_sdk');
|
|
const core = dart_sdk.core;
|
|
const dart = dart_sdk.dart;
|
|
var T = {
|
|
intL: () => (T.intL = dart.constFn(dart.legacy(core.int)))()
|
|
};
|
|
return T.intL().is(a);
|
|
}(
|
|
null,
|
|
null
|
|
))
|
|
''');
|
|
});
|
|
|
|
test(
|
|
'evaluation that non-destructively appends to the constant container',
|
|
() async {
|
|
await driver.check(
|
|
scope: <String, String>{'a': 'null', 'check': 'null'},
|
|
expression: 'const B()',
|
|
expectedResult: '''
|
|
(function(a, check) {
|
|
const dart_sdk = ${options.loadModule}('dart_sdk');
|
|
const dart = dart_sdk.dart;
|
|
const foo\$46dart = ${options.loadModule}('foo.dart');
|
|
const foo = foo\$46dart.foo;
|
|
const CT = Object.create(null);
|
|
dart.defineLazy(CT, {
|
|
get C0() {
|
|
return C[0] = dart.const({
|
|
__proto__: foo.B.prototype
|
|
});
|
|
}
|
|
}, false);
|
|
var C = [void 0];
|
|
return C[0] || CT.C0;
|
|
}(
|
|
null,
|
|
null
|
|
))
|
|
''');
|
|
});
|
|
|
|
test(
|
|
'evaluation that reuses the constant container and canonicalizes properly',
|
|
() async {
|
|
await driver.check(
|
|
scope: <String, String>{'a': 'null', 'check': 'null'},
|
|
expression: 'a == const A()',
|
|
expectedResult: '''
|
|
(function(a, check) {
|
|
const dart_sdk = ${options.loadModule}('dart_sdk');
|
|
const dart = dart_sdk.dart;
|
|
const foo\$46dart = ${options.loadModule}('foo.dart');
|
|
const foo = foo\$46dart.foo;
|
|
const CT = Object.create(null);
|
|
dart.defineLazy(CT, {
|
|
get C0() {
|
|
return C[0] = dart.const({
|
|
__proto__: foo.A.prototype
|
|
});
|
|
}
|
|
}, false);
|
|
var C = [void 0];
|
|
return dart.equals(a, C[0] || CT.C0);
|
|
}(
|
|
null,
|
|
null
|
|
))
|
|
''');
|
|
});
|
|
});
|
|
|
|
group('Expression compiler tests using extension symbols', () {
|
|
var source = '''
|
|
${options.dartLangComment}
|
|
void bar() {
|
|
/* evaluation placeholder */
|
|
}
|
|
|
|
void main() => bar();
|
|
''';
|
|
|
|
TestDriver driver;
|
|
setUp(() {
|
|
driver = TestDriver(options, source);
|
|
});
|
|
|
|
tearDown(() {
|
|
driver.delete();
|
|
});
|
|
|
|
test('map access', () async {
|
|
await driver.check(
|
|
scope: <String, String>{'inScope': '1', 'innerInScope': '0'},
|
|
expression:
|
|
'(Map<String, String> params) { return params["index"]; }({})',
|
|
expectedResult: '''
|
|
(function() {
|
|
const dart_sdk = ${options.loadModule}('dart_sdk');
|
|
const core = dart_sdk.core;
|
|
const _js_helper = dart_sdk._js_helper;
|
|
const dart = dart_sdk.dart;
|
|
const dartx = dart_sdk.dartx;
|
|
var T = {
|
|
StringL: () => (T.StringL = dart.constFn(dart.legacy(core.String)))(),
|
|
MapOfStringL\$StringL: () => (T.MapOfStringL\$StringL = dart.constFn(core.Map\$(T.StringL(), T.StringL())))(),
|
|
MapLOfStringL\$StringL: () => (T.MapLOfStringL\$StringL = dart.constFn(dart.legacy(T.MapOfStringL\$StringL())))(),
|
|
MapLOfStringL\$StringLToStringL: () => (T.MapLOfStringL\$StringLToStringL = dart.constFn(dart.fnType(T.StringL(), [T.MapLOfStringL\$StringL()])))(),
|
|
IdentityMapOfStringL\$StringL: () => (T.IdentityMapOfStringL\$StringL = dart.constFn(_js_helper.IdentityMap\$(T.StringL(), T.StringL())))()
|
|
};
|
|
var S = {\$_get: dartx._get};
|
|
return dart.fn(params => params[S.\$_get]("index"), T.MapLOfStringL\$StringLToStringL())(new (T.IdentityMapOfStringL\$StringL()).new());
|
|
}(
|
|
|
|
))
|
|
''');
|
|
});
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
for (var moduleFormat in [ModuleFormat.amd, ModuleFormat.ddc]) {
|
|
group('Module format: $moduleFormat', () {
|
|
group('Sound null safety:', () {
|
|
var options = SetupCompilerOptions(soundNullSafety: true);
|
|
|
|
group('Expression compiler import tests', () {
|
|
var source = '''
|
|
${options.dartLangComment}
|
|
import 'dart:io' show Directory;
|
|
import 'dart:io' as p;
|
|
import 'dart:convert' as p;
|
|
|
|
main() {
|
|
print(Directory.systemTemp);
|
|
print(p.Directory.systemTemp);
|
|
print(p.utf.decoder);
|
|
}
|
|
|
|
void foo() {
|
|
/* evaluation placeholder */
|
|
}
|
|
''';
|
|
|
|
TestDriver driver;
|
|
|
|
setUp(() {
|
|
driver = TestDriver(options, source);
|
|
});
|
|
|
|
tearDown(() {
|
|
driver.delete();
|
|
});
|
|
|
|
test('expression referencing unnamed import', () async {
|
|
await driver.check(
|
|
scope: <String, String>{},
|
|
expression: 'Directory.systemTemp',
|
|
expectedResult: '''
|
|
(function() {
|
|
const dart_sdk = ${options.loadModule}(\'dart_sdk\');
|
|
const io = dart_sdk.io;
|
|
return io.Directory.systemTemp;
|
|
}(
|
|
|
|
))
|
|
''');
|
|
});
|
|
|
|
test('expression referencing named import', () async {
|
|
await driver.check(
|
|
scope: <String, String>{},
|
|
expression: 'p.Directory.systemTemp',
|
|
expectedResult: '''
|
|
(function() {
|
|
const dart_sdk = ${options.loadModule}(\'dart_sdk\');
|
|
const io = dart_sdk.io;
|
|
return io.Directory.systemTemp;
|
|
}(
|
|
|
|
))
|
|
''');
|
|
});
|
|
|
|
test(
|
|
'expression referencing another library with the same named import',
|
|
() async {
|
|
await driver.check(
|
|
scope: <String, String>{},
|
|
expression: 'p.utf8.decoder',
|
|
expectedResult: '''
|
|
(function() {
|
|
const dart_sdk = ${options.loadModule}(\'dart_sdk\');
|
|
const convert = dart_sdk.convert;
|
|
return convert.utf8.decoder;
|
|
}(
|
|
|
|
))
|
|
''');
|
|
});
|
|
});
|
|
|
|
group('Expression compiler expressions that import extension symbols',
|
|
() {
|
|
var source = '''
|
|
${options.dartLangComment}
|
|
void bar() {
|
|
/* evaluation placeholder */
|
|
}
|
|
|
|
void main() => bar();
|
|
''';
|
|
|
|
TestDriver driver;
|
|
setUp(() {
|
|
driver = TestDriver(options, source);
|
|
});
|
|
|
|
tearDown(() {
|
|
driver.delete();
|
|
});
|
|
|
|
test('map access', () async {
|
|
await driver.check(
|
|
scope: <String, String>{'inScope': '1', 'innerInScope': '0'},
|
|
expression:
|
|
'(Map<String, String> params) { return params["index"]; }({})',
|
|
expectedResult: '''
|
|
(function() {
|
|
const dart_sdk = ${options.loadModule}('dart_sdk');
|
|
const core = dart_sdk.core;
|
|
const _js_helper = dart_sdk._js_helper;
|
|
const dart = dart_sdk.dart;
|
|
const dartx = dart_sdk.dartx;
|
|
var T = {
|
|
StringN: () => (T.StringN = dart.constFn(dart.nullable(core.String)))(),
|
|
MapOfString\$String: () => (T.MapOfString\$String = dart.constFn(core.Map\$(core.String, core.String)))(),
|
|
MapOfString\$StringToStringN: () => (T.MapOfString\$StringToStringN = dart.constFn(dart.fnType(T.StringN(), [T.MapOfString\$String()])))(),
|
|
IdentityMapOfString\$String: () => (T.IdentityMapOfString\$String = dart.constFn(_js_helper.IdentityMap\$(core.String, core.String)))()
|
|
};
|
|
var S = {\$_get: dartx._get};
|
|
return dart.fn(params => params[S.\$_get]("index"), T.MapOfString\$StringToStringN())(new (T.IdentityMapOfString\$String()).new());
|
|
}(
|
|
|
|
))
|
|
''');
|
|
});
|
|
});
|
|
});
|
|
});
|
|
}
|
|
}
|