Skip to content

fix(http-client-python): don't corrupt TypedDict literal values matching builtin names - #11268

Merged
Yuchao Yan (msyyc) merged 1 commit into
mainfrom
l0lawrence-fix-builtins-literal-corruption
Jul 16, 2026
Merged

fix(http-client-python): don't corrupt TypedDict literal values matching builtin names#11268
Yuchao Yan (msyyc) merged 1 commit into
mainfrom
l0lawrence-fix-builtins-literal-corruption

Conversation

@l0lawrence

Copy link
Copy Markdown
Member

Problem

In the Python HTTP client emitter, a TypedDict literal value that coincides with a Python builtin type name got corrupted into builtins.X in the generated types.py:

class TypeParam(TypedDict, total=False):
    type: Required[Literal["builtins.type"]]   # WRONG — should be Literal["type"]
    text: Required[str]

The TypeSpec source (type: "type";) is correct; the corruption is introduced purely by the emitter.

Root cause

packages/http-client-python/generator/pygen/codegen/serializers/types_serializer.py has a TypedDict-only builtin-shadowing workaround: when a field's wire name is a Python builtin type name (e.g. bytes, int, type) and a sibling field is annotated with that bare builtin, pyright errors reportInvalidTypeForm (the earlier field shadows the type). To avoid that, the serializer qualifies the builtin as builtins.X.

Both the detection (get_shadowed_builtins) and the application (_qualify_shadowed_builtins) used a naive \bname\b regex that also matched inside string literals like Literal["type"], so:

  • literal values (and quoted forward references) were rewritten into builtins.X, and
  • spurious import builtins statements were emitted.

A second, related issue: detection ran against the default annotation while emission uses the TYPES_FILE annotation. Under TYPES_FILE some types change (bytes/datetimestr, decimalfloat), so detection could both miss real shadowing and flag false ones.

Fix

  • _qualify_shadowed_builtins and the detection helper are now string-literal-aware: the annotation is split on single/double-quoted spans and the \bname\b substitution only runs on code segments. A (?<!\.) lookbehind also skips already-dotted names (no builtins.builtins).
  • get_shadowed_builtins now detects shadowing against the actually-emitted (TYPES_FILE) annotation, so genuine sibling-builtin shadowing is still qualified while literal values / quoted forward references are left untouched, and no spurious import builtins is emitted.

Surgical change; no unrelated refactoring.

Behavior preserved

The legitimate case is still qualified (verified with pyright):

class Blob(TypedDict, total=False):
    int: Required[str]
    count: Required[builtins.int]   # genuine sibling-builtin shadowing -> still qualified

Tests

  • Added unit coverage in tests/unit/test_typeddict.py (36 pass), covering: (a) a Literal value coinciding with a builtin name is not rewritten; (b) a quoted forward reference is left untouched; (c) genuine sibling-builtin shadowing is qualified to builtins.X; (d) already-dotted names are not double-qualified; (e) no spurious import builtins for the false-positive; and (f) types that change under TYPES_FILE don't cause spurious imports.

Regeneration

Regenerated the full test corpus (azure + unbranded) with and without the change. Zero diff — the fix is behavior-preserving:

  • The existing genuine case (typetest-union, a field named int with builtins.int siblings) is retained unchanged.
  • No existing spec has a literal value that coincides with a builtin name, so the false-positive path isn't exercised by the corpus; it is covered directly by the new unit tests.

Validation

  • pytest tests/unit/test_typeddict.py → 36 passed
  • pylint 10.00/10; black clean on changed lines

…ing builtin names

The builtin-shadowing workaround in types_serializer.py used a naive
word-boundary regex that also matched identifiers inside string literals
(e.g. the `type` in `Literal[\\	ype\\]`), rewriting literal values
and quoted forward references into `builtins.X` and emitting spurious
`import builtins` statements.

Detection and application are now string-literal-aware and detect
shadowing against the actually-emitted (TYPES_FILE) annotation, so
genuine sibling-builtin shadowing is still qualified while literal
values and quoted forward references are left untouched.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@microsoft-github-policy-service microsoft-github-policy-service Bot added the emitter:client:python Issue for the Python client emitter: @typespec/http-client-python label Jul 15, 2026
@pkg-pr-new

pkg-pr-new Bot commented Jul 15, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@typespec/http-client-python@11268

commit: 4cc4ea8

@github-actions

Copy link
Copy Markdown
Contributor

All changed packages have been documented.

  • @typespec/http-client-python
Show changes

@typespec/http-client-python - fix ✏️

Fix a bug where a TypedDict literal value that coincides with a Python builtin type name (e.g. type: "type") was corrupted into Literal["builtins.type"] in the generated types.py. The builtin-shadowing workaround now ignores identifiers inside string literals (literal values and quoted forward references are left untouched) and detects shadowing against the actually-emitted annotation, so genuine sibling-builtin shadowing is still qualified while spurious import builtins statements are no longer emitted.

@azure-sdk-automation

azure-sdk-automation Bot commented Jul 15, 2026

Copy link
Copy Markdown

You can try these changes here

🛝 Playground 🌐 Website 🛝 VSCode Extension

@msyyc
Yuchao Yan (msyyc) added this pull request to the merge queue Jul 16, 2026
Merged via the queue into main with commit 7426a1a Jul 16, 2026
41 checks passed
@msyyc
Yuchao Yan (msyyc) deleted the l0lawrence-fix-builtins-literal-corruption branch July 16, 2026 03:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

emitter:client:python Issue for the Python client emitter: @typespec/http-client-python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants