dart-lang / sdk

The Dart SDK, including the VM, JS and Wasm compilers, analysis, core libraries, and more.
https://dart.dev
BSD 3-Clause "New" or "Revised" License
10.28k stars 1.58k forks source link

Unhelpful Error when using Extensions in Switch Statements #58963

Open PvtPuddles opened 2 days ago

PvtPuddles commented 2 days ago

Hello, I've run into an issue on MacOS using the command line. I recently made some changes to my code around the same time as I upgraded flutter, so I spent several hours trying to debug this issue as a flutter issue before realizing that my change was actually the culprit and that this issue has nothing to do with Flutter.

I've managed to trace my problem; I refactored a switch expression into a switch statement to streamline some logic. Unfortunately, one of the conditions wasn't constant, but neither I nor the compiler managed to catch this error. See code below.

I have an extension that provides a >= operator, which I was using in the switch expression. This was working before. However, if I try to use that same extension in the switch statement, neither the analyzer nor compiler issue a warning or error. Instead, when I try to run the test, I get a pretty big, unhelpful error.

Instead, I would expect:

  1. An error is surfaced by the analyzer on my non-constant switch case
  2. In the error case, a more helpful error message to help me find the problem
  3. That both the switch statement and the switch expression would function (or not function in) the same way. If one throws, I would expect the other to as well.

Test Code

import 'package:test/test.dart';

void main() {
  test("Switch Expression", () {
    final result = switch (MyEnum.first) {
      >= MyEnum.second => "More than second",
      MyEnum.first => "One value",
      _ => "Some values",
    };
    expect(result, "One value");
  });

  // Causes an obtuse `InvalidArgument(s): Type parameter StructuralParameter(T) is not indexed`
  // error.  Comment out this test and the file will compile again.
  test("Switch Statement", () {
    String result;
    switch (MyEnum.first) {
      case >= MyEnum.second:
        result = "More than second";
      case MyEnum.first:
        result = "One value";
      default:
        result = "Some values";
    }
    expect(result, "One value");
  });
}

enum MyEnum implements Comparable<MyEnum> {
  first(1),
  second(2),
  third(3);

  const MyEnum(this.value);

  final int value;

  @override
  int compareTo(MyEnum other) => value.compareTo(other.value);

  // FIXME: Operator >= not implemented
  // operator >=(MyEnum other) => value.compareTo(other.value) >= 0;
}

extension GreaterThanOrEqual<T> on Comparable<T> {
  operator >=(T other) => compareTo(other) >= 0;
}

Compile-time Error

