Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 4a73f34

Browse files
[codex] Fix type literal substitution regressions (#456)
## Summary - stop `type[T]` substitution from raising internal errors when inference feeds back singleton literals or `None` - keep generated-callable names readable by omitting a bogus `None.` module prefix - add both low-level and `@assert_passes()` regressions for the taxonomy-shaped `type[T] | T` path ## Root Cause Latest `main` made `SubclassValue.make()` strict again, which meant generic specialization for patterns like `type[T] | T` could crash when `T` was inferred from a singleton runtime value and then substituted back into the `type[T]` branch. Taxonomy hits this through ADT-style singleton tags. Separately, some generated callables can have `__module__ = None`, and `stringify_object()` was rendering that directly into messages. ## Impact This removes the new taxonomy internal errors without bringing back the broader callable-reset change in `checker.py`. Error messages for generated callables also stay readable. ## Validation - `UV_CACHE_DIR=/tmp/uv-cache uv run --python 3.14 --extra tests pytest` - `UV_CACHE_DIR=/tmp/uv-cache uv run --python 3.14 python tools/taxonomy_ci.py --local-path /Users/jelle/Dropbox/code/taxonomy --install` ## Taxonomy Artifact - regenerated raw errors: `/Users/jelle/py/pycroscope/tmp/taxonomy/raw-errors-2026-04-03-minimal-main.txt`
1 parent 023a98d commit 4a73f34

4 files changed

Lines changed: 67 additions & 12 deletions

File tree

docs/changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## Unreleased
44

5+
- Avoid internal errors for some constructor-style generic patterns like `type[T] | T`, and stop showing a misleading `None.` module prefix in messages for generated callables.
56
- Make `type()` results more consistent for imprecise values like `str`, `type[T]`, and `super()`, so pycroscope preserves subclass-aware class objects where appropriate and stops relying on implicit fallback behavior for internal type values.
67
- Simplify `type()` lookups through narrowed intersections, so `hasattr()`-guarded class-object calls preserve the real constructor signature instead of picking up confusing fallback overloads like `object.__init__`.
78
- Fix assignability involving weak list literals in nested invariant containers, so values like `defaultdict(lambda: ([], []))` can satisfy annotated tuple-valued `dict` entries instead of failing on the inner empty-list literals.

pycroscope/test_name_check_visitor.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1859,6 +1859,37 @@ class Child(Base):
18591859
def capybara(cls: type[Base]) -> None:
18601860
cls.clirm_backrefs.append(1)
18611861

1862+
@assert_passes(run_in_both_module_modes=True)
1863+
def test_type_or_instance_parameter_does_not_crash_on_singleton_substitution(self):
1864+
from collections.abc import Iterable, Sequence
1865+
from typing import TypeVar
1866+
1867+
ADTT = TypeVar("ADTT", bound="ADT")
1868+
1869+
class ADT:
1870+
pass
1871+
1872+
class Child(ADT):
1873+
pass
1874+
1875+
class Tags:
1876+
child = Child()
1877+
1878+
def get_tags(
1879+
tags: Sequence[ADT] | None, tag_cls: type[ADTT] | ADTT, expected: ADTT
1880+
) -> Iterable[ADTT]:
1881+
if tags is None:
1882+
return
1883+
for tag in tags:
1884+
if isinstance(tag_cls, type):
1885+
if isinstance(tag, tag_cls):
1886+
yield tag
1887+
elif tag is tag_cls:
1888+
yield tag
1889+
1890+
def capybara(tags: Sequence[ADT]) -> None:
1891+
list(get_tags(tags, Tags.child, Tags.child))
1892+
18621893
@assert_passes(run_in_both_module_modes=True)
18631894
def test_metaclass_initialized_class_attribute_on_type_object(self):
18641895
from typing import ClassVar

pycroscope/test_value.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,9 @@
66
import types
77
import typing
88
from collections import defaultdict
9-
from typing import NewType
9+
from typing import Any, NewType, cast
1010
from unittest import mock
1111

12-
import pytest
1312
from typing_extensions import Protocol, TypeVarTuple, runtime_checkable
1413

1514
from . import tests, value
@@ -341,8 +340,20 @@ def test_subclass_value() -> None:
341340

342341

343342
def test_subclass_value_make_invalid_literal() -> None:
344-
with pytest.raises(TypeError, match=r"Cannot construct type\[\.\.\.\]"):
345-
SubclassValue.make(KnownValue(1))
343+
assert SubclassValue.make(KnownValue(1)) == AnyValue(AnySource.error)
344+
345+
346+
def test_subclass_value_make_none_literal() -> None:
347+
assert SubclassValue.make(KnownValue(None)) == SubclassValue(TypedValue(type(None)))
348+
349+
350+
def test_stringify_object_without_module_name() -> None:
351+
def generated() -> None:
352+
pass
353+
354+
cast(Any, generated).__module__ = None
355+
cast(Any, generated).__qualname__ = "Generated.__init__"
356+
assert value.stringify_object(generated) == "Generated.__init__"
346357

347358

348359
def test_generic_value() -> None:

pycroscope/value.py

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2810,16 +2810,24 @@ def make(cls, origin: Value) -> Value:
28102810
28112811
This should only be called with values that are valid as the argument to
28122812
``type[...]``: a type expression, ``Any``, a ``TypeVar``, or a union of
2813-
those. Passing class objects or arbitrary values is a caller bug.
2813+
those. If inference substitutes a runtime class object or literal value
2814+
here, preserve the class case and degrade other invalid inputs instead of
2815+
crashing.
28142816
"""
28152817
if isinstance(origin, MultiValuedValue):
28162818
return unite_values(*[cls.make(val) for val in origin.vals])
28172819
if isinstance(origin, AnyValue):
28182820
# Type[Any] is equivalent to plain type
28192821
return TypedValue(type)
2822+
if isinstance(origin, KnownValue):
2823+
if origin.val is None:
2824+
return cls(TypedValue(type(None)))
2825+
if isinstance(origin.val, type):
2826+
return cls(TypedValue(origin.val))
2827+
return AnyValue(AnySource.error)
28202828
if isinstance(origin, (TypeVarValue, TypedValue)):
28212829
return cls(origin)
2822-
raise TypeError(f"Cannot construct type[...] from {origin!r}")
2830+
return AnyValue(AnySource.inference)
28232831

28242832

28252833
@dataclass(frozen=True, order=False)
@@ -4646,12 +4654,16 @@ def stringify_object(obj: Any) -> str:
46464654
objclass = getattr(obj, "__objclass__", None)
46474655
if objclass is not None:
46484656
return f"{stringify_object(objclass)}.{obj.__name__}"
4649-
if obj.__module__ == BUILTIN_MODULE:
4650-
return obj.__name__
4651-
elif hasattr(obj, "__qualname__"):
4652-
return f"{obj.__module__}.{obj.__qualname__}"
4653-
else:
4654-
return f"{obj.__module__}.{obj.__name__}"
4657+
module_name = getattr(obj, "__module__", None)
4658+
qualname = getattr(obj, "__qualname__", None)
4659+
name = getattr(obj, "__name__", None)
4660+
if module_name == BUILTIN_MODULE and name is not None:
4661+
return name
4662+
if qualname is not None:
4663+
return f"{module_name}.{qualname}" if module_name else qualname
4664+
if name is not None:
4665+
return f"{module_name}.{name}" if module_name else name
4666+
return repr(obj)
46554667
except Exception:
46564668
return repr(obj)
46574669

0 commit comments

Comments
 (0)