[ddc] Rename variable used to store late value

Late local variables are lowered by the CFE into a local:
* backing store variable
* get method
* set method
* isSet local variable (optionally when the type is nullable)

This change updates the name in JavaScript used for the backing store
variable to match the name for the late variable from the original
source. It also updates the scope information passed for expression
evaluation to remove the lowered name and replace it with the original
so evaluations will work as well.

The name change avoids the hiding performed by the debugger on
recognized temporary names so it appears in the list of local variables.

b/343405209
Issue: See https://github.com/dart-lang/sdk/issues/55918

Change-Id: I6b65a62baf6f26f6e9cfee9f14667d021e16645e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/369506
Reviewed-by: Sigmund Cherem <sigmund@google.com>
Commit-Queue: Nicholas Shahan <nshahan@google.com>
This commit is contained in:
Nicholas Shahan 2024-06-05 20:50:49 +00:00 committed by Commit Queue
parent 8029fdf79a
commit e7b1bca707
3 changed files with 107 additions and 6 deletions

View file

@ -4794,11 +4794,17 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
/// Detects temporary variables so we can avoid displaying
/// them in the debugger if needed.
bool _isTemporaryVariable(VariableDeclaration v) =>
v.isLowered ||
v.isSynthesized ||
v.name == null ||
v.name!.startsWith('#');
bool _isTemporaryVariable(VariableDeclaration v) {
// Late local variables are be exposed to the debugger for inspection and
// evaluation by treating the backing store local variable as a regular
// non-temporary variable.
// See https://github.com/dart-lang/sdk/issues/55918
if (isLateLoweredLocal(v)) return false;
return v.isLowered ||
v.isSynthesized ||
v.name == null ||
v.name!.startsWith('#');
}
/// Creates a temporary name recognized by the debugger.
/// Assumes `_isTemporaryVariable(v)` is true.
@ -4821,7 +4827,15 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
name ??= 't\$${_tempVariables.length}';
return _tempVariables.putIfAbsent(v, () => _emitTemporaryId(name!));
}
return _emitIdentifier(v.name!);
var name = v.name!;
if (isLateLoweredLocal(v)) {
// Late local variables are be exposed to the debugger for inspection and
// evaluation by treating the backing store local variable as a regular
// non-temporary variable.
// See https://github.com/dart-lang/sdk/issues/55918
name = extractLocalNameFromLateLoweredLocal(name);
}
return _emitIdentifier(name);
}
/// Emits the declaration of a variable.

View file

@ -102,6 +102,19 @@ class ExpressionCompiler {
// different from dart.
// See [issue 40273](https://github.com/dart-lang/sdk/issues/40273)
// Work around mismatched names and lowered representation for late local
// variables.
// Replace the existing entries with a name that matches the named
// extracted from the lowering.
// See https://github.com/dart-lang/sdk/issues/55918
var dartLateLocals = [
for (var name in dartScope.definitions.keys)
if (isLateLoweredLocalName(name)) name,
];
for (var localName in dartLateLocals) {
dartScope.definitions[extractLocalName(localName)] =
dartScope.definitions.remove(localName)!;
}
// remove undefined js variables (this allows us to get a reference error
// from chrome on evaluation)
dartScope.definitions.removeWhere((variable, type) =>

View file

@ -67,6 +67,8 @@ class C {
}
int global = 42;
late int lateGlobal;
late String lateGlobal2;
const soundNullSafety = !(<Null>[] is List<int>);
soundNullSafetyTest() {
@ -105,6 +107,24 @@ extensionsSymbolTest() {
print(list);
}
lateLocalVariableTest() {
late int lateLocal;
late int lateLocal2;
if (42.isEven) {
lateLocal = 42;
}
// Breakpoint: lateLocalVariableBP
print(lateLocal);
}
lateGlobalVariableTest() {
if (42.isEven) {
lateGlobal = 42;
}
// Breakpoint: lateGlobalVariableBP
print(lateGlobal);
}
int foo(int x, {int y = 0}) {
int z = 3;
// Breakpoint: fooBP
@ -268,6 +288,8 @@ main() {
soundNullSafetyTest();
couldReturnNullTest();
extensionsSymbolTest();
lateLocalVariableTest();
lateGlobalVariableTest();
"1234".parseIntPlusOne();
callFooTest();
@ -754,6 +776,58 @@ void runNullSafeSharedTests(
});
});
group('late', () {
group('local', () {
test(
'can be evaluated when initialized',
() async {
await driver.checkInFrame(
breakpointId: 'lateLocalVariableBP',
expression: 'lateLocal',
expectedResult: '42');
},
);
test('does not throw when evaluated and not initialized', () async {
// It isn't clear if this is expected to work or not, the behavior is
// somewhat undefined for the debugger. At this time we expose the
// backing storage variable that can be displayed or might be null if
// uninitialized.
// See https://github.com/dart-lang/sdk/issues/55918
await driver.checkInFrame(
breakpointId: 'lateLocalVariableBP',
expression: 'lateLocal2',
expectedResult: 'null');
});
test('throws when not initialized and used in method call', () async {
// It isn't clear if this is expected to work or not, the behavior is
// somewhat undefined for the debugger. At this time we expose the
// backing storage variable that can be displayed or might be null if
// uninitialized.
// See https://github.com/dart-lang/sdk/issues/55918
await driver.checkInFrame(
breakpointId: 'lateLocalVariableBP',
expression: 'lateLocal2.isEven',
expectedError: "Error: Property 'isEven' cannot be accessed on "
"'int?' because it is potentially null.");
});
});
group('global', () {
test('can be evaluated when initialized', () async {
await driver.checkInFrame(
breakpointId: 'lateGlobalVariableBP',
expression: 'lateGlobal',
expectedResult: '42');
});
test('throws when not initialized', () async {
await driver.checkInFrame(
breakpointId: 'lateGlobalVariableBP',
expression: 'lateGlobal2',
expectedError: 'Error: LateInitializationError: '
"Field 'lateGlobal2' has not been initialized.");
});
});
});
group('regression', () {
test('don\'t crash on implicit null checks', () async {
// Compiling an expression that contains a method with a non-nullable