Fix inspect.type_info crashing on mixed-type Literals - #1080
Conversation
Merging this PR will not alter performance
Comparing Footnotes
|
Siyet
left a comment
There was a problem hiding this comment.
Thanks for the fix! The type_info part is solid: I verified the sort key is total and deterministic for every value the validator lets through, same-type ordering is preserved bit-for-bit, and the full unit suite is green on your branch.
One blocker though - the same crash is still one call above, in json.schema:
>>> msgspec.json.schema(Literal[1, None])
TypeError: '<' not supported between instances of 'int' and 'NoneType'_json_schema.py (~line 367) does its own plain schema["enum"] = sorted(t.values) on the same values. So the motivating examples from the PR description now work in inspect.type_info but still fail in the more user-facing json.schema entry point with the exact same TypeError - the crash just moved one frame down. Since LiteralType.values is already sorted by your fix, schema["enum"] = list(t.values) is enough; please also add a test_schema.py case for Literal[1, None] (the schema suite is green today only because no test covers mixed literals there).
Non-blocking notes:
- The
except TypeErrorfallback looks unreachable:multi_type_infovalidates types by constructing a decoder first, which rejects anything but None/bool/int/str before the sort ever runs, and within those the key can't raise. Fine to keep as a guard, but it could just as well go. - The
sorted(e.value for e in t.cls)for enums a few lines below has the same latent issue for mixed-value enums - out of scope here, just noting it as a possible follow-up. - For bool/int mixes (
Literal[0, True]) the ordering changes vs current main ((0, True)->(True, 0)). That only affects unreleased bool-literal support (#1004), so no released behavior changes, but worth a line in the PR description.
The changelog entry is already in place, thanks for that.
|
Good catch — fixed. >>> msgspec.json.schema(Literal[1, None])
{'enum': [None, 1]}Added |
type_info sorted a Literal's values with a plain sorted(), which raises TypeError on mixed-type literals such as Literal[1, None] or Literal[True, "yes"] since Python 3 forbids ordering across types. The encoder/decoder already support these literals, so type_info crashing on them is a contract violation. Sort by (type name, value) so same-type ordering is unchanged and mixed-type literals are grouped deterministically, with a try/except fallback. Also widen the LiteralType.values annotation to allow mixed/None members. Adds test_bool_literal and test_mixed_literal.
msgspec.json.schema sorted a LiteralType's values with a plain sorted(), which raises TypeError on a mixed-type Literal like Literal[1, None]. Reuse the same type-aware sort as inspect.type_info so the two entry points agree and neither crashes.
f6fb277 to
37c098b
Compare
Siyet
left a comment
There was a problem hiding this comment.
The follow-up commit resolves the change request: json.schema now reuses the same type-aware _sort_literal_args as inspect.type_info, so both entry points handle mixed-type literals consistently instead of one of them still crashing.
Verified against main: inspect.type_info(Literal[1, None]) and msgspec.json.schema(Literal[1, None]) both crash before this PR and both work after, while same-type literal ordering (str/int/bool) is unchanged. test_inspect.py + test_schema.py green. Thanks!
Fixes #1018.
Problem
msgspec.inspect.type_infocrashes on mixed-typeLiterals:type_infodoestuple(sorted(args))on the literal's values, and Python 3 forbids<between values of different types. The encoder/decoder already handle these mixed-type literals fine (e.g.Literal[1, None]decodes1/nulland rejects2), sotype_infofailing on them is a contract violation.Fix
Sort the literal values with a
(type name, value)key instead. This keeps existing same-type ordering identical (Literal[3,1,2]→(1,2,3),Literal[True,False]→(False,True)) while grouping mixed-type literals deterministically rather than crashing, and atry/except TypeErrorfalls back to the original order as a safety net. TheLiteralType.valuesannotation is widened to allow mixed andNonemembers.Tests
Added
test_mixed_literal(Literal[1, None],Literal[True, "yes"]) andtest_bool_literal. Without the fixtest_mixed_literalraises theTypeError; with it the fulltest_inspect.py/test_schema.pypass (276 passed, 13 skipped).I'll add a
docs/changelog.mdentry referencing this PR number in a follow-up commit.Disclosure: I used AI assistance (Claude) to help locate and draft this fix, under my direction and review.