dart test test/test.dart
Building package executable... 
Built test:test.
00:00 +0: loading test/test.dart                                                                                                                                                                                                        Unhandled exception:
Invalid argument(s): Type parameter StructuralParameter(T) is not indexed
#0      TypeParameterIndexer.[] (package:kernel/binary/ast_to_binary.dart:3420)
#1      BinaryPrinter.visitStructuralParameterType (package:kernel/binary/ast_to_binary.dart:2576)
#2      StructuralParameterType.accept (package:kernel/ast.dart:12765)
#3      BinaryPrinter.writeNode (package:kernel/binary/ast_to_binary.dart:441)
#4      BinaryPrinter.visitAsExpression (package:kernel/binary/ast_to_binary.dart:1987)
#5      AsExpression.accept (package:kernel/ast.dart:7607)
#6      BinaryPrinter.writeNode (package:kernel/binary/ast_to_binary.dart:441)
#7      BinaryPrinter.writeNodeList (package:kernel/binary/ast_to_binary.dart:351)
#8      BinaryPrinter.visitArguments (package:kernel/binary/ast_to_binary.dart:1866)
#9      Arguments.accept (package:kernel/ast.dart:5394)
#10     BinaryPrinter.writeArgumentsNode (package:kernel/binary/ast_to_binary.dart:455)
#11     BinaryPrinter.visitStaticInvocation (package:kernel/binary/ast_to_binary.dart:1849)
#12     StaticInvocation.accept (package:kernel/ast.dart:6582)
#13     BinaryPrinter.writeNode (package:kernel/binary/ast_to_binary.dart:441)
#14     BinaryPrinter.visitAsExpression (package:kernel/binary/ast_to_binary.dart:1986)
#15     AsExpression.accept (package:kernel/ast.dart:7607)
#16     BinaryPrinter.writeNode (package:kernel/binary/ast_to_binary.dart:441)
#17     BinaryPrinter.visitIfStatement (package:kernel/binary/ast_to_binary.dart:2351)
#18     IfStatement.accept (package:kernel/ast.dart:10077)
#19     BinaryPrinter.writeNode (package:kernel/binary/ast_to_binary.dart:441)
#20     BinaryPrinter.writeNodeList (package:kernel/binary/ast_to_binary.dart:351)
#21     BinaryPrinter.visitBlock (package:kernel/binary/ast_to_binary.dart:2207)
#22     Block.accept (package:kernel/ast.dart:9181)
#23     BinaryPrinter.writeNode (package:kernel/binary/ast_to_binary.dart:441)
#24     BinaryPrinter.writeNodeList (package:kernel/binary/ast_to_binary.dart:351)
#25     BinaryPrinter.visitBlock (package:kernel/binary/ast_to_binary.dart:2207)
#26     Block.accept (package:kernel/ast.dart:9181)
#27     BinaryPrinter.writeNode (package:kernel/binary/ast_to_binary.dart:441)
#28     BinaryPrinter.visitLabeledStatement (package:kernel/binary/ast_to_binary.dart:2241)
#29     LabeledStatement.accept (package:kernel/ast.dart:9384)
#30     BinaryPrinter.writeNode (package:kernel/binary/ast_to_binary.dart:441)
#31     BinaryPrinter.writeNodeList (package:kernel/binary/ast_to_binary.dart:351)
#32     BinaryPrinter.visitBlockExpression (package:kernel/binary/ast_to_binary.dart:2158)
#33     BlockExpression.accept (package:kernel/ast.dart:8765)
#34     BinaryPrinter.writeNode (package:kernel/binary/ast_to_binary.dart:441)
#35     BinaryPrinter.writeOptionalNode (package:kernel/binary/ast_to_binary.dart:533)
#36     BinaryPrinter.writeVariableDeclaration (package:kernel/binary/ast_to_binary.dart:2424)
#37     BinaryPrinter.visitVariableDeclaration (package:kernel/binary/ast_to_binary.dart:2410)
#38     VariableDeclaration.accept (package:kernel/ast.dart:10739)
#39     BinaryPrinter.writeNode (package:kernel/binary/ast_to_binary.dart:441)
#40     BinaryPrinter.writeNodeList (package:kernel/binary/ast_to_binary.dart:351)
#41     BinaryPrinter.visitBlock (package:kernel/binary/ast_to_binary.dart:2207)
#42     Block.accept (package:kernel/ast.dart:9181)
#43     BinaryPrinter.writeNode (package:kernel/binary/ast_to_binary.dart:441)
#44     BinaryPrinter.writeOptionalNode (package:kernel/binary/ast_to_binary.dart:533)
#45     BinaryPrinter.visitFunctionNode (package:kernel/binary/ast_to_binary.dart:1552)
#46     FunctionNode.accept (package:kernel/ast.dart:3872)
#47     BinaryPrinter.writeFunctionNode (package:kernel/binary/ast_to_binary.dart:448)
#48     BinaryPrinter.visitFunctionExpression (package:kernel/binary/ast_to_binary.dart:2136)
#49     FunctionExpression.accept (package:kernel/ast.dart:8588)
#50     BinaryPrinter.writeNode (package:kernel/binary/ast_to_binary.dart:441)
#51     BinaryPrinter.writeNodeList (package:kernel/binary/ast_to_binary.dart:351)
#52     BinaryPrinter.visitArguments (package:kernel/binary/ast_to_binary.dart:1866)
#53     Arguments.accept (package:kernel/ast.dart:5394)
#54     BinaryPrinter.writeArgumentsNode (package:kernel/binary/ast_to_binary.dart:455)
#55     BinaryPrinter.visitStaticInvocation (package:kernel/binary/ast_to_binary.dart:1849)
#56     StaticInvocation.accept (package:kernel/ast.dart:6582)
#57     BinaryPrinter.writeNode (package:kernel/binary/ast_to_binary.dart:441)
#58     BinaryPrinter.visitExpressionStatement (package:kernel/binary/ast_to_binary.dart:2196)
#59     ExpressionStatement.accept (package:kernel/ast.dart:9128)
#60     BinaryPrinter.writeNode (package:kernel/binary/ast_to_binary.dart:441)
#61     BinaryPrinter.writeNodeList (package:kernel/binary/ast_to_binary.dart:351)
#62     BinaryPrinter.visitBlock (package:kernel/binary/ast_to_binary.dart:2207)
#63     Block.accept (package:kernel/ast.dart:9181)
#64     BinaryPrinter.writeNode (package:kernel/binary/ast_to_binary.dart:441)
#65     BinaryPrinter.writeOptionalNode (package:kernel/binary/ast_to_binary.dart:533)
#66     BinaryPrinter.visitFunctionNode (package:kernel/binary/ast_to_binary.dart:1552)
#67     FunctionNode.accept (package:kernel/ast.dart:3872)
#68     BinaryPrinter.writeFunctionNode (package:kernel/binary/ast_to_binary.dart:448)
#69     BinaryPrinter.visitProcedure (package:kernel/binary/ast_to_binary.dart:1379)
#70     Procedure.accept (package:kernel/ast.dart:3228)
#71     BinaryPrinter.writeProcedureNode (package:kernel/binary/ast_to_binary.dart:469)
#72     BinaryPrinter.writeProcedureNodeList (package:kernel/binary/ast_to_binary.dart:360)
#73     BinaryPrinter.visitLibrary (package:kernel/binary/ast_to_binary.dart:1124)
#74     Library.accept (package:kernel/ast.dart:585)
#75     BinaryPrinter.writeLibraryNode (package:kernel/binary/ast_to_binary.dart:462)
#76     BinaryPrinter.writeLibraries (package:kernel/binary/ast_to_binary.dart:797)
#77     BinaryPrinter.writeComponentFile.<anonymous closure> (package:kernel/binary/ast_to_binary.dart:607)
#78     Timeline.timeSync (dart:developer/timeline.dart:173)
#79     BinaryPrinter.writeComponentFile (package:kernel/binary/ast_to_binary.dart:588)
#80     FrontendCompiler.writeDillFile (package:frontend_server/frontend_server.dart:916)
#81     FrontendCompiler.compile (package:frontend_server/frontend_server.dart:689)
<asynchronous suspension>
#82     listenAndCompile.<anonymous closure> (package:frontend_server/frontend_server.dart:1385)
<asynchronous suspension>
00:00 +0 -1: loading test/test.dart [E]                                                                                                                                                                                                 
  Failed to load "test/test.dart": 
  SocketException: Write failed (OS Error: Broken pipe, errno = 32), port = 0
  dart:io-patch/socket_patch.dart 1252:34                                _NativeSocket.write
  dart:io-patch/socket_patch.dart 2010:15                                _RawSocket.write
  dart:io-patch/socket_patch.dart 2487:18                                _Socket._write
  dart:io-patch/socket_patch.dart 2222:28                                _SocketStreamConsumer.write
  dart:io-patch/socket_patch.dart 2174:11                                _SocketStreamConsumer.addStream.<fn>
  dart:async/zone.dart 1407:47                                           _rootRunUnary
  dart:async/zone.dart 1308:19                                           _CustomZone.runUnary
  dart:async/zone.dart 1217:7                                            _CustomZone.runUnaryGuarded
  dart:async/stream_impl.dart 365:11                                     _BufferingStreamSubscription._sendData
  dart:async/stream_impl.dart 297:7                                      _BufferingStreamSubscription._add
  dart:async/stream_controller.dart 784:19                               _SyncStreamControllerDispatch._sendData
  dart:async/stream_controller.dart 658:7                                _StreamController._add
  dart:async/stream_controller.dart 606:5                                _StreamController.add
  dart:io/io_sink.dart 154:17                                            _StreamSinkImpl.add
  dart:io/io_sink.dart 287:5                                             _IOSinkImpl.write
  dart:io-patch/socket_patch.dart 2320:36                                _Socket.write
  dart:io/stdio.dart 401:13                                              _StdSink._write
  dart:io/stdio.dart 419:5                                               _StdSink.writeln
  package:frontend_server_client/src/frontend_server_client.dart 366:21  FrontendServerClient._sendCommand
  package:frontend_server_client/src/frontend_server_client.dart 283:5   FrontendServerClient.accept
  package:test_core/src/runner/vm/test_compiler.dart 128:30              _TestCompilerForLanguageVersion._compile
  ===== asynchronous gap ===========================
  package:pool/pool.dart 127:14                                          Pool.withResource
  ===== asynchronous gap ===========================
  package:test_core/src/runner/vm/platform.dart 239:9                    VMPlatform._compileToKernel
  ===== asynchronous gap ===========================
  package:test_core/src/runner/vm/platform.dart 220:13                   VMPlatform._spawnIsolate
  ===== asynchronous gap ===========================
  package:test_core/src/runner/vm/platform.dart 75:19                    VMPlatform.load
  ===== asynchronous gap ===========================
  package:test_core/src/runner/loader.dart 219:27                        Loader.loadFile.<fn>
  ===== asynchronous gap ===========================
  package:test_core/src/runner/load_suite.dart 97:19                     new LoadSuite.<fn>.<fn>

