dart-sdk/pkg
Paul Berry 907e705307 Flow analysis: use a more precise split point for refutable patterns.
Previously, the flow control logic for patterns didn't use the
`FlowModel.split` or `FlowModel.unsplit` methods at all. This meant
that if a control flow join point occurred in pattern logic, flow
analysis would consider the split point to be whatever split point was
established by the enclosing expression or statement. In the case of
an if-case statement, it would consider the split point to be at the
beginning of the scrutinee expression.

Split points are used by flow analysis for the sole purpose of
ensuring that joins propagate type promotions the same way in dead
code as they do in live code (so that users introducing temporary
`throw` expressions or `return` statements into their code do not have
to deal with nuisance compile errors in the (now dead) code that
follows. The consequence of flow analysis considering the split point
to be at the beginning of the scrutinee expression is that if the
scrutinee expression is proven to always throw, then joins that arise
from the pattern or guard may not behave consistently with how they
would have behaved otherwise. For example:

    int getInt(Object o) => ...;
    void consumeInt(int i) { ... }
    test(int? i) {
      if (
          // (1)
          getInt('foo')
          case
              // (2)
              int()
          // (3)
          when i == null) {
      } else {
        // (4)
        consumeInt(i);
      }
    }

In the above code, there is a join point at (4), joining control flows
from (a) the situation where the pattern `int()` failed to match, and
(b) the situation where `i == null` evaluated to `false` (and hence
`i` is promoted to non-nullable `int`). Since the return type of
`getInt` is `int`, it's impossible for the pattern `int()` to fail, so
at the join point, control flow path (a) is considered
unreacable. Therefore the promotion from control flow path (b) is
kept, and so the call to `consumeInt` is valid.

In order to decide whether to preserve promotions from one of the
control flow paths leading up to a join, flow analysis only considers
reachability relative to the corresponding split point. Prior to this
change, the split point in question occurred at (1), so if the
expression `getInt('foo')` had been replaced with `getInt(throw
UnimplementedError())`, flow analysis would have considered both
control flow paths (a) and (b) to be unreachable relative to the split
point, so it would not have preserved the promotion from (b), and
there would have been a compile time error in the (now dead) call to
`consumeInt`.

This change moves the split point from (1) to (2), so that changing
`getInt('foo')` to `getInt(throw UnimplementedError())` no longer
causes any change in type promotion behavior.

The implementation of this change is to add calls to `FlowModel.split`
and `FlowModel.unsplit` around all top-level patterns. At first glance
this might appear to affect the behavior of all patterns, but actually
the only user-visible effect is on patterns in if-case statements,
because:

- In switch statements and switch expressions, there is already a
  split point before each case.

- In irrefutable patterns, there is no user-visible effect, because
  irrefutable patterns cannot fail to match, and therefore don't do
  any control flow joins.

This change allows the split points for patterns to be determined by a
simple syntactic rule, which will facilitate some refactoring of split
points that I am currently working on.

Change-Id: I55573ba5c28b2f2e6bba8731f9e3b02613b6beb2
Bug: https://github.com/dart-lang/sdk/issues/53167
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/319381
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
2023-08-11 17:09:49 +00:00
..
_fe_analyzer_shared Flow analysis: use a more precise split point for refutable patterns. 2023-08-11 17:09:49 +00:00
_js_interop_checks [dart:js_interop] Disallow user @staticInterop classes from subtyping most dart:_js_types types 2023-08-08 19:17:25 +00:00
analysis_server [analyzer] Mark DEFAULT_LIST_CONSTRUCTOR as removed 2023-08-11 14:54:58 +00:00
analysis_server_client [analysis_server] Change LSP-over-Legacy to be wrapped with the original protocol 2023-08-06 14:32:18 +00:00
analyzer Extension types. Compute notSimplyBounded flag. 2023-08-11 16:39:53 +00:00
analyzer_cli Update old linter site links to dart.dev 2023-08-04 19:45:23 +00:00
analyzer_plugin [analysis_server] Don't produce fix-all-in-file fixes when there's no individual fix 2023-08-08 18:20:39 +00:00
analyzer_utilities
async_helper
bisect_dart [tool] Bisection tool 2023-07-13 13:22:26 +00:00
build_integration
compiler Revert "[dart2js] Use symbols for isolate tags" 2023-08-11 12:48:31 +00:00
dap [dap] Regenerate DAP classes based on latest published version of spec 2023-06-20 17:29:24 +00:00
dart2js_info [dart2js] cleanup backend usage (I) 2023-08-10 22:45:54 +00:00
dart2js_runtime_metrics
dart2js_tools
dart2native
dart2wasm [dart2wasm] Fix normalization of nullable FutureOr 2023-08-11 09:08:32 +00:00
dart_internal Increase maximum sdk version constraint on dart_internal to 3.3.0 2023-07-27 00:29:42 +00:00
dartdev [deps] rev native 2023-08-08 19:32:30 +00:00
dds [ DDS ] Prepare for 2.9.4 release 2023-08-01 22:59:17 +00:00
dds_service_extensions Revert "Revert "Send DAP events through DDS"" - only check for event handler when DDS URI is also set. 2023-06-22 21:43:22 +00:00
dev_compiler [ddc] Fix runtime failure on evaluation of expressions that use JS interop and extension types 2023-08-11 16:15:00 +00:00
expect [vm] Add @pragma('vm:keep-name') annotation 2023-06-16 10:22:23 +00:00
front_end [cfe] Compute instantiated representation type 2023-08-11 10:32:00 +00:00
frontend_server [frontend_server] Add --canary flag 2023-06-27 22:21:04 +00:00
js
js_ast [dart2js] Better const Maps and Sets 2023-06-20 23:44:08 +00:00
js_runtime
js_shared
kernel [cfe] Compute instantiated representation type 2023-08-11 10:32:00 +00:00
language_versioning_2_7_test
meta meta 1.10.0 2023-08-09 18:07:32 +00:00
mmap
modular_test [modular_test] second attempt to fix shards. 2023-07-27 22:29:07 +00:00
native_stack_traces [vm] Rework awaiter stack unwinding. 2023-06-30 14:03:03 +00:00
nnbd_migration Expose greatestLowerBound() from TypeSystem. 2023-08-04 20:47:46 +00:00
scrape
smith
sourcemap_testing
status_file [infra] Make the sanitizer a first-class status variable. 2023-07-10 17:46:31 +00:00
telemetry
test_runner [ddc] Seal the native Object prototype in test infra 2023-08-10 19:45:59 +00:00
testing
vm [cfe] Compute instantiated representation type 2023-08-11 10:32:00 +00:00
vm_service [dart:developer] Add static Service.getObjectId method 2023-08-04 14:18:38 +00:00
vm_service_protos [VM/Service] Create package:vm_service_protos for distributing code for working with Perfetto protos 2023-06-15 19:01:00 +00:00
vm_snapshot_analysis [pkg/vm_snapshot_analysis] Allow old 'patched_class_' field. 2023-07-07 11:36:59 +00:00
wasm_builder [wasm_builder] Fix failure type of br_on_cast[_fail] 2023-08-09 08:24:37 +00:00
.gitignore
analysis_options.yaml
BUILD.gn
OWNERS
pkg.dart
pkg.status [deps/ffi] Unbundle package:native_assets_builder 2023-06-28 09:09:09 +00:00
README.md

Package validation

The packages in pkg/ are automatically validated on the LUCI CI bots. The validation is largely done by the tools/package_deps package; it can be tested locally via:

dart tools/package_deps/bin/package_deps.dart

Packages which are published

There are several packages developed in pkg/ which are published to pub. Validation of these packages is particularly important because the pub tools are not used for these packages during development; we get our dependency versions from the DEPS file. Its very easy for the dependencies specified in a package's pubspec file to get out of date wrt the packages and versions actually used.

In order to better ensure we're publishing correct packages, we validate some properties of the pubspec files on our CI system. These validations include:

  • that the dependencies listed in the pubspec are used in the package
  • that all the packages used by the source are listed in the pubspec
  • that we don't use relative path deps to pkg/ or third_party/ packages

Packages which are not published

For packages in pkg/ which we do not intend to be published, we put the following comment in the pubspec.yaml file:

# This package is not intended for consumption on pub.dev. DO NOT publish.
publish_to: none

These pubspecs are still validated by the package validation tool. The contents are more informational as the pubspecs for these packages are not consumed by the pub tool or ecosystem.

We validate:

  • that the dependencies listed in the pubspec are used in the package
  • that all the packages used by the source are listed in the pubspec
  • that a reference to a pkg/ package is done via a relative path dependency