@@ -661,6 +661,56 @@ dindex(obj, index) => callMethod(obj, '_get', null, [index], null, '[]');
661
661
dsetindex (obj, index, value) =>
662
662
callMethod (obj, '_set' , null , [index, value], null , '[]=' );
663
663
664
+ // TODO(nshahan): Cleanup the following `is`, `as`, and isSubtype
665
+ // implementations.
666
+ //
667
+ // The logic is currently too convoluted but is temporary while the
668
+ // implementation gets added directly to the dart:rti library and moved out of
669
+ // the DDC only runtime library.
670
+ //
671
+ // These methods are dead code when running with sound null safety.
672
+ //
673
+ // Ideally each runtime should be able to set the compile time flag
674
+ // `JS_GET_FLAG('EXTRA_NULL_SAFETY_CHECKS')` and these helpers will no longer be
675
+ // needed. The dart:rti library will know how to perform the checks and call a
676
+ // method for the backend specific warning/error/logging.
677
+ //
678
+ // As currently written flow goes as follows:
679
+ // 1) A type check (`is`, `as`, or isSubtype) is dispatched to the
680
+ // corresponding helper.
681
+ // 2) The legacy stars are stripped out of the test type. This is expected to
682
+ // prove less impactful over time. Once all code has been migrated to ^2.12
683
+ // getting a legacy type on the RHS of a type test is much harder. Possibly
684
+ // only achievable through type checks involving type variables
685
+ // instantiated in constants.
686
+ // 3) Flags are set manually to tell the dart:rti library how to perform the
687
+ // upcoming type check:
688
+ // - `extraNullSafetyChecks`: This is currently used to signal that if the type
689
+ // check reaches the full `isSubtype()` implementation, the legacy stars
690
+ // should be erased from the rti that is extracted from the value being
691
+ // tested.
692
+ // - `legacyTypeChecks`: This is currently used to signal that the test
693
+ // should be performed with sound semantics.
694
+ // 4) Perform the sound type check using the legacy erased test type. This may
695
+ // fall into a fast path optimization for simple checks like
696
+ // `val is String?`. If the test involves a more complicated check it might
697
+ // trigger the extraction of the rti from the value being tested and passed
698
+ // to the full `isSubtype()` implementation. This uncertainty requires the
699
+ // two flags described above.
700
+ // - Note: `isSubtype()` uses two caches (sound and unsound) to speedup
701
+ // repeated checks if the results have already been determined. This
702
+ // could be reduced to a single cache when a reporting method is called
703
+ // from the dart:rti library directly. The single cache could store one
704
+ // of three values: "pass", "fail", and "disagree" (passes when unsound
705
+ // but would fail if sound).
706
+ // 5) Reset the flags back to the default state.
707
+ // 6) If the check passes then it will also pass in weak mode so simply return
708
+ // the result.
709
+ // 7) Otherwise, perform the same type check in weak mode without erasing
710
+ // any legacy types.
711
+ // 8) If the result disagrees with the sound mode check issue a warning.
712
+ // 9) Return the result.
713
+
664
714
/// General implementation of the Dart `is` operator.
665
715
///
666
716
/// Some basic cases are handled directly by the `.is` methods that are attached
@@ -670,22 +720,33 @@ dsetindex(obj, index, value) =>
670
720
@JSExportName ('is' )
671
721
bool instanceOf (obj, type) {
672
722
if (JS_GET_FLAG ('NEW_RUNTIME_TYPES' )) {
673
- var testRti = JS <rti.Rti >('!' , '#' , type);
674
723
// When running without sound null safety is type tests are dispatched here
675
724
// to issue optional warnings/errors when the result is true but would be
676
725
// false with sound null safety.
677
- var legacyErasedRecipe =
678
- rti.Rti .getLegacyErasedRecipe (JS <rti.Rti >('!' , '#' , testRti));
679
- var legacyErasedType = rti.findType (legacyErasedRecipe);
726
+ var testRti = JS <rti.Rti >('!' , '#' , type);
727
+ // TODO(nshahan): Move to isSubtype in dart:rti once all fast path checks
728
+ // have been updated to be aware of
729
+ // `JS_GET_FLAG('EXTRA_NULL_SAFETY_CHECKS')`.
730
+ rti.Rti legacyErasedType;
731
+ if (JS_GET_FLAG ('PRINT_LEGACY_STARS' )) {
732
+ // When preserving the legacy stars in the runtime type, avoid caching
733
+ // the version with erased types on the Rti.
734
+ var legacyErasedRecipe = rti.Rti .getLegacyErasedRecipe (testRti);
735
+ legacyErasedType = rti.findType (legacyErasedRecipe);
736
+ } else {
737
+ legacyErasedType = rti.getLegacyErasedRti (testRti);
738
+ }
739
+ extraNullSafetyChecks = true ;
680
740
legacyTypeChecks = false ;
681
741
var result = JS <bool >('bool' , '#.#(#)' , legacyErasedType,
682
742
JS_GET_NAME (JsGetName .RTI_FIELD_IS ), obj);
743
+ extraNullSafetyChecks = false ;
683
744
legacyTypeChecks = true ;
684
745
if (result) return true ;
685
746
result =
686
747
JS ('bool' , '#.#(#)' , testRti, JS_GET_NAME (JsGetName .RTI_FIELD_IS ), obj);
687
748
if (result) {
688
- // Type test passes put would fail with sound null safety.
749
+ // Type test returned true but would be false with sound null safety.
689
750
var t1 = runtimeType (obj);
690
751
var t2 = rti.createRuntimeType (testRti);
691
752
_nullWarn ('$t1 is not a subtype of $t2 .' );
@@ -713,18 +774,33 @@ cast(obj, type) {
713
774
// optional warnings/errors when casts pass but would fail with sound null
714
775
// safety.
715
776
var testRti = JS <rti.Rti >('!' , '#' , type);
716
- var objRti = JS <rti.Rti >('!' , '#' , getReifiedType (obj));
717
- var legacyErasedRecipe = rti.Rti .getLegacyErasedRecipe (testRti);
718
- var legacyErasedType = rti.findType (legacyErasedRecipe);
777
+ if (obj == null && ! rti.isNullable (testRti)) {
778
+ // Allow cast to pass but warn that it would fail in sound null safety.
779
+ _nullWarnOnType (type);
780
+ return obj;
781
+ }
782
+ // TODO(nshahan): Move to isSubtype in dart:rti once all fast path checks
783
+ // have been updated to be aware of
784
+ // `JS_GET_FLAG('EXTRA_NULL_SAFETY_CHECKS')`.
785
+ rti.Rti legacyErasedType;
786
+ if (JS_GET_FLAG ('PRINT_LEGACY_STARS' )) {
787
+ // When preserving the legacy stars in the runtime type, avoid caching
788
+ // the version with erased types on the Rti.
789
+ var legacyErasedRecipe = rti.Rti .getLegacyErasedRecipe (testRti);
790
+ legacyErasedType = rti.findType (legacyErasedRecipe);
791
+ } else {
792
+ legacyErasedType = rti.getLegacyErasedRti (testRti);
793
+ }
794
+ extraNullSafetyChecks = true ;
719
795
legacyTypeChecks = false ;
720
- // Call `isSubtype()` to avoid throwing an error if it fails.
721
- var result = rti. isSubtype (
722
- JS_EMBEDDED_GLOBAL ( '' , RTI_UNIVERSE ), objRti, legacyErasedType) ;
796
+ var result = JS < bool >( '!' , '#.#(#)' , legacyErasedType,
797
+ JS_GET_NAME ( JsGetName . RTI_FIELD_IS ), obj);
798
+ extraNullSafetyChecks = false ;
723
799
legacyTypeChecks = true ;
724
800
if (result) return obj;
725
801
// Perform the actual cast and allow the error to be thrown if it fails.
726
802
JS ('' , '#.#(#)' , testRti, JS_GET_NAME (JsGetName .RTI_FIELD_AS ), obj);
727
- // Subtype check passes put would fail with sound null safety.
803
+ // Cast succeeds but would fail with sound null safety.
728
804
var t1 = runtimeType (obj);
729
805
var t2 = rti.createRuntimeType (testRti);
730
806
_nullWarn ('$t1 is not a subtype of $t2 .' );
@@ -756,17 +832,29 @@ bool _isSubtypeWithWarning(@notNull t1, @notNull t2) {
756
832
if (JS_GET_FLAG ('NEW_RUNTIME_TYPES' )) {
757
833
var t1Rti = JS <rti.Rti >('!' , '#' , t1);
758
834
var t2Rti = JS <rti.Rti >('!' , '#' , t2);
759
- var legacyErasedRecipe = rti.Rti .getLegacyErasedRecipe (t2Rti);
760
- var legacyErasedType = rti.findType (legacyErasedRecipe);
835
+ // TODO(nshahan): Move to isSubtype in dart:rti once all fast path checks
836
+ // have been updated to be aware of
837
+ // `JS_GET_FLAG('EXTRA_NULL_SAFETY_CHECKS')`.
838
+ rti.Rti legacyErasedType;
839
+ if (JS_GET_FLAG ('PRINT_LEGACY_STARS' )) {
840
+ // When preserving the legacy stars in the runtime type, avoid caching
841
+ // the version with erased types on the Rti.
842
+ var legacyErasedRecipe = rti.Rti .getLegacyErasedRecipe (t2Rti);
843
+ legacyErasedType = rti.findType (legacyErasedRecipe);
844
+ } else {
845
+ legacyErasedType = rti.getLegacyErasedRti (t2Rti);
846
+ }
847
+ extraNullSafetyChecks = true ;
761
848
legacyTypeChecks = false ;
762
849
var validSubtype = rti.isSubtype (
763
850
JS_EMBEDDED_GLOBAL ('' , RTI_UNIVERSE ), t1Rti, legacyErasedType);
851
+ extraNullSafetyChecks = false ;
764
852
legacyTypeChecks = true ;
765
853
if (validSubtype) return true ;
766
854
validSubtype =
767
855
rti.isSubtype (JS_EMBEDDED_GLOBAL ('' , RTI_UNIVERSE ), t1Rti, t2Rti);
768
856
if (validSubtype) {
769
- // Subtype check passes put would fail with sound null safety.
857
+ // Subtype check passes but would fail with sound null safety.
770
858
_nullWarn ('${rti .createRuntimeType (t1Rti )} '
771
859
'is not a subtype of '
772
860
'${rti .createRuntimeType (t2Rti )}.' );
0 commit comments