diff --git a/crates/red_knot_python_semantic/resources/mdtest/known_constants.md b/crates/red_knot_python_semantic/resources/mdtest/known_constants.md new file mode 100644 index 0000000000000..ddad1fcc75dac --- /dev/null +++ b/crates/red_knot_python_semantic/resources/mdtest/known_constants.md @@ -0,0 +1,52 @@ +# 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 + +```py +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: + +```py +from typing import TYPE_CHECKING as TC + +reveal_type(TC) # revealed: Literal[True] +``` + +### Must originate from `typing` + +Make sure we only use our special handling for `typing.TYPE_CHECKING` and not for other constants +with the same name: + +```py path=constants.py +TYPE_CHECKING: bool = False +``` + +```py +from constants import TYPE_CHECKING + +reveal_type(TYPE_CHECKING) # revealed: bool +``` + +### `typing_extensions` re-export + +This should behave in the same way as `typing.TYPE_CHECKING`: + +```py +from typing_extensions import TYPE_CHECKING + +reveal_type(TYPE_CHECKING) # revealed: Literal[True] +``` diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 188af14027bb8..83b02eddb6cf5 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -120,6 +120,14 @@ fn symbol_by_id<'db>(db: &'db dyn Db, scope: ScopeId<'db>, symbol: ScopedSymbolI /// Shorthand for `symbol_by_id` that takes a symbol name instead of an ID. fn symbol<'db>(db: &'db dyn Db, scope: ScopeId<'db>, name: &str) -> Symbol<'db> { + // We don't need to check for `typing_extensions` here, because `typing_extensions.TYPE_CHECKING` + // is just a re-export of `typing.TYPE_CHECKING`. + if name == "TYPE_CHECKING" + && file_to_module(db, scope.file(db)).is_some_and(|module| module.name() == "typing") + { + return Symbol::Type(Type::BooleanLiteral(true), Boundness::Bound); + } + let table = symbol_table(db, scope); table .symbol_id_by_name(name)