Skip to content

Narrow some more types via TypedDicts - #2

Open
FichteFoll wants to merge 7 commits into
mainfrom
feature/more-type-narrowing
Open

Narrow some more types via TypedDicts#2
FichteFoll wants to merge 7 commits into
mainfrom
feature/more-type-narrowing

Conversation

@FichteFoll

@FichteFoll FichteFoll commented Aug 9, 2026

Copy link
Copy Markdown
Member

The lack of a TypedDict definition for ui_info was discovered via PackageDev, after which I started looking for more definitions that were more generic than necessary. We now define several more types in sublime_types (re-exported in sublime for compatibility) that do not exist upstream but that provide more precise type information.

This also surfaced an error in the documentation about the types in style_for_scope's return value.

Note: All of this was done by Claude Opus (but I had to manually append the "Co-Authored-By" signature to each commit's message 😔 ).

FichteFoll and others added 7 commits August 9, 2026 22:47
The reference only annotates it as a bare dict; the official API docs and
community docs go no further than naming the system/theme/color_scheme
top-level keys. The shapes are reverse engineered from a live call and have
no counterpart in sublime_types at runtime, so they are stub-only additions
via a new EXTRA_TYPE_ALIAS_CLASSES table for generator content that isn't
keyed to anything in the reference.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The reference returns a bare `dict[str, Value]`, but its docstring
(references/python38/sublime.py:3057-3080) enumerates every key, so the
shape transcribes directly into a `TypedDict`. The keys it flags
"(only if set)" become `NotRequired`; the rest stay required, which is
what makes reading `style["foreground"]` legal while `background` still
needs `.get`. This is the first use of `NotRequired`, hence the new
entry in the generator's `TYPING_EXTENSIONS_NAMES`.

Two of the documented types are wrong. sublime.py:3074-3076 claims
`"source_line": str` and `"source_file": int`, but a live
`view.style_for_scope(...)` call returns `'source_line': -1` and
`'source_file': 'Packages/Theme - Nil/Tubnil_mod.tmTheme'`. The stubs
follow the runtime, as they already do for `QuickPanelItem.details`.

Prose inside an `EXTRA_TYPE_ALIAS_CLASSES` block has to avoid the bare
word `sublime`: the import builder scans the generated body for module
names, so a `references/python38/sublime.py` citation there would add an
unused `import sublime` to `sublime_types`. The citation therefore lives
in the surrounding Python comment, as it does for `UIInfo`.

`SUBLIME_TYPES_REEXPORTS["sublime"]` is wrapped one name per line, since
it no longer fits on one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The reference returns a bare `list[dict]`, but its docstring
(references/python38/sublime.py:1314-1320) says each entry "will
contain the keys `"command"` and `"args"`", so both fields transcribe
as required rather than `NotRequired`.

`CommandArgs` is already `dict[str, Value] | None`, so a command with
no arguments is covered by its `None` arm without widening `args` any
further.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`Window.layout()`, `Window.get_layout()` and `Window.set_layout()`
returned or accepted a bare `dict[str, Value]`, but the reference
(references/python38/sublime.py:1679-1695) documents no keys at all,
only "Get/Set the group layout of the window". The `cols`, `rows` and
`cells` keys below come from a live `window.layout()` call, which
returned `{'cells': [[0, 0, 1, 1]], 'cols': [0.0, 1.0], 'rows': [0.0,
1.0]}`; all three were present, so the class is total.

`set_layout`'s parameter needs its own `PARAMS` override to
`WindowLayout`, not just the `RETURNS` change on `layout()` and
`get_layout()`. A `TypedDict` is not assignable to `dict[str, Value]`,
so without it `window.set_layout(window.layout())` -- the round trip
the getter/setter pair exists for -- would stop type-checking the
moment `layout()` returns a `WindowLayout` instead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The reference returns a bare `dict[str, str]`, but its docstring
(references/python38/sublime.py:2017-2038) enumerates every key. It
introduces that list with "May contain:", so not one of them is
guaranteed: the class is uniformly `total=False` rather than marking all
thirteen fields `NotRequired`, which would say the same thing at greater
length.

`expand_variables` has to name both `dict[str, str]` and
`WindowVariables` in its `variables` parameter. sublime.py:2036 tells
the user the result of `extract_variables()` is "suitable for use with
`expand_variables()`", but a `TypedDict` is assignable to neither
`dict[str, str]` nor `Mapping[str, str]` -- only to
`Mapping[str, object]`, which would wrongly accept any mapping of
arbitrary values. Without the union, typing the result at all would
break the round trip the reference itself recommends.

The win over `dict[str, str]` is rejecting an unknown or misspelled key.
That is a negative property, and the sample code deliberately asserts
none: the repository has no `type: ignore` precedent, and
`reportUnnecessaryTypeIgnoreComment` plus mypy's `warn_unused_ignores`
would make such an assertion fragile across four checkers.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The reference (references/python38/sublime.py:925-947) documents no
types at all, only the example `{ "font_face": "monospace" }` at
sublime.py:932. A live `choose_font_dialog(print)` call passed
`{'font_face': 'Sans', 'font_size': 10}` to the callback, which is
where `font_size: int` comes from.

`FontOptions` is `total=False` because the same type describes both
the callback argument and the `default` parameter, and the input side
reads both fields through `.get` (sublime.py:941-943:
`default.get("font_face")` and `default.get("font_size")`).

The `| None` sits on the callback argument, not on `default`: the
dialog "will be called with ``None`` if the dialog is cancelled"
(sublime.py:933-934), so the callback has to accept that. `default`
needs no `| None` of its own -- the generator already emits
`FontOptions | None = ...` from the reference's `= None` default.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@FichteFoll
FichteFoll force-pushed the feature/more-type-narrowing branch from 4d6e36b to 32cfef3 Compare August 9, 2026 20:48
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.

1 participant