Dart Info

(Sorry, built the project with Flutter)

#### General info

- Dart 3.5.4 (stable) (Wed Oct 16 16:18:51 2024 +0000) on "macos_arm64"
- on macos / Version 15.1 (Build 24B83)
- locale is en-US

#### Project info

- sdk constraint: '^3.5.4'
- dependencies: flutter, test
- dev_dependencies: flutter_lints, flutter_test

#### Process info

|  Memory |  CPU | Elapsed time | Command line                                                                               |
| ------: | ---: | -----------: | ------------------------------------------------------------------------------------------ |
|   12 MB | 0.0% |     01:59:25 | dart devtools --machine --dtd-uri=ws:<path>/I6TfSYmG1wvHrPy5                               |
|    8 MB | 0.0% |  11-04:34:54 | dart devtools --machine --dtd-uri=ws:<path>/SCrKN6CEHrmYXX48                               |
|   15 MB | 0.0% |     23:30:54 | dart devtools --no-launch-browser                                                          |
| 3068 MB | 0.0% |     01:59:25 | dart language-server --client-id=IntelliJ-IDEA --client-version=IU-243.21565.193 --protocol=analyzer |
|   12 MB | 0.0% |     01:59:25 | dart tooling-daemon --machine                                                              |
|    9 MB | 0.0% |  11-04:34:54 | dart tooling-daemon --machine                                                              |
|   56 MB | 0.6% |     01:40:34 | flutter_tools.snapshot daemon                                                              |
dart-github-bot commented 2 days ago

Summary: Using extensions in Dart switch statements with non-constant conditions causes unhelpful compiler errors. The analyzer should flag the issue and provide a more informative error message.