Skip to content

Fix inspect.type_info crashing on mixed-type Literals - #1080

Merged
Siyet merged 3 commits into
msgspec:mainfrom
gaoflow:fix-inspect-mixed-literal
Jul 9, 2026
Merged

Fix inspect.type_info crashing on mixed-type Literals#1080
Siyet merged 3 commits into
msgspec:mainfrom
gaoflow:fix-inspect-mixed-literal

Conversation

@gaoflow

@gaoflow gaoflow commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Fixes #1018.

Problem

msgspec.inspect.type_info crashes on mixed-type Literals:

msgspec.inspect.type_info(Literal[1, None])      # TypeError: '<' not supported between instances of 'NoneType' and 'int'
msgspec.inspect.type_info(Literal[True, "yes"])  # same

type_info does tuple(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] decodes 1/null and rejects 2), so type_info failing 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 a try/except TypeError falls back to the original order as a safety net. The LiteralType.values annotation is widened to allow mixed and None members.

Tests

Added test_mixed_literal (Literal[1, None], Literal[True, "yes"]) and test_bool_literal. Without the fix test_mixed_literal raises the TypeError; with it the full test_inspect.py/test_schema.py pass (276 passed, 13 skipped).

I'll add a docs/changelog.md entry 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.

gaoflow added a commit to gaoflow/msgspec that referenced this pull request Jun 17, 2026
@codspeed-hq

codspeed-hq Bot commented Jul 2, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 139 untouched benchmarks
⏩ 135 skipped benchmarks1


Comparing gaoflow:fix-inspect-mixed-literal (37c098b) with main (956a7a4)

Open in CodSpeed

Footnotes

  1. 135 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@Siyet Siyet left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 TypeError fallback looks unreachable: multi_type_info validates 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.

@gaoflow

gaoflow commented Jul 7, 2026

Copy link
Copy Markdown
Contributor Author

Good catch — fixed. json.schema did its own sorted(t.values) on the same values, so Literal[1, None] still crashed there. It now reuses the same type-aware sort (_sort_literal_args) as inspect.type_info, so both entry points agree and neither crashes:

>>> msgspec.json.schema(Literal[1, None])
{'enum': [None, 1]}

Added test_mixed_literal to tests/unit/test_schema.py (Literal[1, None] and Literal[True, "yes"]) — it fails at the old sorted() and passes now. Since a LiteralType's values are already canonically ordered by type_info, the reused helper is idempotent for single-type literals, so existing enum ordering is unchanged. Full schema + inspect suites green.

gaoflow added 3 commits July 7, 2026 17:51
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.
@gaoflow
gaoflow force-pushed the fix-inspect-mixed-literal branch from f6fb277 to 37c098b Compare July 7, 2026 15:52

@Siyet Siyet left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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!

@Siyet
Siyet added this pull request to the merge queue Jul 9, 2026
Merged via the queue into msgspec:main with commit ecf2282 Jul 9, 2026
26 of 27 checks passed
@Siyet Siyet mentioned this pull request Jul 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

inspect.type_info crashes on mixed-type Literals

2 participants