Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions mypy/stubtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -1274,6 +1274,7 @@ def verify_var(
yield Error(object_path, "is read-only at runtime but not in the stub", stub, runtime)

runtime_type = get_mypy_type_of_runtime_value(runtime, type_context=stub.type)
note = ""
if (
runtime_type is not None
and stub.type is not None
Expand All @@ -1286,17 +1287,28 @@ def verify_var(
runtime_type = get_mypy_type_of_runtime_value(runtime.value)
if runtime_type is not None and is_subtype_helper(runtime_type, stub.type):
should_error = False
# We always allow setting the stub value to ...
# We always allow setting the stub value to Ellipsis (...), but use
# _value_ type as a fallback if given. If a member is ... and _value_
# type is given, all runtime types should be assignable to _value_.
proper_type = mypy.types.get_proper_type(stub.type)
if (
isinstance(proper_type, mypy.types.Instance)
and proper_type.type.fullname in mypy.types.ELLIPSIS_TYPE_NAMES
):
should_error = False
value_t = stub.info.get("_value_")
if value_t is None or value_t.type is None or runtime_type is None:
should_error = False
elif is_subtype_helper(runtime_type, value_t.type):
should_error = False
else:
note = " (incompatible '_value_')"
Copy link
Collaborator

@A5rocks A5rocks Aug 30, 2025

Choose a reason for hiding this comment

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

Is this intentionally making the runtime_type is None case error now? (I tried thinking about it but IDK what that means. I also don't know stubtest very well :^)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I also tried thinking about that, couldn't invent a case where we have found an attribute but failed to interpret its value, and decided to be cautious, reporting such "smth weird" cases as incompatibilities. Maybe this should be an assert, but I'm afraid to accidentally kill stubtest right before release...

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Okay, almost all other checks do not reject "wtf" values as incompatible, updated for consistency.


if should_error:
yield Error(
object_path, f"variable differs from runtime type {runtime_type}", stub, runtime
object_path,
f"variable differs from runtime type {runtime_type}{note}",
stub,
runtime,
)


Expand Down
24 changes: 24 additions & 0 deletions mypy/test/teststubtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -1483,6 +1483,30 @@ class HasEmptySlots:
""",
error=None,
)
yield Case(
stub="""
class HasCompatibleValue(enum.Enum):
_value_: str
FOO = ...
""",
runtime="""
class HasCompatibleValue(enum.Enum):
FOO = "foo"
""",
error=None,
)
yield Case(
stub="""
class HasIncompatibleValue(enum.Enum):
_value_: int
FOO = ...
""",
runtime="""
class HasIncompatibleValue(enum.Enum):
FOO = "foo"
""",
error="HasIncompatibleValue.FOO",
)

@collect_cases
def test_decorator(self) -> Iterator[Case]:
Expand Down