From 5ecf10052fc2bf59f3f427dd21d21f22cb74acd9 Mon Sep 17 00:00:00 2001 From: Nate Wilson Date: Thu, 3 Oct 2024 12:21:04 -0600 Subject: [PATCH] pattern-matching refactor (#154753) This pull request aims to improve code readability, based on feedback gathered in a recent design doc.
There are two factors that hugely impact how easy it is to understand a piece of code: **verbosity** and **complexity**. Reducing **verbosity** is important, because boilerplate makes a project more difficult to navigate. It also has a tendency to make one's eyes gloss over, and subtle typos/bugs become more likely to slip through. Reducing **complexity** makes the code more accessible to more people. This is especially important for open-source projects like Flutter, where the code is read by those who make contributions, as well as others who read through source code as they debug their own projects.

The following examples show how pattern-matching might affect these two factors:

Example 1 (GOOD)

[click to expand]
```dart if (ancestor case InheritedElement(:final InheritedTheme widget)) { themes.add(widget); } ``` Without using patterns, this might expand to ```dart if (ancestor is InheritedElement) { final InheritedWidget widget = ancestor.widget; if (widget is InheritedTheme) { themes.add(widget); } } ``` Had `ancestor` been a non-local variable, it would need to be "converted" as well: ```dart final Element ancestor = this.ancestor; if (ancestor is InheritedElement) { final InheritedWidget inheritedWidget = ancestor.widget; if (widget is InheritedTheme) { themes.add(theme); } } ```

Example 2 (BAD)

[click to expand]
```dart if (widget case PreferredSizeWidget(preferredSize: Size(:final double height))) { return height; } ``` Assuming `widget` is a non-local variable, this would expand to: ```dart final Widget widget = this.widget; if (widget is PreferredSizeWidget) { return widget.preferredSize.height; } ```
In both of the examples above, an `if-case` statement simultaneously verifies that an object meets the specified criteria and performs a variable assignment accordingly. But there are some differences: Example 2 uses a more deeply-nested pattern than Example 1 but makes fewer useful checks. **Example 1:** - checks that `ancestor` is an `InheritedElement` - checks that the inherited element's `widget` is an `InheritedTheme` **Example 2:** - checks that `widget` is a `PreferredSizeWidget` (every `PreferredSizeWidget` has a `size` field, and every `Size` has a `height` field)

I feel hesitant to try presenting a set of cut-and-dry rules as to which scenarios should/shouldn't use pattern-matching, since there are an abundance of different types of patterns, and an abundance of different places where they might be used. But hopefully the conversations we've had recently will help us converge toward a common intuition of how pattern-matching can best be utilized for improved readability.

- resolves https://github.com/flutter/flutter/issues/152313 - Design Doc: [flutter.dev/go/dart-patterns](https://flutter.dev/go/dart-patterns) --- .../custom_rules/avoid_future_catcherror.dart | 12 +- .../dev/tools/gen_defaults/bin/template.dart | 16 +- .../localization/bin/gen_localizations.dart | 12 +- dev/devicelab/lib/framework/ios.dart | 12 +- .../lib/framework/metrics_center.dart | 8 +- .../demo/shrine/model/app_state_model.dart | 10 +- .../flutter_gallery/test/live_smoketest.dart | 13 +- .../lib/studies/reply/adaptive_nav.dart | 18 +-- dev/manual_tests/lib/focus.dart | 23 ++- dev/snippets/bin/snippets.dart | 9 +- .../focus_traversal_group.0.dart | 10 +- .../lib/src/foundation/assertions.dart | 16 +- .../src/foundation/synchronous_future.dart | 9 +- .../flutter/lib/src/material/dropdown.dart | 43 ++---- .../lib/src/material/input_decorator.dart | 14 +- packages/flutter/lib/src/material/tabs.dart | 23 ++- .../flutter/lib/src/painting/alignment.dart | 82 +++------- .../lib/src/painting/box_decoration.dart | 28 ++-- .../lib/src/painting/matrix_utils.dart | 53 +++---- .../flutter/lib/src/rendering/editable.dart | 8 +- .../lib/src/rendering/mouse_tracker.dart | 11 +- .../flutter/lib/src/rendering/paragraph.dart | 8 +- .../flutter/lib/src/rendering/proxy_box.dart | 42 ++---- .../lib/src/services/message_codecs.dart | 6 +- .../lib/src/services/platform_views.dart | 24 ++- packages/flutter/lib/src/widgets/actions.dart | 22 +-- .../flutter/lib/src/widgets/focus_scope.dart | 23 ++- .../flutter/lib/src/widgets/framework.dart | 15 +- .../lib/src/widgets/inherited_theme.dart | 3 +- .../flutter/lib/src/widgets/navigator.dart | 29 ++-- .../lib/src/widgets/platform_menu_bar.dart | 2 +- .../test/services/message_codecs_testing.dart | 31 ++-- .../test/widgets/widget_inspector_test.dart | 44 +++--- .../lib/src/common/handler_factory.dart | 13 +- ...me_request_pending_latency_summarizer.dart | 20 ++- .../flutter_goldens/lib/flutter_goldens.dart | 26 +--- packages/flutter_test/lib/src/binding.dart | 23 ++- .../lib/src/build_system/targets/assets.dart | 14 +- packages/flutter_tools/lib/src/daemon.dart | 6 +- .../lib/src/dart_pub_json_formatter.dart | 38 ++--- .../lib/src/flutter_manifest.dart | 42 ++---- .../lib/src/ios/core_devices.dart | 24 ++- .../lib/src/ios/xcode_debug.dart | 6 +- ...package_manager_integration_migration.dart | 141 ++++++------------ packages/flutter_tools/lib/src/vmservice.dart | 4 +- .../test/integration.shard/test_driver.dart | 13 +- 46 files changed, 378 insertions(+), 671 deletions(-) diff --git a/dev/bots/custom_rules/avoid_future_catcherror.dart b/dev/bots/custom_rules/avoid_future_catcherror.dart index 1ff83be792..ce9d014115 100644 --- a/dev/bots/custom_rules/avoid_future_catcherror.dart +++ b/dev/bots/custom_rules/avoid_future_catcherror.dart @@ -77,13 +77,11 @@ class _Visitor extends RecursiveAstVisitor { @override void visitMethodInvocation(MethodInvocation node) { - if (node.methodName.name != 'onError' && node.methodName.name != 'catchError') { - return; + if (node case MethodInvocation( + methodName: SimpleIdentifier(name: 'onError' || 'catchError'), + realTarget: Expression(staticType: DartType(isDartAsyncFuture: true)), + )) { + _offendingNodes.add(node); } - final DartType? targetType = node.realTarget?.staticType; - if (targetType == null || !targetType.isDartAsyncFuture) { - return; - } - _offendingNodes.add(node); } } diff --git a/dev/bots/test/analyze-gen-defaults/dev/tools/gen_defaults/bin/template.dart b/dev/bots/test/analyze-gen-defaults/dev/tools/gen_defaults/bin/template.dart index 1e611feb98..9478d29088 100644 --- a/dev/bots/test/analyze-gen-defaults/dev/tools/gen_defaults/bin/template.dart +++ b/dev/bots/test/analyze-gen-defaults/dev/tools/gen_defaults/bin/template.dart @@ -169,16 +169,12 @@ abstract class TokenTemplate { } String? _numToString(Object? value, [int? digits]) { - if (value == null) { - return null; - } - if (value is num) { - if (value == double.infinity) { - return 'double.infinity'; - } - return digits == null ? value.toString() : value.toStringAsFixed(digits); - } - return getToken(value as String).toString(); + return switch (value) { + null => null, + double.infinity => 'double.infinity', + num() => digits == null ? value.toString() : value.toStringAsFixed(digits), + _ => getToken(value as String).toString(), + }; } /// Generate an elevation value for the given component token. diff --git a/dev/bots/test/analyze-test-input/root/dev/tools/localization/bin/gen_localizations.dart b/dev/bots/test/analyze-test-input/root/dev/tools/localization/bin/gen_localizations.dart index 8505054ed5..bc0f30f542 100644 --- a/dev/bots/test/analyze-test-input/root/dev/tools/localization/bin/gen_localizations.dart +++ b/dev/bots/test/analyze-test-input/root/dev/tools/localization/bin/gen_localizations.dart @@ -3,13 +3,11 @@ // found in the LICENSE file. void main(List args) { - String type = ''; - if (args[0] == '--material') { - type = 'material'; - } - if (args[0] == '--cupertino') { - type = 'cupertino'; - } + final String type = switch (args.first) { + '--material' => 'material', + '--cupertino' => 'cupertino', + _ => '', + }; print(''' // Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be diff --git a/dev/devicelab/lib/framework/ios.dart b/dev/devicelab/lib/framework/ios.dart index 7fc35606eb..2f61af1299 100644 --- a/dev/devicelab/lib/framework/ios.dart +++ b/dev/devicelab/lib/framework/ios.dart @@ -85,14 +85,10 @@ Future testWithNewIOSSimulator( final String? iosKey = decodeResult.keys .where((String key) => key.contains('iphoneos')) .firstOrNull; - final Object? iosDetails = decodeResult[iosKey]; - String? runtimeBuildForSelectedXcode; - if (iosDetails != null && iosDetails is Map) { - final Object? preferredBuild = iosDetails['preferredBuild']; - if (preferredBuild is String) { - runtimeBuildForSelectedXcode = preferredBuild; - } - } + final String? runtimeBuildForSelectedXcode = switch (decodeResult[iosKey]) { + {'preferredBuild': final String build} => build, + _ => null, + }; String? iOSSimRuntime; diff --git a/dev/devicelab/lib/framework/metrics_center.dart b/dev/devicelab/lib/framework/metrics_center.dart index 1f5adda62a..2cb78ab48b 100644 --- a/dev/devicelab/lib/framework/metrics_center.dart +++ b/dev/devicelab/lib/framework/metrics_center.dart @@ -12,14 +12,12 @@ import 'package:metrics_center/metrics_center.dart'; /// /// It supports both token and credential authentications. Future connectFlutterDestination() async { - const String kTokenPath = 'TOKEN_PATH'; - const String kGcpProject = 'GCP_PROJECT'; final Map env = Platform.environment; final bool isTesting = env['IS_TESTING'] == 'true'; - if (env.containsKey(kTokenPath) && env.containsKey(kGcpProject)) { + if (env case {'TOKEN_PATH': final String path, 'GCP_PROJECT': final String project}) { return FlutterDestination.makeFromAccessToken( - File(env[kTokenPath]!).readAsStringSync(), - env[kGcpProject]!, + File(path).readAsStringSync(), + project, isTesting: isTesting, ); } diff --git a/dev/integration_tests/flutter_gallery/lib/demo/shrine/model/app_state_model.dart b/dev/integration_tests/flutter_gallery/lib/demo/shrine/model/app_state_model.dart index 71ac5d763c..a28668cd09 100644 --- a/dev/integration_tests/flutter_gallery/lib/demo/shrine/model/app_state_model.dart +++ b/dev/integration_tests/flutter_gallery/lib/demo/shrine/model/app_state_model.dart @@ -70,16 +70,12 @@ class AppStateModel extends Model { // Removes an item from the cart. void removeItemFromCart(int productId) { - final int? value = _productsInCart[productId]; - - if (value != null) { - if (_productsInCart[productId] == 1) { + switch (_productsInCart[productId]) { + case 1: _productsInCart.remove(productId); - } else { + case final int value: _productsInCart[productId] = value - 1; - } } - notifyListeners(); } diff --git a/dev/integration_tests/flutter_gallery/test/live_smoketest.dart b/dev/integration_tests/flutter_gallery/test/live_smoketest.dart index cbc7fe9a60..abcbf92156 100644 --- a/dev/integration_tests/flutter_gallery/test/live_smoketest.dart +++ b/dev/integration_tests/flutter_gallery/test/live_smoketest.dart @@ -92,15 +92,10 @@ Future main() async { } final Finder backFinder = find.byElementPredicate( - (Element element) { - final Widget widget = element.widget; - if (widget is Tooltip) { - return widget.message == 'Back'; - } - if (widget is CupertinoNavigationBarBackButton) { - return true; - } - return false; + (Element element) => switch (element.widget) { + Tooltip(message: 'Back') => true, + CupertinoNavigationBarBackButton() => true, + _ => false, }, description: 'Material or Cupertino back button', ); diff --git a/dev/integration_tests/new_gallery/lib/studies/reply/adaptive_nav.dart b/dev/integration_tests/new_gallery/lib/studies/reply/adaptive_nav.dart index 088fb3f37d..d9bcdfb468 100644 --- a/dev/integration_tests/new_gallery/lib/studies/reply/adaptive_nav.dart +++ b/dev/integration_tests/new_gallery/lib/studies/reply/adaptive_nav.dart @@ -574,16 +574,14 @@ class _MobileNavState extends State<_MobileNav> with TickerProviderStateMixin { } bool _handleScrollNotification(ScrollNotification notification) { - if (notification.depth == 0) { - if (notification is UserScrollNotification) { - switch (notification.direction) { - case ScrollDirection.forward: - _bottomAppBarController.forward(); - case ScrollDirection.reverse: - _bottomAppBarController.reverse(); - case ScrollDirection.idle: - break; - } + if (notification case UserScrollNotification(depth: 0)) { + switch (notification.direction) { + case ScrollDirection.forward: + _bottomAppBarController.forward(); + case ScrollDirection.reverse: + _bottomAppBarController.reverse(); + case ScrollDirection.idle: + break; } } return false; diff --git a/dev/manual_tests/lib/focus.dart b/dev/manual_tests/lib/focus.dart index 6680909e45..5c9da3d8d3 100644 --- a/dev/manual_tests/lib/focus.dart +++ b/dev/manual_tests/lib/focus.dart @@ -108,20 +108,15 @@ class _FocusDemoState extends State { return KeyEventResult.handled; } } - if (event.logicalKey == LogicalKeyboardKey.arrowLeft) { - node.focusInDirection(TraversalDirection.left); - return KeyEventResult.handled; - } - if (event.logicalKey == LogicalKeyboardKey.arrowRight) { - node.focusInDirection(TraversalDirection.right); - return KeyEventResult.handled; - } - if (event.logicalKey == LogicalKeyboardKey.arrowUp) { - node.focusInDirection(TraversalDirection.up); - return KeyEventResult.handled; - } - if (event.logicalKey == LogicalKeyboardKey.arrowDown) { - node.focusInDirection(TraversalDirection.down); + final TraversalDirection? direction = switch (event.logicalKey) { + LogicalKeyboardKey.arrowLeft => TraversalDirection.left, + LogicalKeyboardKey.arrowRight => TraversalDirection.right, + LogicalKeyboardKey.arrowUp => TraversalDirection.up, + LogicalKeyboardKey.arrowDown => TraversalDirection.down, + _ => null, + }; + if (direction != null) { + node.focusInDirection(direction); return KeyEventResult.handled; } } diff --git a/dev/snippets/bin/snippets.dart b/dev/snippets/bin/snippets.dart index eb73a4b8ff..2d1042d07e 100644 --- a/dev/snippets/bin/snippets.dart +++ b/dev/snippets/bin/snippets.dart @@ -57,13 +57,12 @@ String getChannelName({ Platform platform = const LocalPlatform(), ProcessManager processManager = const LocalProcessManager(), }) { - final String? envReleaseChannel = platform.environment['LUCI_BRANCH']?.trim(); - if (['master', 'stable', 'main'].contains(envReleaseChannel)) { + switch (platform.environment['LUCI_BRANCH']?.trim()) { // Backward compatibility: Still support running on "master", but pretend it is "main". - if (envReleaseChannel == 'master') { + case 'master' || 'main': return 'main'; - } - return envReleaseChannel!; + case 'stable': + return 'stable'; } final RegExp gitBranchRegexp = RegExp(r'^## (?.*)'); diff --git a/examples/api/lib/widgets/focus_traversal/focus_traversal_group.0.dart b/examples/api/lib/widgets/focus_traversal/focus_traversal_group.0.dart index eaaccadef1..7b492e67da 100644 --- a/examples/api/lib/widgets/focus_traversal/focus_traversal_group.0.dart +++ b/examples/api/lib/widgets/focus_traversal/focus_traversal_group.0.dart @@ -71,12 +71,10 @@ class _OrderedButtonState extends State> { @override Widget build(BuildContext context) { - FocusOrder order; - if (widget.order is num) { - order = NumericFocusOrder((widget.order as num).toDouble()); - } else { - order = LexicalFocusOrder(widget.order.toString()); - } + final FocusOrder order = switch (widget.order) { + final num number => NumericFocusOrder(number.toDouble()), + final Object? object => LexicalFocusOrder(object.toString()), + }; return FocusTraversalOrder( order: order, diff --git a/packages/flutter/lib/src/foundation/assertions.dart b/packages/flutter/lib/src/foundation/assertions.dart index aa433f13bb..081766441a 100644 --- a/packages/flutter/lib/src/foundation/assertions.dart +++ b/packages/flutter/lib/src/foundation/assertions.dart @@ -676,16 +676,12 @@ class FlutterErrorDetails with Diagnosticable { if (exception is num) { properties.add(ErrorDescription('The number $exception was $verb.')); } else { - final DiagnosticsNode errorName; - if (exception is AssertionError) { - errorName = ErrorDescription('assertion'); - } else if (exception is String) { - errorName = ErrorDescription('message'); - } else if (exception is Error || exception is Exception) { - errorName = ErrorDescription('${exception.runtimeType}'); - } else { - errorName = ErrorDescription('${exception.runtimeType} object'); - } + final DiagnosticsNode errorName = ErrorDescription(switch (exception) { + AssertionError() => 'assertion', + String() => 'message', + Error() || Exception() => '${exception.runtimeType}', + _ => '${exception.runtimeType} object', + }); properties.add(ErrorDescription('The following $errorName was $verb:')); if (diagnosticable != null) { diagnosticable.debugFillProperties(properties); diff --git a/packages/flutter/lib/src/foundation/synchronous_future.dart b/packages/flutter/lib/src/foundation/synchronous_future.dart index 2b08456c69..6ac4d08a76 100644 --- a/packages/flutter/lib/src/foundation/synchronous_future.dart +++ b/packages/flutter/lib/src/foundation/synchronous_future.dart @@ -40,11 +40,10 @@ class SynchronousFuture implements Future { @override Future then(FutureOr Function(T value) onValue, { Function? onError }) { - final FutureOr result = onValue(_value); - if (result is Future) { - return result; - } - return SynchronousFuture(result); + return switch (onValue(_value)) { + final Future result => result, + final R result => SynchronousFuture(result), + }; } @override diff --git a/packages/flutter/lib/src/material/dropdown.dart b/packages/flutter/lib/src/material/dropdown.dart index ed1d15ee2c..0cbbf6bec8 100644 --- a/packages/flutter/lib/src/material/dropdown.dart +++ b/packages/flutter/lib/src/material/dropdown.dart @@ -1700,7 +1700,6 @@ class DropdownButtonFormField extends FormField { ? effectiveHint != null : effectiveHint != null || effectiveDisabledHint != null; final bool isEmpty = !showSelectedItem && !isHintOrDisabledHintAvailable; - final bool hasError = effectiveDecoration.errorText != null; // An unfocusable Focus widget so that this widget can detect if its // descendants have focus or not. @@ -1709,31 +1708,17 @@ class DropdownButtonFormField extends FormField { skipTraversal: true, child: Builder(builder: (BuildContext context) { final bool isFocused = Focus.of(context).hasFocus; - InputBorder? resolveInputBorder() { - if (hasError) { - if (isFocused) { - return effectiveDecoration.focusedErrorBorder; - } - return effectiveDecoration.errorBorder; - } - if (isFocused) { - return effectiveDecoration.focusedBorder; - } - if (effectiveDecoration.enabled) { - return effectiveDecoration.enabledBorder; - } - return effectiveDecoration.border; - } - BorderRadius? effectiveBorderRadius() { - final InputBorder? inputBorder = resolveInputBorder(); - if (inputBorder is OutlineInputBorder) { - return inputBorder.borderRadius; - } - if (inputBorder is UnderlineInputBorder) { - return inputBorder.borderRadius; - } - return null; - } + late final InputBorder? resolvedBorder = switch (( + enabled: effectiveDecoration.enabled, + focused: isFocused, + error: effectiveDecoration.errorText != null, + )) { + (enabled: _, focused: true, error: true) => effectiveDecoration.focusedErrorBorder, + (enabled: _, focused: _, error: true) => effectiveDecoration.errorBorder, + (enabled: _, focused: true, error: _) => effectiveDecoration.focusedBorder, + (enabled: true, focused: _, error: _) => effectiveDecoration.enabledBorder, + (enabled: false, focused: _, error: _) => effectiveDecoration.border, + }; return DropdownButtonHideUnderline( child: DropdownButton._formField( @@ -1760,7 +1745,11 @@ class DropdownButtonFormField extends FormField { menuMaxHeight: menuMaxHeight, enableFeedback: enableFeedback, alignment: alignment, - borderRadius: borderRadius ?? effectiveBorderRadius(), + borderRadius: borderRadius ?? switch (resolvedBorder) { + final OutlineInputBorder border => border.borderRadius, + final UnderlineInputBorder border => border.borderRadius, + _ => null, + }, // Clear the decoration hintText because DropdownButton has its own hint logic. inputDecoration: effectiveDecoration.copyWith( errorText: field.errorText, diff --git a/packages/flutter/lib/src/material/input_decorator.dart b/packages/flutter/lib/src/material/input_decorator.dart index 4a45868703..08c13d661e 100644 --- a/packages/flutter/lib/src/material/input_decorator.dart +++ b/packages/flutter/lib/src/material/input_decorator.dart @@ -931,16 +931,10 @@ class _RenderDecoration extends RenderBox with SlottedContainerRenderObjectMixin required ChildLayouter layoutChild, required _ChildBaselineGetter getBaseline, }) { - final RenderBox? counter = this.counter; - Size counterSize; - final double counterAscent; - if (counter != null) { - counterSize = layoutChild(counter, constraints); - counterAscent = getBaseline(counter, constraints); - } else { - counterSize = Size.zero; - counterAscent = 0.0; - } + final (Size counterSize, double counterAscent) = switch (counter) { + final RenderBox box => (layoutChild(box, constraints), getBaseline(box, constraints)), + null => (Size.zero, 0.0), + }; final BoxConstraints helperErrorConstraints = constraints.deflate(EdgeInsets.only(left: counterSize.width)); final double helperErrorHeight = layoutChild(helperError, helperErrorConstraints).height; diff --git a/packages/flutter/lib/src/material/tabs.dart b/packages/flutter/lib/src/material/tabs.dart index 45de852821..34969c9b2b 100644 --- a/packages/flutter/lib/src/material/tabs.dart +++ b/packages/flutter/lib/src/material/tabs.dart @@ -1700,22 +1700,17 @@ class _TabBarState extends State { } final List wrappedTabs = List.generate(widget.tabs.length, (int index) { - const double verticalAdjustment = (_kTextAndIconTabHeight - _kTabHeight)/2.0; - EdgeInsetsGeometry? adjustedPadding; + EdgeInsetsGeometry padding = widget.labelPadding ?? tabBarTheme.labelPadding ?? kTabLabelPadding; + const double verticalAdjustment = (_kTextAndIconTabHeight - _kTabHeight) / 2.0; - if (widget.tabs[index] is PreferredSizeWidget) { - final PreferredSizeWidget tab = widget.tabs[index] as PreferredSizeWidget; - if (widget.tabHasTextAndIcon && tab.preferredSize.height == _kTabHeight) { - if (widget.labelPadding != null || tabBarTheme.labelPadding != null) { - adjustedPadding = (widget.labelPadding ?? tabBarTheme.labelPadding!).add(const EdgeInsets.symmetric(vertical: verticalAdjustment)); - } - else { - adjustedPadding = const EdgeInsets.symmetric(vertical: verticalAdjustment, horizontal: 16.0); - } - } + final Widget tab = widget.tabs[index]; + if ( + tab is PreferredSizeWidget && tab.preferredSize.height == _kTabHeight + && widget.tabHasTextAndIcon + ) { + padding = padding.add(const EdgeInsets.symmetric(vertical: verticalAdjustment)); } - - _labelPaddings[index] = adjustedPadding ?? widget.labelPadding ?? tabBarTheme.labelPadding ?? kTabLabelPadding; + _labelPaddings[index] = padding; return Center( heightFactor: 1.0, diff --git a/packages/flutter/lib/src/painting/alignment.dart b/packages/flutter/lib/src/painting/alignment.dart index 81825536a5..76f0cc62e8 100644 --- a/packages/flutter/lib/src/painting/alignment.dart +++ b/packages/flutter/lib/src/painting/alignment.dart @@ -355,35 +355,18 @@ class Alignment extends AlignmentGeometry { Alignment resolve(TextDirection? direction) => this; static String _stringify(double x, double y) { - if (x == -1.0 && y == -1.0) { - return 'Alignment.topLeft'; - } - if (x == 0.0 && y == -1.0) { - return 'Alignment.topCenter'; - } - if (x == 1.0 && y == -1.0) { - return 'Alignment.topRight'; - } - if (x == -1.0 && y == 0.0) { - return 'Alignment.centerLeft'; - } - if (x == 0.0 && y == 0.0) { - return 'Alignment.center'; - } - if (x == 1.0 && y == 0.0) { - return 'Alignment.centerRight'; - } - if (x == -1.0 && y == 1.0) { - return 'Alignment.bottomLeft'; - } - if (x == 0.0 && y == 1.0) { - return 'Alignment.bottomCenter'; - } - if (x == 1.0 && y == 1.0) { - return 'Alignment.bottomRight'; - } - return 'Alignment(${x.toStringAsFixed(1)}, ' - '${y.toStringAsFixed(1)})'; + return switch ((x, y)) { + (-1.0, -1.0) => 'Alignment.topLeft', + ( 0.0, -1.0) => 'Alignment.topCenter', + ( 1.0, -1.0) => 'Alignment.topRight', + (-1.0, 0.0) => 'Alignment.centerLeft', + ( 0.0, 0.0) => 'Alignment.center', + ( 1.0, 0.0) => 'Alignment.centerRight', + (-1.0, 1.0) => 'Alignment.bottomLeft', + ( 0.0, 1.0) => 'Alignment.bottomCenter', + ( 1.0, 1.0) => 'Alignment.bottomRight', + _ => 'Alignment(${x.toStringAsFixed(1)}, ${y.toStringAsFixed(1)})', + }; } @override @@ -550,35 +533,18 @@ class AlignmentDirectional extends AlignmentGeometry { } static String _stringify(double start, double y) { - if (start == -1.0 && y == -1.0) { - return 'AlignmentDirectional.topStart'; - } - if (start == 0.0 && y == -1.0) { - return 'AlignmentDirectional.topCenter'; - } - if (start == 1.0 && y == -1.0) { - return 'AlignmentDirectional.topEnd'; - } - if (start == -1.0 && y == 0.0) { - return 'AlignmentDirectional.centerStart'; - } - if (start == 0.0 && y == 0.0) { - return 'AlignmentDirectional.center'; - } - if (start == 1.0 && y == 0.0) { - return 'AlignmentDirectional.centerEnd'; - } - if (start == -1.0 && y == 1.0) { - return 'AlignmentDirectional.bottomStart'; - } - if (start == 0.0 && y == 1.0) { - return 'AlignmentDirectional.bottomCenter'; - } - if (start == 1.0 && y == 1.0) { - return 'AlignmentDirectional.bottomEnd'; - } - return 'AlignmentDirectional(${start.toStringAsFixed(1)}, ' - '${y.toStringAsFixed(1)})'; + return switch ((start, y)) { + (-1.0, -1.0) => 'AlignmentDirectional.topStart', + ( 0.0, -1.0) => 'AlignmentDirectional.topCenter', + ( 1.0, -1.0) => 'AlignmentDirectional.topEnd', + (-1.0, 0.0) => 'AlignmentDirectional.centerStart', + ( 0.0, 0.0) => 'AlignmentDirectional.center', + ( 1.0, 0.0) => 'AlignmentDirectional.centerEnd', + (-1.0, 1.0) => 'AlignmentDirectional.bottomStart', + ( 0.0, 1.0) => 'AlignmentDirectional.bottomCenter', + ( 1.0, 1.0) => 'AlignmentDirectional.bottomEnd', + _ => 'AlignmentDirectional(${start.toStringAsFixed(1)}, ${y.toStringAsFixed(1)})', + }; } @override diff --git a/packages/flutter/lib/src/painting/box_decoration.dart b/packages/flutter/lib/src/painting/box_decoration.dart index f643acf40f..493ff0f892 100644 --- a/packages/flutter/lib/src/painting/box_decoration.dart +++ b/packages/flutter/lib/src/painting/box_decoration.dart @@ -248,26 +248,18 @@ class BoxDecoration extends Decoration { bool get isComplex => boxShadow != null; @override - BoxDecoration? lerpFrom(Decoration? a, double t) { - if (a == null) { - return scale(t); - } - if (a is BoxDecoration) { - return BoxDecoration.lerp(a, this, t); - } - return super.lerpFrom(a, t) as BoxDecoration?; - } + BoxDecoration? lerpFrom(Decoration? a, double t) => switch (a) { + null => scale(t), + BoxDecoration() => BoxDecoration.lerp(a, this, t), + _ => super.lerpFrom(a, t) as BoxDecoration? + }; @override - BoxDecoration? lerpTo(Decoration? b, double t) { - if (b == null) { - return scale(1.0 - t); - } - if (b is BoxDecoration) { - return BoxDecoration.lerp(this, b, t); - } - return super.lerpTo(b, t) as BoxDecoration?; - } + BoxDecoration? lerpTo(Decoration? b, double t) => switch (b) { + null => scale(1.0 - t), + BoxDecoration() => BoxDecoration.lerp(this, b, t), + _ => super.lerpTo(b, t) as BoxDecoration? + }; /// Linearly interpolate between two box decorations. /// diff --git a/packages/flutter/lib/src/painting/matrix_utils.dart b/packages/flutter/lib/src/painting/matrix_utils.dart index 738e780982..697fe82799 100644 --- a/packages/flutter/lib/src/painting/matrix_utils.dart +++ b/packages/flutter/lib/src/painting/matrix_utils.dart @@ -17,23 +17,15 @@ abstract final class MatrixUtils { /// /// Otherwise, returns null. static Offset? getAsTranslation(Matrix4 transform) { - final Float64List values = transform.storage; - // Values are stored in column-major order. - if (values[0] == 1.0 && // col 1 - values[1] == 0.0 && - values[2] == 0.0 && - values[3] == 0.0 && - values[4] == 0.0 && // col 2 - values[5] == 1.0 && - values[6] == 0.0 && - values[7] == 0.0 && - values[8] == 0.0 && // col 3 - values[9] == 0.0 && - values[10] == 1.0 && - values[11] == 0.0 && - values[14] == 0.0 && // bottom of col 4 (values 12 and 13 are the x and y offsets) - values[15] == 1.0) { - return Offset(values[12], values[13]); + // Values are stored in column-major order, so the appearance + // of dx is transposed from the top-right to the bottom-left. + if (transform.storage case [ + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + final double dx, final double dy, 0.0, 1.0, + ]) { + return Offset(dx, dy); } return null; } @@ -43,24 +35,15 @@ abstract final class MatrixUtils { /// /// Otherwise, returns null. static double? getAsScale(Matrix4 transform) { - final Float64List values = transform.storage; - // Values are stored in column-major order. - if (values[1] == 0.0 && // col 1 (value 0 is the scale) - values[2] == 0.0 && - values[3] == 0.0 && - values[4] == 0.0 && // col 2 (value 5 is the scale) - values[6] == 0.0 && - values[7] == 0.0 && - values[8] == 0.0 && // col 3 - values[9] == 0.0 && - values[10] == 1.0 && - values[11] == 0.0 && - values[12] == 0.0 && // col 4 - values[13] == 0.0 && - values[14] == 0.0 && - values[15] == 1.0 && - values[0] == values[5]) { // uniform scale - return values[0]; + // Values are stored in column-major order + // (but this symmetric matrix is unaffected). + if (transform.storage case [ + final double diagonal1, 0.0, 0.0, 0.0, + 0.0, final double diagonal2, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 1.0, + ] when diagonal1 == diagonal2) { + return diagonal1; } return null; } diff --git a/packages/flutter/lib/src/rendering/editable.dart b/packages/flutter/lib/src/rendering/editable.dart index 4d10ffd278..8061bc3835 100644 --- a/packages/flutter/lib/src/rendering/editable.dart +++ b/packages/flutter/lib/src/rendering/editable.dart @@ -1456,10 +1456,10 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, ..textDirection = initialDirection ..attributedLabel = AttributedString(info.semanticsLabel ?? info.text, attributes: info.stringAttributes); switch (info.recognizer) { - case TapGestureRecognizer(onTap: final VoidCallback? onTap): - case DoubleTapGestureRecognizer(onDoubleTap: final VoidCallback? onTap): - if (onTap != null) { - configuration.onTap = onTap; + case TapGestureRecognizer(onTap: final VoidCallback? handler): + case DoubleTapGestureRecognizer(onDoubleTap: final VoidCallback? handler): + if (handler != null) { + configuration.onTap = handler; configuration.isLink = true; } case LongPressGestureRecognizer(onLongPress: final GestureLongPressCallback? onLongPress): diff --git a/packages/flutter/lib/src/rendering/mouse_tracker.dart b/packages/flutter/lib/src/rendering/mouse_tracker.dart index a68a0f0e9a..3d6afabdb6 100644 --- a/packages/flutter/lib/src/rendering/mouse_tracker.dart +++ b/packages/flutter/lib/src/rendering/mouse_tracker.dart @@ -309,13 +309,10 @@ class MouseTracker extends ChangeNotifier { if (event is PointerSignalEvent) { return; } - final HitTestResult result; - if (event is PointerRemovedEvent) { - result = HitTestResult(); - } else { - final int viewId = event.viewId; - result = hitTestResult ?? _hitTestInView(event.position, viewId); - } + final HitTestResult result = switch (event) { + PointerRemovedEvent() => HitTestResult(), + _ => hitTestResult ?? _hitTestInView(event.position, event.viewId), + }; final int device = event.device; final _MouseState? existingState = _mouseStates[device]; if (!_shouldMarkStateDirty(existingState, event)) { diff --git a/packages/flutter/lib/src/rendering/paragraph.dart b/packages/flutter/lib/src/rendering/paragraph.dart index 41df07cb57..afe956d96d 100644 --- a/packages/flutter/lib/src/rendering/paragraph.dart +++ b/packages/flutter/lib/src/rendering/paragraph.dart @@ -1227,10 +1227,10 @@ class RenderParagraph extends RenderBox with ContainerRenderObjectMixin onPointerDown?.call(event), + PointerMoveEvent() => onPointerMove?.call(event), + PointerUpEvent() => onPointerUp?.call(event), + PointerHoverEvent() => onPointerHover?.call(event), + PointerCancelEvent() => onPointerCancel?.call(event), + PointerPanZoomStartEvent() => onPointerPanZoomStart?.call(event), + PointerPanZoomUpdateEvent() => onPointerPanZoomUpdate?.call(event), + PointerPanZoomEndEvent() => onPointerPanZoomEnd?.call(event), + PointerSignalEvent() => onPointerSignal?.call(event), + _ => null, + }; } @override diff --git a/packages/flutter/lib/src/services/message_codecs.dart b/packages/flutter/lib/src/services/message_codecs.dart index d16bb0906e..97f5dc1afe 100644 --- a/packages/flutter/lib/src/services/message_codecs.dart +++ b/packages/flutter/lib/src/services/message_codecs.dart @@ -147,10 +147,8 @@ class JSONMethodCodec implements MethodCodec { if (decoded is! Map) { throw FormatException('Expected method call Map, got $decoded'); } - final Object? method = decoded['method']; - final Object? arguments = decoded['args']; - if (method is String) { - return MethodCall(method, arguments); + if (decoded case {'method': final String method}) { + return MethodCall(method, decoded['args']); } throw FormatException('Invalid method call: $decoded'); } diff --git a/packages/flutter/lib/src/services/platform_views.dart b/packages/flutter/lib/src/services/platform_views.dart index 2cefb2f50c..addeef1a5b 100644 --- a/packages/flutter/lib/src/services/platform_views.dart +++ b/packages/flutter/lib/src/services/platform_views.dart @@ -624,20 +624,16 @@ class _AndroidMotionEventConverter { return null; } - final int action; - if (event is PointerDownEvent) { - action = numPointers == 1 - ? AndroidViewController.kActionDown - : AndroidViewController.pointerAction(pointerIdx, AndroidViewController.kActionPointerDown); - } else if (event is PointerUpEvent) { - action = numPointers == 1 - ? AndroidViewController.kActionUp - : AndroidViewController.pointerAction(pointerIdx, AndroidViewController.kActionPointerUp); - } else if (event is PointerMoveEvent) { - action = AndroidViewController.kActionMove; - } else if (event is PointerCancelEvent) { - action = AndroidViewController.kActionCancel; - } else { + final int? action = switch (event) { + PointerDownEvent() when numPointers == 1 => AndroidViewController.kActionDown, + PointerUpEvent() when numPointers == 1 => AndroidViewController.kActionUp, + PointerDownEvent() => AndroidViewController.pointerAction(pointerIdx, AndroidViewController.kActionPointerDown), + PointerUpEvent() => AndroidViewController.pointerAction(pointerIdx, AndroidViewController.kActionPointerUp), + PointerMoveEvent() => AndroidViewController.kActionMove, + PointerCancelEvent() => AndroidViewController.kActionCancel, + _ => null, + }; + if (action == null) { return null; } diff --git a/packages/flutter/lib/src/widgets/actions.dart b/packages/flutter/lib/src/widgets/actions.dart index 998ffebf81..dfdb409448 100644 --- a/packages/flutter/lib/src/widgets/actions.dart +++ b/packages/flutter/lib/src/widgets/actions.dart @@ -246,13 +246,10 @@ abstract class Action with Diagnosticable { /// [ContextAction] instead of [Action]. bool isEnabled(T intent) => isActionEnabled; - bool _isEnabled(T intent, BuildContext? context) { - final Action self = this; - if (self is ContextAction) { - return self.isEnabled(intent, context); - } - return self.isEnabled(intent); - } + bool _isEnabled(T intent, BuildContext? context) => switch (this) { + final ContextAction action => action.isEnabled(intent, context), + _ => isEnabled(intent), + }; /// Whether this [Action] is inherently enabled. /// @@ -338,13 +335,10 @@ abstract class Action with Diagnosticable { @protected Object? invoke(T intent); - Object? _invoke(T intent, BuildContext? context) { - final Action self = this; - if (self is ContextAction) { - return self.invoke(intent, context); - } - return self.invoke(intent); - } + Object? _invoke(T intent, BuildContext? context) => switch (this) { + final ContextAction action => action.invoke(intent, context), + _ => invoke(intent), + }; /// Register a callback to listen for changes to the state of this action. /// diff --git a/packages/flutter/lib/src/widgets/focus_scope.dart b/packages/flutter/lib/src/widgets/focus_scope.dart index 25fb1fc9ec..d0ee7d558a 100644 --- a/packages/flutter/lib/src/widgets/focus_scope.dart +++ b/packages/flutter/lib/src/widgets/focus_scope.dart @@ -444,20 +444,15 @@ class Focus extends StatefulWidget { /// * [of], which is similar to this function, but will throw an exception if /// it doesn't find a [Focus] node, instead of returning null. static FocusNode? maybeOf(BuildContext context, { bool scopeOk = false, bool createDependency = true }) { - final _FocusInheritedScope? scope; - if (createDependency) { - scope = context.dependOnInheritedWidgetOfExactType<_FocusInheritedScope>(); - } else { - scope = context.getInheritedWidgetOfExactType<_FocusInheritedScope>(); - } - final FocusNode? node = scope?.notifier; - if (node == null) { - return null; - } - if (!scopeOk && node is FocusScopeNode) { - return null; - } - return node; + final _FocusInheritedScope? scope = createDependency + ? context.dependOnInheritedWidgetOfExactType<_FocusInheritedScope>() + : context.getInheritedWidgetOfExactType<_FocusInheritedScope>(); + + return switch (scope?.notifier) { + null => null, + FocusScopeNode() when !scopeOk => null, + final FocusNode node => node, + }; } /// Returns true if the nearest enclosing [Focus] widget's node is focused. diff --git a/packages/flutter/lib/src/widgets/framework.dart b/packages/flutter/lib/src/widgets/framework.dart index 15394fd373..a1553a2ba1 100644 --- a/packages/flutter/lib/src/widgets/framework.dart +++ b/packages/flutter/lib/src/widgets/framework.dart @@ -187,17 +187,10 @@ abstract class GlobalKey> extends Key { /// The current state is null if (1) there is no widget in the tree that /// matches this global key, (2) that widget is not a [StatefulWidget], or the /// associated [State] object is not a subtype of `T`. - T? get currentState { - final Element? element = _currentElement; - if (element is StatefulElement) { - final StatefulElement statefulElement = element; - final State state = statefulElement.state; - if (state is T) { - return state; - } - } - return null; - } + T? get currentState => switch (_currentElement) { + StatefulElement(:final T state) => state, + _ => null, + }; } /// A global key with a debugging label. diff --git a/packages/flutter/lib/src/widgets/inherited_theme.dart b/packages/flutter/lib/src/widgets/inherited_theme.dart index f95efeafde..56500b1335 100644 --- a/packages/flutter/lib/src/widgets/inherited_theme.dart +++ b/packages/flutter/lib/src/widgets/inherited_theme.dart @@ -110,8 +110,7 @@ abstract class InheritedTheme extends InheritedWidget { }()); return false; } - if (ancestor is InheritedElement && ancestor.widget is InheritedTheme) { - final InheritedTheme theme = ancestor.widget as InheritedTheme; + if (ancestor case InheritedElement(widget: final InheritedTheme theme)) { final Type themeType = theme.runtimeType; // Only remember the first theme of any type. This assumes // that inherited themes completely shadow ancestors of the diff --git a/packages/flutter/lib/src/widgets/navigator.dart b/packages/flutter/lib/src/widgets/navigator.dart index cdbe527242..0bfcaf7709 100644 --- a/packages/flutter/lib/src/widgets/navigator.dart +++ b/packages/flutter/lib/src/widgets/navigator.dart @@ -2808,17 +2808,15 @@ class Navigator extends StatefulWidget { BuildContext context, { bool rootNavigator = false, }) { - // Handles the case where the input context is a navigator element. NavigatorState? navigator; - if (context is StatefulElement && context.state is NavigatorState) { - navigator = context.state as NavigatorState; - } - if (rootNavigator) { - navigator = context.findRootAncestorStateOfType() ?? navigator; - } else { - navigator = navigator ?? context.findAncestorStateOfType(); + if (context case StatefulElement(:final NavigatorState state)) { + navigator = state; } + navigator = rootNavigator + ? context.findRootAncestorStateOfType() ?? navigator + : navigator ?? context.findAncestorStateOfType(); + assert(() { if (navigator == null) { throw FlutterError( @@ -2858,17 +2856,14 @@ class Navigator extends StatefulWidget { BuildContext context, { bool rootNavigator = false, }) { - // Handles the case where the input context is a navigator element. NavigatorState? navigator; - if (context is StatefulElement && context.state is NavigatorState) { - navigator = context.state as NavigatorState; + if (context case StatefulElement(:final NavigatorState state)) { + navigator = state; } - if (rootNavigator) { - navigator = context.findRootAncestorStateOfType() ?? navigator; - } else { - navigator = navigator ?? context.findAncestorStateOfType(); - } - return navigator; + + return rootNavigator + ? context.findRootAncestorStateOfType() ?? navigator + : navigator ?? context.findAncestorStateOfType(); } /// Turn a route name into a set of [Route] objects. diff --git a/packages/flutter/lib/src/widgets/platform_menu_bar.dart b/packages/flutter/lib/src/widgets/platform_menu_bar.dart index 4f74381647..f2056102e9 100644 --- a/packages/flutter/lib/src/widgets/platform_menu_bar.dart +++ b/packages/flutter/lib/src/widgets/platform_menu_bar.dart @@ -606,7 +606,7 @@ class PlatformMenu extends PlatformMenuItem with DiagnosticableTreeMixin { previousItem = item; return false; }); - if (result.isNotEmpty && result.last[_kIsDividerKey] == true) { + if (result.lastOrNull case {_kIsDividerKey: true}) { result.removeLast(); } return { diff --git a/packages/flutter/test/services/message_codecs_testing.dart b/packages/flutter/test/services/message_codecs_testing.dart index 50bc8fa248..693d1e85f9 100644 --- a/packages/flutter/test/services/message_codecs_testing.dart +++ b/packages/flutter/test/services/message_codecs_testing.dart @@ -49,25 +49,20 @@ bool deepEquals(dynamic valueA, dynamic valueB) { bool deepEqualsTypedData(TypedData valueA, TypedData valueB) { if (valueA is ByteData) { - return valueB is ByteData - && deepEqualsList(valueA.buffer.asUint8List(), valueB.buffer.asUint8List()); + return valueB is ByteData && deepEqualsList( + valueA.buffer.asUint8List(), + valueB.buffer.asUint8List(), + ); } - if (valueA is Uint8List) { - return valueB is Uint8List && deepEqualsList(valueA, valueB); - } - if (valueA is Int32List) { - return valueB is Int32List && deepEqualsList(valueA, valueB); - } - if (valueA is Int64List) { - return valueB is Int64List && deepEqualsList(valueA, valueB); - } - if (valueA is Float32List) { - return valueB is Float32List && deepEqualsList(valueA, valueB); - } - if (valueA is Float64List) { - return valueB is Float64List && deepEqualsList(valueA, valueB); - } - throw 'Unexpected typed data: $valueA'; + + return switch (valueA) { + Uint8List() => valueB is Uint8List && deepEqualsList(valueA, valueB), + Int32List() => valueB is Int32List && deepEqualsList(valueA, valueB), + Int64List() => valueB is Int64List && deepEqualsList(valueA, valueB), + Float32List() => valueB is Float32List && deepEqualsList(valueA, valueB), + Float64List() => valueB is Float64List && deepEqualsList(valueA, valueB), + _ => throw 'Unexpected typed data: $valueA', + }; } bool deepEqualsList(List valueA, List valueB) { diff --git a/packages/flutter/test/widgets/widget_inspector_test.dart b/packages/flutter/test/widgets/widget_inspector_test.dart index 59c95fbf7f..e7c673158c 100644 --- a/packages/flutter/test/widgets/widget_inspector_test.dart +++ b/packages/flutter/test/widgets/widget_inspector_test.dart @@ -5258,33 +5258,23 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { ), ), ); - final Finder columnWidgetFinder = find.byType(Column); - expect(columnWidgetFinder, findsOneWidget); - final Element columnWidgetElement = columnWidgetFinder - .evaluate() - .first; - final DiagnosticsNode node = columnWidgetElement.toDiagnosticsNode(); - final InspectorSerializationDelegate delegate = - InspectorSerializationDelegate( - service: service, - includeProperties: true, - addAdditionalPropertiesCallback: - (DiagnosticsNode node, InspectorSerializationDelegate delegate) { - final Map additionalJson = {}; - final Object? value = node.value; - if (value is Element) { - final RenderObject? renderObject = value.renderObject; - if (renderObject != null) { - additionalJson['renderObject'] = - renderObject.toDiagnosticsNode().toJsonMap( - delegate.copyWith(subtreeDepth: 0), - ); - } - } - additionalJson['callbackExecuted'] = true; - return additionalJson; - }, - ); + + final Finder columnFinder = find.byType(Column); + expect(columnFinder, findsOneWidget); + + final DiagnosticsNode node = columnFinder.evaluate().first.toDiagnosticsNode(); + final InspectorSerializationDelegate delegate = InspectorSerializationDelegate( + service: service, + includeProperties: true, + addAdditionalPropertiesCallback: + (DiagnosticsNode node, InspectorSerializationDelegate delegate) => { + if (node.value case Element(:final RenderObject renderObject)) + 'renderObject': renderObject.toDiagnosticsNode().toJsonMap( + delegate.copyWith(subtreeDepth: 0), + ), + 'callbackExecuted': true, + }, + ); final Map json = node.toJsonMap(delegate); expect(json['callbackExecuted'], true); expect(json.containsKey('renderObject'), true); diff --git a/packages/flutter_driver/lib/src/common/handler_factory.dart b/packages/flutter_driver/lib/src/common/handler_factory.dart index 0446b3c6d8..68101ff1f3 100644 --- a/packages/flutter_driver/lib/src/common/handler_factory.dart +++ b/packages/flutter_driver/lib/src/common/handler_factory.dart @@ -94,15 +94,10 @@ mixin CreateFinderFactory { } Finder _createPageBackFinder() { - return find.byElementPredicate((Element element) { - final Widget widget = element.widget; - if (widget is Tooltip) { - return widget.message == 'Back'; - } - if (widget is CupertinoNavigationBarBackButton) { - return true; - } - return false; + return find.byElementPredicate((Element element) => switch (element.widget) { + Tooltip(message: 'Back') => true, + CupertinoNavigationBarBackButton() => true, + _ => false, }, description: 'Material or Cupertino back button'); } diff --git a/packages/flutter_driver/lib/src/driver/frame_request_pending_latency_summarizer.dart b/packages/flutter_driver/lib/src/driver/frame_request_pending_latency_summarizer.dart index 96a334d565..dec36b168a 100644 --- a/packages/flutter_driver/lib/src/driver/frame_request_pending_latency_summarizer.dart +++ b/packages/flutter_driver/lib/src/driver/frame_request_pending_latency_summarizer.dart @@ -45,18 +45,16 @@ class FrameRequestPendingLatencySummarizer { List _computeFrameRequestPendingLatencies() { final List result = []; final Map starts = {}; - for (int i = 0; i < frameRequestPendingEvents.length; i++) { - final TimelineEvent event = frameRequestPendingEvents[i]; - if (event.phase == 'b') { - final String? id = event.json['id'] as String?; - if (id != null) { + for (final TimelineEvent event in frameRequestPendingEvents) { + switch (event) { + case TimelineEvent(phase: 'b', json: {'id': final String id}): starts[id] = event.timestampMicros!; - } - } else if (event.phase == 'e') { - final int? start = starts[event.json['id']]; - if (start != null) { - result.add((event.timestampMicros! - start).toDouble()); - } + + case TimelineEvent(phase: 'e', json: {'id': final String id}): + final int? start = starts[id]; + if (start != null) { + result.add((event.timestampMicros! - start).toDouble()); + } } } return result; diff --git a/packages/flutter_goldens/lib/flutter_goldens.dart b/packages/flutter_goldens/lib/flutter_goldens.dart index 3c32f83139..7ed06ed306 100644 --- a/packages/flutter_goldens/lib/flutter_goldens.dart +++ b/packages/flutter_goldens/lib/flutter_goldens.dart @@ -210,27 +210,15 @@ abstract class FlutterGoldenFileComparator extends GoldenFileComparator { required FileSystem fs, }) { final Directory flutterRoot = fs.directory(platform.environment[_kFlutterRootKey]); - Directory comparisonRoot; + final Directory comparisonRoot = switch (suffix) { + null => flutterRoot.childDirectory(fs.path.join('bin', 'cache', 'pkg', 'skia_goldens')), + _ => fs.systemTempDirectory.createTempSync(suffix), + }; - if (suffix != null) { - comparisonRoot = fs.systemTempDirectory.createTempSync(suffix); - } else { - comparisonRoot = flutterRoot.childDirectory( - fs.path.join( - 'bin', - 'cache', - 'pkg', - 'skia_goldens', - ) - ); - } - - final Directory testDirectory = fs.directory(defaultComparator.basedir); - final String testDirectoryRelativePath = fs.path.relative( - testDirectory.path, - from: flutterRoot.path, + final String testPath = fs.directory(defaultComparator.basedir).path; + return comparisonRoot.childDirectory( + fs.path.relative(testPath, from: flutterRoot.path), ); - return comparisonRoot.childDirectory(testDirectoryRelativePath); } /// Returns the golden [File] identified by the given [Uri]. diff --git a/packages/flutter_test/lib/src/binding.dart b/packages/flutter_test/lib/src/binding.dart index d763c15329..fc8d7a6af4 100644 --- a/packages/flutter_test/lib/src/binding.dart +++ b/packages/flutter_test/lib/src/binding.dart @@ -849,20 +849,15 @@ abstract class TestWidgetsFlutterBinding extends BindingBase } Future _handleAnnouncementMessage(Object? mockMessage) async { - final Map message = mockMessage! as Map; - if (message['type'] == 'announce') { - final Map data = - message['data']! as Map; - final String dataMessage = data['message'].toString(); - final TextDirection textDirection = - TextDirection.values[data['textDirection']! as int]; - final int assertivenessLevel = (data['assertiveness'] as int?) ?? 0; - final Assertiveness assertiveness = - Assertiveness.values[assertivenessLevel]; - final CapturedAccessibilityAnnouncement announcement = - CapturedAccessibilityAnnouncement._( - dataMessage, textDirection, assertiveness); - _announcements.add(announcement); + if (mockMessage! case { + 'type': 'announce', + 'data': final Map data as Map, + }) { + _announcements.add(CapturedAccessibilityAnnouncement._( + data['message'].toString(), + TextDirection.values[data['textDirection']! as int], + Assertiveness.values[(data['assertiveness'] ?? 0) as int], + )); } } diff --git a/packages/flutter_tools/lib/src/build_system/targets/assets.dart b/packages/flutter_tools/lib/src/build_system/targets/assets.dart index 9a4496a451..ce10d1222d 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/assets.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/assets.dart @@ -302,14 +302,14 @@ DevFSContent? processSkSLBundle(String? bundlePath, { throw Exception('SkSL bundle was invalid'); } - final String? parsedPlatform = bundle['platform'] as String?; - TargetPlatform? bundleTargetPlatform; - if (parsedPlatform != null) { - bundleTargetPlatform = getTargetPlatformForName(parsedPlatform); - } - if (bundleTargetPlatform == null || bundleTargetPlatform != targetPlatform) { + final TargetPlatform? bundlePlatform = switch (bundle['platform'] as String?) { + final String platform => getTargetPlatformForName(platform), + null => null, + }; + + if (bundlePlatform != targetPlatform) { logger.printError( - 'The SkSL bundle was created for $bundleTargetPlatform, but the current ' + 'The SkSL bundle was created for $bundlePlatform, but the current ' 'platform is $targetPlatform. This may lead to less efficient shader ' 'caching.' ); diff --git a/packages/flutter_tools/lib/src/daemon.dart b/packages/flutter_tools/lib/src/daemon.dart index c53913af15..0415405502 100644 --- a/packages/flutter_tools/lib/src/daemon.dart +++ b/packages/flutter_tools/lib/src/daemon.dart @@ -349,10 +349,8 @@ class DaemonConnection { } else { _incomingCommands.add(message); } - } else if (data['event'] != null) { - // This is an event - _logger.printTrace('<- Event received: ${data['event']}'); - final Object? eventName = data['event']; + } else if (data case {'event': final Object eventName}) { + _logger.printTrace('<- Event received: $eventName'); if (eventName is String) { _events.add(DaemonEventData( eventName, diff --git a/packages/flutter_tools/lib/src/dart_pub_json_formatter.dart b/packages/flutter_tools/lib/src/dart_pub_json_formatter.dart index 156df4804d..07c6d5e62a 100644 --- a/packages/flutter_tools/lib/src/dart_pub_json_formatter.dart +++ b/packages/flutter_tools/lib/src/dart_pub_json_formatter.dart @@ -16,31 +16,23 @@ class DartDependencyPackage { }); factory DartDependencyPackage.fromHashMap(dynamic packageInfo) { - String name = ''; - String version = ''; - String source = ''; - List dependencies = []; - - if (packageInfo is LinkedHashMap) { - final LinkedHashMap info = packageInfo as LinkedHashMap; - if (info.containsKey('name')) { - name = info['name'] as String; - } - if (info.containsKey('version')) { - version = info['version'] as String; - } - if (info.containsKey('source')) { - source = info['source'] as String; - } - if (info.containsKey('dependencies')) { - dependencies = info['dependencies'] as List; - } + if (packageInfo is! LinkedHashMap) { + return DartDependencyPackage( + name: '', + version: '', + source: '', + dependencies: [], + ); } + return DartDependencyPackage( - name: name, - version: version, - source: source, - dependencies: dependencies.map((dynamic e) => e.toString()).toList(), + name: packageInfo['name'] as String? ?? '', + version: packageInfo['version'] as String? ?? '', + source: packageInfo['source'] as String? ?? '', + dependencies: switch (packageInfo['dependencies'] as List?) { + final List list => list.map((Object? e) => '$e').toList(), + null => [], + }, ); } diff --git a/packages/flutter_tools/lib/src/flutter_manifest.dart b/packages/flutter_tools/lib/src/flutter_manifest.dart index b59b3216ac..537714826f 100644 --- a/packages/flutter_tools/lib/src/flutter_manifest.dart +++ b/packages/flutter_tools/lib/src/flutter_manifest.dart @@ -166,11 +166,10 @@ class FlutterManifest { /// - assets/foo_license.txt /// ``` List get additionalLicenses { - final Object? licenses = _flutterDescriptor['licenses']; - if (licenses is YamlList) { - return licenses.map((Object? element) => element.toString()).toList(); - } - return []; + return [ + if (_flutterDescriptor case {'licenses': final YamlList list}) + for (final Object? item in list) '$item', + ]; } /// True if this manifest declares a Flutter module project. @@ -197,27 +196,19 @@ class FlutterManifest { /// such declaration. String? get androidPackage { if (isModule) { - final Object? module = _flutterDescriptor['module']; - if (module is YamlMap) { - return module['androidPackage'] as String?; + if (_flutterDescriptor case {'module': final YamlMap map}) { + return map['androidPackage'] as String?; } } - final Map? platforms = supportedPlatforms; - if (platforms == null) { + + late final YamlMap? plugin = _flutterDescriptor['plugin'] as YamlMap?; + + return switch (supportedPlatforms) { + {'android': final YamlMap map} => map['package'] as String?, // Pre-multi-platform plugin format - if (isPlugin) { - final YamlMap? plugin = _flutterDescriptor['plugin'] as YamlMap?; - return plugin?['androidPackage'] as String?; - } - return null; - } - if (platforms.containsKey('android')) { - final Object? android = platforms['android']; - if (android is YamlMap) { - return android['package'] as String?; - } - } - return null; + null when isPlugin => plugin?['androidPackage'] as String?, + _ => null, + }; } /// Returns the deferred components configuration if declared. Returns @@ -253,9 +244,8 @@ class FlutterManifest { /// module descriptor. Returns null if there is no such declaration. String? get iosBundleIdentifier { if (isModule) { - final Object? module = _flutterDescriptor['module']; - if (module is YamlMap) { - return module['iosBundleIdentifier'] as String?; + if (_flutterDescriptor case {'module': final YamlMap map}) { + return map['iosBundleIdentifier'] as String?; } } return null; diff --git a/packages/flutter_tools/lib/src/ios/core_devices.dart b/packages/flutter_tools/lib/src/ios/core_devices.dart index adc6eddfe6..e7eff51e28 100644 --- a/packages/flutter_tools/lib/src/ios/core_devices.dart +++ b/packages/flutter_tools/lib/src/ios/core_devices.dart @@ -396,15 +396,14 @@ class IOSCoreDevice { required Logger logger, }) { final List<_IOSCoreDeviceCapability> capabilitiesList = <_IOSCoreDeviceCapability>[ - if (data['capabilities'] case final List capabilitiesData) + if (data case {'capabilities': final List capabilitiesData}) for (final Object? capabilityData in capabilitiesData) if (capabilityData != null && capabilityData is Map) _IOSCoreDeviceCapability.fromBetaJson(capabilityData), ]; _IOSCoreDeviceConnectionProperties? connectionProperties; - if (data['connectionProperties'] is Map) { - final Map connectionPropertiesData = data['connectionProperties']! as Map; + if (data case {'connectionProperties': final Map connectionPropertiesData}) { connectionProperties = _IOSCoreDeviceConnectionProperties.fromBetaJson( connectionPropertiesData, logger: logger, @@ -412,14 +411,12 @@ class IOSCoreDevice { } IOSCoreDeviceProperties? deviceProperties; - if (data['deviceProperties'] is Map) { - final Map devicePropertiesData = data['deviceProperties']! as Map; + if (data case {'deviceProperties': final Map devicePropertiesData}) { deviceProperties = IOSCoreDeviceProperties.fromBetaJson(devicePropertiesData); } _IOSCoreDeviceHardwareProperties? hardwareProperties; - if (data['hardwareProperties'] is Map) { - final Map hardwarePropertiesData = data['hardwareProperties']! as Map; + if (data case {'hardwareProperties': final Map hardwarePropertiesData}) { hardwareProperties = _IOSCoreDeviceHardwareProperties.fromBetaJson( hardwarePropertiesData, logger: logger, @@ -535,8 +532,7 @@ class _IOSCoreDeviceConnectionProperties { required Logger logger, }) { List? localHostnames; - if (data['localHostnames'] is List) { - final List values = data['localHostnames']! as List; + if (data case {'localHostnames': final List values}) { try { localHostnames = List.from(values); } on TypeError { @@ -545,8 +541,7 @@ class _IOSCoreDeviceConnectionProperties { } List? potentialHostnames; - if (data['potentialHostnames'] is List) { - final List values = data['potentialHostnames']! as List; + if (data case {'potentialHostnames': final List values}) { try { potentialHostnames = List.from(values); } on TypeError { @@ -700,12 +695,12 @@ class _IOSCoreDeviceHardwareProperties { required Logger logger, }) { _IOSCoreDeviceCPUType? cpuType; - if (data['cpuType'] case final Map betaJson) { + if (data case {'cpuType': final Map betaJson}) { cpuType = _IOSCoreDeviceCPUType.fromBetaJson(betaJson); } List<_IOSCoreDeviceCPUType>? supportedCPUTypes; - if (data['supportedCPUTypes'] case final List values) { + if (data case {'supportedCPUTypes': final List values}) { supportedCPUTypes = <_IOSCoreDeviceCPUType>[ for (final Object? cpuTypeData in values) if (cpuTypeData is Map) @@ -714,8 +709,7 @@ class _IOSCoreDeviceHardwareProperties { } List? supportedDeviceFamilies; - if (data['supportedDeviceFamilies'] is List) { - final List values = data['supportedDeviceFamilies']! as List; + if (data case {'supportedDeviceFamilies': final List values}) { try { supportedDeviceFamilies = List.from(values); } on TypeError { diff --git a/packages/flutter_tools/lib/src/ios/xcode_debug.dart b/packages/flutter_tools/lib/src/ios/xcode_debug.dart index ac86858693..ce0eda1485 100644 --- a/packages/flutter_tools/lib/src/ios/xcode_debug.dart +++ b/packages/flutter_tools/lib/src/ios/xcode_debug.dart @@ -468,10 +468,8 @@ class XcodeAutomationScriptResponse { factory XcodeAutomationScriptResponse.fromJson(Map data) { XcodeAutomationScriptDebugResult? debugResult; - if (data['debugResult'] != null && data['debugResult'] is Map) { - debugResult = XcodeAutomationScriptDebugResult.fromJson( - data['debugResult']! as Map, - ); + if (data case {'debugResult': final Map resultData}) { + debugResult = XcodeAutomationScriptDebugResult.fromJson(resultData); } return XcodeAutomationScriptResponse._( status: data['status'] is bool? ? data['status'] as bool? : null, diff --git a/packages/flutter_tools/lib/src/migrations/swift_package_manager_integration_migration.dart b/packages/flutter_tools/lib/src/migrations/swift_package_manager_integration_migration.dart index 58169d592b..e3e74239a1 100644 --- a/packages/flutter_tools/lib/src/migrations/swift_package_manager_integration_migration.dart +++ b/packages/flutter_tools/lib/src/migrations/swift_package_manager_integration_migration.dart @@ -862,33 +862,26 @@ class ParsedProjectInfo { final List parsedSwiftPackageProductDependencies = []; final List parsedLocalSwiftPackageProductDependencies = []; - if (data['objects'] is Map) { - final Map values = - data['objects']! as Map; + if (data case {'objects': final Map values}) { for (final String key in values.keys) { - if (values[key] is Map) { - final Map details = - values[key]! as Map; - if (details['isa'] is String) { - final String objectType = details['isa']! as String; - if (objectType == 'PBXBuildFile') { + if (values[key] case final Map details) { + switch (details['isa']) { + case 'PBXBuildFile': buildFiles.add(key); - } else if (objectType == 'PBXFileReference') { + case 'PBXFileReference': references.add(key); - } else if (objectType == 'PBXGroup') { + case 'PBXGroup': groups.add(ParsedProjectGroup.fromJson(key, details)); - } else if (objectType == 'PBXFrameworksBuildPhase') { - buildPhases.add( - ParsedProjectFrameworksBuildPhase.fromJson(key, details)); - } else if (objectType == 'PBXNativeTarget') { + case 'PBXFrameworksBuildPhase': + buildPhases.add(ParsedProjectFrameworksBuildPhase.fromJson(key, details)); + case 'PBXNativeTarget': native.add(ParsedNativeTarget.fromJson(key, details)); - } else if (objectType == 'PBXProject') { + case 'PBXProject': project.add(ParsedProject.fromJson(key, details)); - } else if (objectType == 'XCSwiftPackageProductDependency') { + case 'XCSwiftPackageProductDependency': parsedSwiftPackageProductDependencies.add(key); - } else if (objectType == 'XCLocalSwiftPackageReference') { + case 'XCLocalSwiftPackageReference': parsedLocalSwiftPackageProductDependencies.add(key); - } } } } @@ -935,27 +928,16 @@ class ParsedProjectInfo { /// Representation of data parsed from PBXGroup section in Xcode project's project.pbxproj. class ParsedProjectGroup { - ParsedProjectGroup._(this.identifier, this.children, this.name); - - factory ParsedProjectGroup.fromJson(String key, Map data) { - String? name; - if (data['name'] is String) { - name = data['name']! as String; - } else if (data['path'] is String) { - name = data['path']! as String; - } - - final List parsedChildren = []; - if (data['children'] is List) { - for (final Object? item in data['children']! as List) { - if (item is String) { - parsedChildren.add(item); - } - } - return ParsedProjectGroup._(key, parsedChildren, name); - } - return ParsedProjectGroup._(key, null, name); - } + ParsedProjectGroup.fromJson(this.identifier, Map data) + : children = switch (data['children']) { + final List children => children.whereType().toList(), + _ => null, + }, + name = switch (data) { + {'name': final String name} => name, + {'path': final String path} => path, + _ => null, + }; final String identifier; final List? children; @@ -965,21 +947,13 @@ class ParsedProjectGroup { /// Representation of data parsed from PBXFrameworksBuildPhase section in Xcode /// project's project.pbxproj. class ParsedProjectFrameworksBuildPhase { - ParsedProjectFrameworksBuildPhase._(this.identifier, this.files); - - factory ParsedProjectFrameworksBuildPhase.fromJson( - String key, Map data) { - final List parsedFiles = []; - if (data['files'] is List) { - for (final Object? item in data['files']! as List) { - if (item is String) { - parsedFiles.add(item); - } - } - return ParsedProjectFrameworksBuildPhase._(key, parsedFiles); - } - return ParsedProjectFrameworksBuildPhase._(key, null); - } + ParsedProjectFrameworksBuildPhase.fromJson( + this.identifier, + Map data, + ) : files = switch (data['files']) { + final List files => files.whereType().toList(), + _ => null, + }; final String identifier; final List? files; @@ -988,31 +962,15 @@ class ParsedProjectFrameworksBuildPhase { /// Representation of data parsed from PBXNativeTarget section in Xcode project's /// project.pbxproj. class ParsedNativeTarget { - ParsedNativeTarget._( - this.data, - this.identifier, - this.name, - this.packageProductDependencies, - ); - - factory ParsedNativeTarget.fromJson(String key, Map data) { - String? name; - if (data['name'] is String) { - name = data['name']! as String; - } - - final List parsedChildren = []; - if (data['packageProductDependencies'] is List) { - for (final Object? item - in data['packageProductDependencies']! as List) { - if (item is String) { - parsedChildren.add(item); - } - } - return ParsedNativeTarget._(data, key, name, parsedChildren); - } - return ParsedNativeTarget._(data, key, name, null); - } + ParsedNativeTarget.fromJson(this.identifier, this.data) + : name = switch (data) { + {'name': final String name} => name, + _ => null, + }, + packageProductDependencies = switch (data['packageProductDependencies']) { + final List dependencies => dependencies.whereType().toList(), + _ => null, + }; final Map data; final String identifier; @@ -1023,24 +981,11 @@ class ParsedNativeTarget { /// Representation of data parsed from PBXProject section in Xcode project's /// project.pbxproj. class ParsedProject { - ParsedProject._( - this.data, - this.identifier, - this.packageReferences, - ); - - factory ParsedProject.fromJson(String key, Map data) { - final List parsedChildren = []; - if (data['packageReferences'] is List) { - for (final Object? item in data['packageReferences']! as List) { - if (item is String) { - parsedChildren.add(item); - } - } - return ParsedProject._(data, key, parsedChildren); - } - return ParsedProject._(data, key, null); - } + ParsedProject.fromJson(this.identifier, this.data) + : packageReferences = switch (data['packageReferences']) { + final List references => references.whereType().toList(), + _ => null, + }; final Map data; final String identifier; diff --git a/packages/flutter_tools/lib/src/vmservice.dart b/packages/flutter_tools/lib/src/vmservice.dart index db3cf0eee1..98ae0bcdf8 100644 --- a/packages/flutter_tools/lib/src/vmservice.dart +++ b/packages/flutter_tools/lib/src/vmservice.dart @@ -851,8 +851,8 @@ class FlutterVmService { ? {'value': platform} : {}, ); - if (result != null && result['value'] is String) { - return result['value']! as String; + if (result case {'value': final String value}) { + return value; } return 'unknown'; } diff --git a/packages/flutter_tools/test/integration.shard/test_driver.dart b/packages/flutter_tools/test/integration.shard/test_driver.dart index b493c14756..017a4f99c3 100644 --- a/packages/flutter_tools/test/integration.shard/test_driver.dart +++ b/packages/flutter_tools/test/integration.shard/test_driver.dart @@ -423,14 +423,11 @@ abstract class FlutterTestDriver { final StringBuffer error = StringBuffer(); error.write('Received app.stop event while waiting for $interestingOccurrence\n\n$_errorBuffer'); final Object? jsonParams = json['params']; - if (jsonParams is Map) { - if (jsonParams['error'] != null) { - error.write('${jsonParams['error']}\n\n'); - } - final Object? trace = jsonParams['trace']; - if (trace != null) { - error.write('$trace\n\n'); - } + if (jsonParams case {'error': final Object errorObject}) { + error.write('$errorObject\n\n'); + } + if (jsonParams case {'trace': final Object trace}) { + error.write('$trace\n\n'); } response.completeError(Exception(error.toString())); }