Skip to content

Commit 1edc2ec

Browse files
nshahanCommit Queue
authored and
Commit Queue
committed
[ddc] Support warnings/errors in weak mode
In the new runtime type system when running with weak null safety, perform type tests multiple times to produce optional warnings or errors when a test passes but would fail in sound null safety. This is the same technique DDC uses with the current type system. Issue: #48585 Change-Id: Ic1514987a6f4ffeb127a0d2be5ec15b606016212 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/266543 Reviewed-by: Mark Zhou <[email protected]> Reviewed-by: Anna Gringauze <[email protected]> Commit-Queue: Nicholas Shahan <[email protected]>
1 parent fd30c4f commit 1edc2ec

File tree

4 files changed

+108
-22
lines changed

4 files changed

+108
-22
lines changed

pkg/dev_compiler/lib/src/kernel/compiler.dart

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5935,7 +5935,13 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
59355935
case 'PRINT_LEGACY_STARS':
59365936
return js.boolean(_options.printLegacyStars);
59375937
case 'LEGACY':
5938-
return js.boolean(!_options.soundNullSafety);
5938+
return _options.soundNullSafety
5939+
? js.boolean(false)
5940+
// When running the new runtime type system with weak null
5941+
// safety this flag gets toggled when performing `is` and `as`
5942+
// checks. This allows DDC to produce optional warnings or
5943+
// errors when tests pass but would fail in sound null safety.
5944+
: runtimeCall('legacyTypeChecks');
59395945
case 'MINIFIED':
59405946
return js.boolean(false);
59415947
case 'NEW_RUNTIME_TYPES':
@@ -6563,16 +6569,24 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
65636569
type.nullability == Nullability.nonNullable &&
65646570
type != _types.coreTypes.intNonNullableRawType) {
65656571
return js.call('typeof # == #', [lhs, js.string(typeofName, "'")]);
6566-
} else {
6567-
return _options.newRuntimeTypes
6572+
}
6573+
6574+
if (_options.newRuntimeTypes) {
6575+
// When using the new runtime type system with sound null safety we can
6576+
// call to the library directly. In weak mode we call a DDC only method
6577+
// that can optionally produce warnings or errors when the check passes
6578+
// but would fail with sound null safety.
6579+
return _options.soundNullSafety
65686580
? js.call('#.#(#)', [
65696581
_emitType(type),
65706582
_emitMemberName(js_ast.FixedNames.rtiIsField,
65716583
memberClass: rtiClass),
65726584
lhs
65736585
])
6574-
: js.call('#.is(#)', [_emitType(type), lhs]);
6586+
: runtimeCall('is(#, #)', [lhs, _emitType(type)]);
65756587
}
6588+
6589+
return js.call('#.is(#)', [_emitType(type), lhs]);
65766590
}
65776591

65786592
@override
@@ -6649,15 +6663,21 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
66496663

66506664
js_ast.Expression _emitCast(js_ast.Expression expr, DartType type) {
66516665
if (_types.isTop(type)) return expr;
6652-
6653-
return _options.newRuntimeTypes
6654-
? js.call('#.#(#)', [
6655-
_emitType(type),
6656-
_emitMemberName(js_ast.FixedNames.rtiAsField,
6657-
memberClass: rtiClass),
6658-
expr
6659-
])
6660-
: js.call('#.as(#)', [_emitType(type), expr]);
6666+
if (_options.newRuntimeTypes) {
6667+
// When using the new runtime type system with sound null safety we can
6668+
// call to the library directly. In weak mode we call a DDC only method
6669+
// that can optionally produce warnings or errors when the cast passes but
6670+
// would fail with sound null safety.
6671+
return _options.soundNullSafety
6672+
? js.call('#.#(#)', [
6673+
_emitType(type),
6674+
_emitMemberName(js_ast.FixedNames.rtiAsField,
6675+
memberClass: rtiClass),
6676+
expr
6677+
])
6678+
: runtimeCall('as(#, #)', [expr, _emitType(type)]);
6679+
}
6680+
return js.call('#.as(#)', [_emitType(type), expr]);
66616681
}
66626682

66636683
@override

sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -456,12 +456,36 @@ dsetindex(obj, index, value) =>
456456
@notNull
457457
@JSExportName('is')
458458
bool instanceOf(obj, type) {
459-
if (obj == null) {
460-
return _equalType(type, Null) ||
461-
_isTop(type) ||
462-
_jsInstanceOf(type, NullableType);
459+
if (JS_GET_FLAG('NEW_RUNTIME_TYPES')) {
460+
var testRti = JS<rti.Rti>('!', '#', type);
461+
// When running without sound null safety is type tests are dispatched here
462+
// to issue optional warnings/errors when the result is true but would be
463+
// false with sound null safety.
464+
var legacyErasedRecipe =
465+
rti.Rti.getLegacyErasedRecipe(JS<rti.Rti>('!', '#', testRti));
466+
var legacyErasedType = rti.findType(legacyErasedRecipe);
467+
legacyTypeChecks = false;
468+
var result = JS<bool>('bool', '#.#(#)', legacyErasedType,
469+
JS_GET_NAME(JsGetName.RTI_FIELD_IS), obj);
470+
legacyTypeChecks = true;
471+
if (result) return true;
472+
result =
473+
JS('bool', '#.#(#)', testRti, JS_GET_NAME(JsGetName.RTI_FIELD_IS), obj);
474+
if (result) {
475+
// Type test passes put would fail with sound null safety.
476+
var t1 = runtimeType(obj);
477+
var t2 = rti.createRuntimeType(testRti);
478+
_nullWarn('$t1 is not a subtype of $t2.');
479+
}
480+
return result;
481+
} else {
482+
if (obj == null) {
483+
return _equalType(type, Null) ||
484+
_isTop(type) ||
485+
_jsInstanceOf(type, NullableType);
486+
}
487+
return isSubtypeOf(getReifiedType(obj), type);
463488
}
464-
return isSubtypeOf(getReifiedType(obj), type);
465489
}
466490

467491
/// General implementation of the Dart `as` operator.
@@ -477,12 +501,33 @@ cast(obj, type) {
477501
// Check the null comparison cache to avoid emitting repeated warnings.
478502
_nullWarnOnType(type);
479503
return obj;
504+
}
505+
if (JS_GET_FLAG('NEW_RUNTIME_TYPES')) {
506+
// When running without sound null safety casts are dispatched here to issue
507+
// optional warnings/errors when casts pass but would fail with sound null
508+
// safety.
509+
var testRti = JS<rti.Rti>('!', '#', type);
510+
var objRti = JS<rti.Rti>('!', '#', getReifiedType(obj));
511+
var legacyErasedRecipe = rti.Rti.getLegacyErasedRecipe(testRti);
512+
var legacyErasedType = rti.findType(legacyErasedRecipe);
513+
legacyTypeChecks = false;
514+
// Call `isSubtype()` to avoid throwing an error if it fails.
515+
var result = rti.isSubtype(
516+
JS_EMBEDDED_GLOBAL('', RTI_UNIVERSE), objRti, legacyErasedType);
517+
legacyTypeChecks = true;
518+
if (result) return obj;
519+
// Perform the actual cast and allow the error to be thrown if it fails.
520+
JS('bool', '#.#(#)', testRti, JS_GET_NAME(JsGetName.RTI_FIELD_AS), obj);
521+
// Subtype check passes put would fail with sound null safety.
522+
var t1 = runtimeType(obj);
523+
var t2 = rti.createRuntimeType(testRti);
524+
_nullWarn('$t1 is not a subtype of $t2.');
525+
return obj;
480526
} else {
481527
var actual = getReifiedType(obj);
482528
if (isSubtypeOf(actual, type)) return obj;
529+
return castError(obj, type);
483530
}
484-
485-
return castError(obj, type);
486531
}
487532

488533
bool test(bool? obj) {

sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/runtime.dart

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,15 @@ import 'dart:collection';
1010

1111
import 'dart:_debugger' show stackTraceMapper, trackCall;
1212
import 'dart:_foreign_helper'
13-
show JS, JS_CLASS_REF, JS_GET_FLAG, JS_GET_NAME, JSExportName, rest, spread;
13+
show
14+
JS,
15+
JS_CLASS_REF,
16+
JS_EMBEDDED_GLOBAL,
17+
JS_GET_FLAG,
18+
JS_GET_NAME,
19+
JSExportName,
20+
rest,
21+
spread;
1422
import 'dart:_interceptors'
1523
show
1624
JSArray,
@@ -37,9 +45,13 @@ import 'dart:_js_helper'
3745
import 'dart:_js_shared_embedded_names';
3846
import 'dart:_rti' as rti
3947
show
48+
createRuntimeType,
4049
constructorRtiCachePropertyName,
50+
findType,
4151
instanceType,
42-
interfaceTypeRecipePropertyName;
52+
interfaceTypeRecipePropertyName,
53+
isSubtype,
54+
Rti;
4355

4456
export 'dart:_debugger' show getDynamicStats, clearDynamicStats, trackCall;
4557

sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/types.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,15 @@ external bool compileTimeFlag(String flag);
1616
_throwInvalidFlagError(String message) =>
1717
throw UnsupportedError('Invalid flag combination.\n$message');
1818

19+
/// When running the new runtime type system with weak null safety this flag
20+
/// gets toggled to change the behavior of the dart:_rti library when performing
21+
/// `is` and `as` checks.
22+
///
23+
/// This allows DDC to produce optional warnings or errors when tests pass but
24+
/// would fail in sound null safety.
25+
@notNull
26+
bool legacyTypeChecks = !compileTimeFlag("soundNullSafety");
27+
1928
@notNull
2029
bool _weakNullSafetyWarnings = false;
2130

0 commit comments

Comments
 (0)