Files
ruff/crates/red_knot_python_semantic/resources/mdtest/known_constants.md
Shunsuke Shibayama 1977dda079 [red-knot] respect TYPE_CHECKING even if not imported from typing (#16468)
## Summary

This PR closes #15722.

The change is that if the variable `TYPE_CHECKING` is defined/imported,
the type of the variable is interpreted as `Literal[True]` regardless of
what the value is.
This is compatible with the behavior of other type checkers (e.g. mypy,
pyright).

## Test Plan

I ran the tests with `cargo test -p red_knot_python_semantic` and
confirmed that all tests passed.

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
2025-03-04 07:58:29 -08:00

1.7 KiB

Known constants

typing.TYPE_CHECKING

This constant is True when in type-checking mode, False otherwise. The symbol is defined to be False at runtime. In typeshed, it is annotated as bool. This test makes sure that we infer Literal[True] for it anyways.

Basic

from typing import TYPE_CHECKING
import typing

reveal_type(TYPE_CHECKING)  # revealed: Literal[True]
reveal_type(typing.TYPE_CHECKING)  # revealed: Literal[True]

Aliased

Make sure that we still infer the correct type if the constant has been given a different name:

from typing import TYPE_CHECKING as TC

reveal_type(TC)  # revealed: Literal[True]

User-defined TYPE_CHECKING

If we set TYPE_CHECKING = False directly instead of importing it from the typing module, it will still be treated as True during type checking. This behavior is for compatibility with other major type checkers, e.g. mypy and pyright.

TYPE_CHECKING = False
reveal_type(TYPE_CHECKING)  # revealed: Literal[True]
if TYPE_CHECKING:
    type_checking = True
if not TYPE_CHECKING:
    runtime = True

# type_checking is treated as unconditionally assigned.
reveal_type(type_checking)  # revealed: Literal[True]
# error: [unresolved-reference]
reveal_type(runtime)  # revealed: Unknown

Importing user-defined TYPE_CHECKING

constants.py:

TYPE_CHECKING = False
from constants import TYPE_CHECKING

# constants.TYPE_CHECKING is modifiable, but it is still treated as True.
reveal_type(TYPE_CHECKING)  # revealed: Literal[True]

typing_extensions re-export

This should behave in the same way as typing.TYPE_CHECKING:

from typing_extensions import TYPE_CHECKING

reveal_type(TYPE_CHECKING)  # revealed: Literal[True]