Skip to content

Commit 559b855

Browse files
nshahanCommit Queue
authored andcommitted
[ddc,frontend_server] Fix expression compilation after recompile
Update the last known good component after a recompile reject back to the expected state before the recompile. Adds tests for expression compilation after recompile accept and recompile reject cases with `--target=dartdevc`. Similar to existing tests for the VM. Removes calls to `_generator.accept()` and `component.computeCanonicalNames()` from `compileExpressionToJs()`. These were added very early in the prototype implementation and it is no longer clear why they would be needed. Added some additional test cases for expression compilation involving import resolution because the change that originally added the `component.computeCanonicalNames()` call implies it was needed for that reason. See: https://dart-review.googlesource.com/c/sdk/+/138010 TEST=pkg/frontend_server/test/frontend_server_test.dart,pkg/dev_compiler/test/expression_compiler/expression_compiler_test.dart Change-Id: I9c04bd46e56b33dc654d00fb330de29c3c643a5b Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/434522 Reviewed-by: Alexander Markov <[email protected]> Commit-Queue: Nicholas Shahan <[email protected]> Reviewed-by: Nate Biggs <[email protected]> Reviewed-by: Jens Johansen <[email protected]>
1 parent e389cee commit 559b855

File tree

5 files changed

+310
-20
lines changed

5 files changed

+310
-20
lines changed

pkg/dev_compiler/test/expression_compiler/expression_compiler_suite.dart

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,45 +9,72 @@ import 'package:test/test.dart';
99
import '../shared_test_options.dart';
1010
import 'test_compiler.dart';
1111

12+
typedef AdditionalLibrary = ({String name, String source});
13+
1214
class ExpressionCompilerTestDriver {
1315
final SetupCompilerOptions setup;
14-
late Directory testDir;
16+
late Directory _testRootDirectory;
1517
String source;
1618
late Uri input;
1719
late Uri output;
1820
late Uri packages;
1921
late int line;
22+
final List<AdditionalLibrary> additionalLibraries;
2023

21-
ExpressionCompilerTestDriver(this.setup, this.source) {
24+
ExpressionCompilerTestDriver(
25+
this.setup,
26+
this.source, {
27+
this.additionalLibraries = const [],
28+
}) {
2229
line = _getEvaluationLine(source);
2330
var systemTempDir = Directory.systemTemp;
24-
testDir = systemTempDir.createTempSync('foo bar');
31+
_testRootDirectory = systemTempDir.createTempSync('expression_eval_test');
32+
var testPackageDirectory = Directory.fromUri(
33+
_testRootDirectory.uri.resolve('test_package/'),
34+
);
2535

26-
output = testDir.uri.resolve('test.js');
27-
input = testDir.uri.resolve('test.dart');
36+
output = testPackageDirectory.uri.resolve('test.js');
37+
input = testPackageDirectory.uri.resolve('test.dart');
2838
File.fromUri(input)
29-
..createSync()
39+
..createSync(recursive: true)
3040
..writeAsStringSync(source);
3141

32-
packages = testDir.uri.resolve('package_config.json');
42+
packages = _testRootDirectory.uri.resolve('package_config.json');
43+
var packageDescriptors = [_createPackageDescriptor('test_package')];
44+
for (var library in additionalLibraries) {
45+
var name = library.name;
46+
var source = library.source;
47+
var additionalPackageDirectory = Directory.fromUri(
48+
_testRootDirectory.uri.resolve('$name/'),
49+
);
50+
var additionalFile = additionalPackageDirectory.uri.resolve('$name.dart');
51+
File.fromUri(additionalFile)
52+
..createSync(recursive: true)
53+
..writeAsStringSync(source);
54+
packageDescriptors.add(_createPackageDescriptor(name));
55+
}
3356
File.fromUri(packages)
3457
..createSync()
3558
..writeAsStringSync('''
3659
{
3760
"configVersion": 2,
3861
"packages": [
39-
{
40-
"name": "foo",
41-
"rootUri": "./",
42-
"packageUri": "./"
43-
}
62+
${packageDescriptors.join(',\n ')}
4463
]
4564
}
4665
''');
4766
}
4867

68+
/// Creates a `packages_config.json` entry for a test package called [name].
69+
static String _createPackageDescriptor(String name) =>
70+
'''{
71+
"name": "$name",
72+
"rootUri": "./$name",
73+
"packageUri": "./"
74+
}''';
75+
4976
void delete() {
50-
testDir.deleteSync(recursive: true);
77+
_testRootDirectory.deleteSync(recursive: true);
5178
}
5279

5380
Future<TestExpressionCompiler> createCompiler() =>

pkg/dev_compiler/test/expression_compiler/expression_compiler_test.dart

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ void runTests(SetupCompilerOptions setup) {
9292
});
9393
});
9494

