Change UnsetValueType to single-value enum for better type narrowing#147
Merged
Conversation
ninanomenon
approved these changes
Apr 30, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR changes the
UnsetValueTypeclass from a regular class to a single-value enum class (and therefore changesUnsetValueto be the only member of this enum). This improves type narrowing.Explanation
The current approach of how the
UnsetValuesentinel object is implemented has several drawbacks. The biggest issue for users is that type checkers can't do proper type narrowing on it.For example, given a variable
fooof typestr | UnsetValueType, the following 3 if conditions all work to filter out UnsetValue:A type checker should be able to recognize this and narrow down the type: Within each of the if blocks, the variable should be treated as a
str.However, with the current approach, this does not work: The identity check is not a type check, it only tells us that
varis not the specific objectUnsetValue, but from mypy's perspective, it could still be a different object of the typeUnsetValueType. There's no way for mypy to know that there is only (and can only be) a single instance of the typeUnsetValueType.This PR replaces the current approach with a different one: Defining
UnsetValueTypeas an enum class that only has a single valueUnsetValueType.UnsetValue(and definingUnsetValueas a short name for the enum member).This solves the problem, because mypy has some special logic for single-value enums. An enum with only a single value is essentially a singleton class, so mypy knows that
UnsetValueis the only instance ofUnsetValueType. If a variable is notUnsetValue, mypy can infer that the variable cannot be of typeUnsetValueTypeeither, so type narrowing works as intended.Additionally, to allow type narrowing in boolean evaluation (the third example), the return type annotation of
UnsetValueType.__bool__was changed frombooltoLiteral[False], because the class always evaluates as False.List of changes
UnsetValueTypeis now anEnumclass with the single valueUnsetValueType.UnsetValue.UnsetValueis now an alias forUnsetValueType.UnsetValue.UnsetValueType.__bool__is nowLiteral[False](instead ofbool).UnsetValueType()now raises an exception instead of returningUnsetValue. This is a side effect of the enum implementation, shouldn't affect anyone in practice though.