Pyrefly Fails to Report Non-Exhaustive match Statements and Incorrect Enum Case Patterns
Describe the Bug
Observed Behavior (with Pyrefly):
When analyzing Python match statements, Pyrefly currently reports no errors under the following conditions where issues are present:
-
Non-exhaustive matching: When
casestatements within amatchblock do not collectively handle all possible members of anEnumor all types within aUnionthat the matched variable could be. -
Incorrect syntax for Enum member case patterns: When
Enummembers used incasepatterns are incorrectly written with parentheses as if they were being called (e.g.,MyEnum.MEMBER()instead of the correct syntaxMyEnum.MEMBER).
This behavior implies Pyrefly currently considers these potentially problematic patterns as valid.
Expected Behavior:
Pyrefly should identify and report errors in the scenarios described above to help developers catch potential bugs and ensure code correctness:
-
For non-exhaustive matching: Pyrefly should issue an error or warning, ideally specifying which
Enummembers orUniontypes are not handled by anycasestatement. -
For incorrect Enum syntax: Pyrefly should report a syntax or type error when
Enummembers are incorrectly used with parentheses incasepatterns (e.g., similar to Pyright's"Literal[...] is not a class"report).
This would align Pyrefly's strictness with other type checkers and provide more robust static analysis for match statements.
Pyright's Output (for reference, from the original user report):
/Users/davi/gits/chief-app/test_types.py
/Users/davi/gits/chief-app/test_types.py:21:11 - error: Cases within match statement do not exhaustively handle all values
Unhandled type: "OrderStatus"
If exhaustive handling is not intended, add "case _: pass" (reportMatchNotExhaustive)
/Users/davi/gits/chief-app/test_types.py:22:14 - error: "Literal[OrderStatus.Ready]" is not a class (reportGeneralTypeIssues)
/Users/davi/gits/chief-app/test_types.py:24:14 - error: "Literal[OrderStatus.Shipped]" is not a class (reportGeneralTypeIssues)
/Users/davi/gits/chief-app/test_types.py:30:11 - error: Cases within match statement do not exhaustively handle all values
Unhandled type: "Literal[OrderStatus.Scheduled]"
If exhaustive handling is not intended, add "case _: pass" (reportMatchNotExhaustive)
/Users/davi/gits/chief-app/test_types.py:52:11 - error: Cases within match statement do not exhaustively handle all values
Unhandled type: "int"
If exhaustive handling is not intended, add "case _: pass" (reportMatchNotExhaustive)
5 errors, 0 warnings, 0 information
Steps to Reproduce:
-
Prepare the code: Save the Python code snippet provided in the "Sandbox Link" section of this GitHub issue (which contains functions
handle_order_1,handle_order_2, andhandle) into a local file (e.g.,test_match_issues.py). This code demonstrates the non-exhaustive matches and incorrect Enum syntax. -
Analyze with Pyrefly: Run
pyreflyon the created file:pyrefly check test_match_issues.py -
Observe Pyrefly's output: Notice that Pyrefly does not report any errors for the problematic
matchstatements within thehandle_order_1,handle_order_2, orhandlefunctions. -
(Optional Comparison) Analyze with Pyright: Run
pyrighton the same file:pyright test_match_issues.py - Observe Pyright's output: Notice that Pyright correctly identifies and reports multiple errors related to non-exhaustiveness and incorrect enum case patterns for the same functions, as detailed in the "Pyright's Output" section of this issue. This highlights the behavior expected from Pyrefly.
Sandbox Link
https://pyrefly.org/sandbox/?code=GYJw9gtgBApgdgV2gSwgBzCALlAooiAKEIGIoB5EZAc2TgEMAbKAZTARAGMYAuKACyxY0AZx4B6cf3oBrZACN4yLPQB0nSOLQBPLPzBwAtBG07DMAB7SEIrMgBuMQ534xOcuNVWkoAFX7IIlAaACYwsBZojJgwQZbWtg7wsUEubh7UUHRQEPRYLlC2eTAQ8Fgi3mQAYphQAAq6+nBQAMyqAIztANQANFBYpnRe9CIiMNgA+nAwjiBZQfT29MiM9PKMvAJCohLiA2hDqiAw9CF6MCFgnBXIYOLw4qtYsVjiIuxcMOIIcMf0LmsNqpBBBGD5-OFLPR0BsgopogB3KD0Y5QGwXfpgfovCIJOyOaajYKudxDHwI5T8eqNAytDoABgA5EFcvkqUVnqU4FhVNSqNRBMyoIppPZbnMABQaEDHThYRjaLJhbnIYDaIawGWYEQASnmUDgYGeIUqfgCQWOGGwAho-EYtvK-WkOB0x2ACqgEoRAQKlswxo9dBCyE4xSCACIwDJw1BaobNeAQEFgLVzmNgiNYjqfCJ9AhGCElSdGB6mO8iyq1U7YuEMM8VUx5iIELEoCm5uB5DYcPtwmlSZ5vIROKsiZQwiAWCosDYJfgkDqeIQoCuoAAlE4hRUAXig4b+W-Dy9XLDSIXzGN34ZEZ4vISPq9YATQaEve9zyBfFyPPgabo9ubsAWRZMB6frYEuZDtLyAByBjmFY9DdkkOR5AUErjuMU55DYqinq454bIWECBCIQw6qaABMvIAJJwNKsounkzwgM0AAGmGTtOuEbqc2gSjqbGeoB+aFooUAcSAE7YTOFS8VubEUT4bRQHRDFuExQjjOxnEybhLDPq+IQCUJEoicB4mSdJ3EVAZn5GYp3hhMAAj0HAIQbBMmAThM7RmTZfC6TZeqGAAfFAcHTEuj6suhHI2IuUBkH+DpQPa0xQJRAAcWTNJgNB0I2UIwjAx6PiuobpkFOFyZu-GJcl2j8oIaV0OElEAJxleVj5oFQ3IStez6xlJ4zhtmPUVZmFCjVxNV4YZFwCXwjXNTg6XhC0lHdZNUB9XQWCDQCnjhN5Y3Zr+TUwO6irmYWyBhKBirgVgkFQNBEXwfESGJI4qFsp61WyXht5ETkpHkU512ue5nlnSAEyUf5NWBbNem6lAYWfVF3WxVSyOyQ1fKpRtUAACz0rlI0FQwzDFVEpW7ZV4RAzxdXRbtq77QNQ2fiNE7jTtq7MzN1nzXZX4hBznN7f1h3hsd1CnbNguPiAyzpgAakwLa4FqIASsA4bo1AADe8UiAAvvqho4Es9r3hdZD+IE+rAG1fSutdAF5sBL0Gli4yJhUhDOTDHkwF5s0TC0BM2KjYuE9L-1xcFyePiLrO1Xx6eTdz8sfmg-PnULU1VWjNkgwRd65z1+eDYXxcgKrk2ZxX4uLVLpflfXCvSCdTeC4QdFYJQU5zLuB1QAAPoUWAgMQq3e7dvv3Y9JbPTAVqvT4H2RQheIoXjnpsQdQm9uDoyQ6H0P9xHEoWFAfAj2P88heFkW8LjaFUhYK3EwKdabUoAAHZKJU3yrQWmERoQM27iLWwBtFzd16nLQaAAJNyEdHZAA
(Only applicable for extension issues) IDE Information
No response
Thanks for reporting, there are some known gaps in how we analyze match statements. I'm not sure if its feasible to be 100% accurate for exhaustiveness checks when matching on gradual types, but we can certainly do better than we're doing now.
This issue has been automatically marked as stale because it is assigned, but has not had recent activity for more than 2 weeks.
If you are still working on this issue, please add a comment to keep it active. Otherwise, please unassign yourself and allow someone else to take over.
Thank you for your contributions!
It appears that additionally, for match statements with enumerations, pyrefly doesn't mark all branches as being handled when the enum is exhausted. From my perspective, it's probably the same issue, but if you have a return type in a function and return from inside the match, then pyrefly will complain unless you have a case _ or similar, even though you've covered the whole enum. Example:
class MyEnum(Enum):
VALUE1: "my_val"
VALUE2: "not_my_val"
def func(value: MyEnum) -> str: # marked with does not return <str> on all paths
match value:
case VALUE1:
return "matched"
case VALUE2:
return VALUE2.value
Presumably the same as what @alschade wrote, but I just wanted to add that the problem also occurs with StrEnums:
class Type(StrEnum):
FIRST = "FIRST"
SECOND = "SECOND"
def format_type(type_: Type) -> str:
match type_:
case Type.FIRST:
return "First Type"
case Type.SECOND:
return "Second Type"
This yields:
ERROR Function declared to return `str`, but one or more paths are missing an explicit `return` [bad-return]
--> /home/coder2k/dev/generics-test/match.py:9:33
|
9 | def format_type(type_: Type) -> str:
| ^^^
|
INFO 1 error