95-
group('Expression compiler import tests', () {
95+
group('Expression compiler dart: import tests', () {
9696
var source = '''
9797
import 'dart:io' show Directory;
9898
import 'dart:io' as p;
@@ -146,4 +146,65 @@ void runTests(SetupCompilerOptions setup) {
146146
},
147147
);
148148
});
149+
group('Expression compiler package: import tests', () {
150+
var source = '''
151+
import 'package:a/a.dart' show topLevelMethod;
152+
import 'package:a/a.dart' as prefix;
153+
import 'package:b/b.dart' as prefix;
154+
155+
main() {
156+
print(topLevelMethod(99));
157+
print(prefix.topLevelMethod(99));
158+
print(prefix.anotherTopLevelMethod('hello'));
159+
}
160+
161+
void foo() {
162+
// Breakpoint
163+
}
164+
''';
165+
166+
var a = 'bool topLevelMethod(int i) => i.isEven;';
167+
var b = 'int anotherTopLevelMethod(String s) => s.length;';
168+
169+
late ExpressionCompilerTestDriver driver;
170+
171+
setUp(() {
172+
driver = ExpressionCompilerTestDriver(
173+
setup,
174+
source,
175+
additionalLibraries: [(name: 'a', source: a), (name: 'b', source: b)],
176+
);
177+
});
178+
179+
tearDown(() {
180+
driver.delete();
181+
});
182+
183+
test('expression referencing unnamed import', () async {
184+
await driver.check(
185+
scope: <String, String>{},
186+
expression: 'topLevelMethod(99)',
187+
expectedResult: contains('return a.topLevelMethod(99);'),
188+
);
189+
});
190+
191+
test('expression referencing named import', () async {
192+
await driver.check(
193+
scope: <String, String>{},
194+
expression: 'prefix.topLevelMethod(99)',
195+
expectedResult: contains('return a.topLevelMethod(99);'),
196+
);
197+
});
198+
199+
test(
200+
'expression referencing another library with the same named import',
201+
() async {
202+
await driver.check(
203+
scope: <String, String>{},
204+
expression: 'prefix.anotherTopLevelMethod("hello")',
205+
expectedResult: contains('return b.anotherTopLevelMethod("hello")'),
206+
);
207+
},
208+
);
209+
});
149210
}

pkg/frontend_server/lib/frontend_server.dart

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1146,9 +1146,7 @@ class FrontendCompiler implements CompilerInterface {
11461146
Map<String, String> jsModules,
11471147
Map<String, String> jsFrameValues,
11481148
String expression) async {
1149-
_generator.accept();
11501149
errors.clear();
1151-
11521150
if (_bundler == null) {
11531151
reportError('JavaScript bundler is null');
11541152
return;
@@ -1166,9 +1164,7 @@ class FrontendCompiler implements CompilerInterface {
11661164
final Compiler kernel2jsCompiler = cachedProgramCompilers[libraryUri]!;
11671165
IncrementalCompilerResult compilerResult = _generator.lastKnownGoodResult!;
11681166
Component component = compilerResult.component;
1169-
component.computeCanonicalNames();
1170-
1171-
_processedOptions.ticker.logMs('Computed component');
1167+
_processedOptions.ticker.logMs('Retrieved cached component');
11721168

11731169
ModuleFormat moduleFormat =
11741170
parseModuleFormat(_options['dartdevc-module-format'] as String);

pkg/frontend_server/test/frontend_server_test.dart

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1095,6 +1095,204 @@ extension type Foo(int value) {
10951095
frontendServer.close();
10961096
}, timeout: new Timeout.factor(100));
10971097

1098+
test('compile expression to JavaScript when delta is rejected', () async {
1099+
File fileLib = new File('${tempDir.path}/lib.dart')..createSync();
1100+
fileLib.writeAsStringSync("foo() => 42;\n");
1101+
File file = new File('${tempDir.path}/foo.dart')..createSync();
1102+
// Initial compile contains a generic class so an edit can be made later
1103+
// that is known to be rejected.
1104+
file.writeAsStringSync(
1105+
"import 'lib.dart'; class A<T, S> {} main1() => print(foo);\n");
1106+
File packageConfig =
1107+
new File('${tempDir.path}/.dart_tool/package_config.json')
1108+
..createSync(recursive: true)
1109+
..writeAsStringSync('''
1110+
{
1111+
"configVersion": 2,
1112+
"packages": [
1113+
{
1114+
"name": "hello",
1115+
"rootUri": "../",
1116+
"packageUri": "./"
1117+
}
1118+
]
1119+
}
1120+
''');
1121+
File dillFile = new File('${tempDir.path}/app.dill');
1122+
expect(dillFile.existsSync(), equals(false));
1123+
final List<String> args = <String>[
1124+
'--sdk-root=${sdkRoot.toFilePath()}',
1125+
'--incremental',
1126+
'--platform=${ddcPlatformKernel.path}',
1127+
'--output-dill=${dillFile.path}',
1128+
'--target=dartdevc',
1129+
// TODO(nshahan): Remove these two flags when library bundle format is
1130+
// the default without passing --dartdevc-canary.
1131+
'--dartdevc-module-format=ddc',
1132+
'--dartdevc-canary',
1133+
'--packages=${packageConfig.path}',
1134+
];
1135+
1136+
FrontendServer frontendServer = new FrontendServer();
1137+
final Future<int> result = frontendServer.open(args);
1138+
frontendServer.compile(file.path);
1139+
int count = 0;
1140+
frontendServer.listen((Result compiledResult) {
1141+
CompilationResult result =
1142+
new CompilationResult.parse(compiledResult.status);
1143+
if (count == 0) {
1144+
// First request was to 'compile', which resulted in full kernel file.
1145+
expect(result.errorsCount, 0);
1146+
expect(dillFile.existsSync(), equals(true));
1147+
expect(result.filename, dillFile.path);
1148+
frontendServer.accept();
1149+
1150+
frontendServer.compileExpressionToJs(
1151+
expression: 'main1',
1152+
libraryUri: 'package:hello/foo.dart',
1153+
line: 1,
1154+
column: 1,
1155+
);
1156+
count += 1;
1157+
} else if (count == 1) {
1158+
// Second request was to 'compile-expression', which resulted in
1159+
// kernel file with a function that wraps compiled expression.
1160+
expect(result.errorsCount, 0);
1161+
File outputFile = new File(result.filename);
1162+
expect(outputFile.existsSync(), equals(true));
1163+
expect(outputFile.lengthSync(), isPositive);
1164+
1165+
// Removing a generic class type argument is known to cause a reload
1166+
// rejection.
1167+
file.writeAsStringSync(
1168+
"import 'lib.dart'; class A<T> {} main() => foo();\n");
1169+
1170+
frontendServer.recompile(file.uri, entryPoint: file.path);
1171+
count += 1;
1172+
} else if (count == 2) {
1173+
// Third request was to recompile the script after renaming a
1174+
// function.
1175+
expect(result.errorsCount, 1);
1176+
frontendServer.reject();
1177+
count += 1;
1178+
} else if (count == 3) {
1179+
// Fourth request was to reject the compilation results.
1180+
expect(result.errorsCount, 0);
1181+
frontendServer.compileExpressionToJs(
1182+
expression: 'main1',
1183+
libraryUri: 'package:hello/foo.dart',
1184+
line: 1,
1185+
column: 1,
1186+
);
1187+
count += 1;
1188+
} else {
1189+
expect(count, 4);
1190+
// Fifth request was to 'compile-expression' that references original
1191+
// function, which should still be successful.
1192+
expect(result.errorsCount, 0);
1193+
frontendServer.quit();
1194+
}
1195+
});
1196+
1197+
expect(await result, 0);
1198+
frontendServer.close();
1199+
}, timeout: new Timeout.factor(100));
1200+
1201+
test('compile expression to JavaScript when delta is accepted', () async {
1202+
File fileLib = new File('${tempDir.path}/lib.dart')..createSync();
1203+
fileLib.writeAsStringSync("foo() => 42;\n");
1204+
File file = new File('${tempDir.path}/foo.dart')..createSync();
1205+
file.writeAsStringSync("import 'lib.dart'; main1() => print(foo);\n");
1206+
File packageConfig =
1207+
new File('${tempDir.path}/.dart_tool/package_config.json')
1208+
..createSync(recursive: true)
1209+
..writeAsStringSync('''
1210+
{
1211+
"configVersion": 2,
1212+
"packages": [
1213+
{
1214+
"name": "hello",
1215+
"rootUri": "../",
1216+
"packageUri": "./"
1217+
}
1218+
]
1219+
}
1220+
''');
1221+
File dillFile = new File('${tempDir.path}/app.dill');
1222+
expect(dillFile.existsSync(), equals(false));
1223+
final List<String> args = <String>[
1224+
'--sdk-root=${sdkRoot.toFilePath()}',
1225+
'--incremental',
1226+
'--platform=${ddcPlatformKernel.path}',
1227+
'--output-dill=${dillFile.path}',
1228+
'--target=dartdevc',
1229+
// TODO(nshahan): Remove these two flags when library bundle format is
1230+
// the default without passing --dartdevc-canary.
1231+
'--dartdevc-module-format=ddc',
1232+
'--dartdevc-canary',
1233+
'--packages=${packageConfig.path}',
1234+
];
1235+
1236+
FrontendServer frontendServer = new FrontendServer();
1237+
final Future<int> result = frontendServer.open(args);
1238+
frontendServer.compile(file.path);
1239+
int count = 0;
1240+
frontendServer.listen((Result compiledResult) {
1241+
CompilationResult result =
1242+
new CompilationResult.parse(compiledResult.status);
1243+
if (count == 0) {
1244+
// First request was to 'compile', which resulted in full kernel file.
1245+
expect(result.errorsCount, 0);
1246+
expect(dillFile.existsSync(), equals(true));
1247+
expect(result.filename, dillFile.path);
1248+
frontendServer.accept();
1249+
1250+
frontendServer.compileExpressionToJs(
1251+
expression: 'main1',
1252+
libraryUri: 'package:hello/foo.dart',
1253+
line: 1,
1254+
column: 1,
1255+
);
1256+
count += 1;
1257+
} else if (count == 1) {
1258+
// Second request was to 'compile-expression', which resulted in
1259+
// kernel file with a function that wraps compiled expression.
1260+
expect(result.errorsCount, 0);
1261+
File outputFile = new File(result.filename);
1262+
expect(outputFile.existsSync(), equals(true));
1263+
expect(outputFile.lengthSync(), isPositive);
1264+
1265+
file.writeAsStringSync("import 'lib.dart'; main() => foo();\n");
1266+
frontendServer.recompile(file.uri, entryPoint: file.path);
1267+
count += 1;
1268+
} else if (count == 2) {
1269+
// Third request was to recompile the script after renaming a
1270+
// function.
1271+
expect(result.errorsCount, 0);
1272+
File dillIncFile = new File('${dillFile.path}.incremental.dill');
1273+
expect(dillIncFile.existsSync(), equals(true));
1274+
expect(result.filename, dillIncFile.path);
1275+
frontendServer.accept();
1276+
frontendServer.compileExpressionToJs(
1277+
expression: 'main',
1278+
libraryUri: 'package:hello/foo.dart',
1279+
line: 1,
1280+
column: 1,
1281+
);
1282+
count += 1;
1283+
} else {
1284+
expect(count, 3);
1285+
// Fourth request was to 'compile-expression' that references the new
1286+
// function, which should still be successful.
1287+
expect(result.errorsCount, 0);
1288+
frontendServer.quit();
1289+
}
1290+
});
1291+
1292+
expect(await result, 0);
1293+
frontendServer.close();
1294+
}, timeout: new Timeout.factor(100));
1295+
10981296
test('reject - recreate issue 55357', () async {
10991297
File file = new File('${tempDir.path}/foo.dart')..createSync();
11001298
file.writeAsStringSync(
@@ -2342,6 +2540,8 @@ e() {
23422540
'--output-dill=${dillFile.path}',
23432541
'--target=dartdevc',
23442542
// Errors are only emitted with the DDC library bundle format.
2543+
// TODO(nshahan): Remove these two flags when library bundle format
2544+
// is the default without passing --dartdevc-canary.
23452545
'--dartdevc-module-format=ddc',
23462546
'--dartdevc-canary',
23472547
'--packages=${packageConfig.path}',

0 commit comments

Comments
 (0)