From 1ce57edf33535583f5880132fec1dee344ae30da Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Mon, 6 Oct 2025 12:43:34 +0100 Subject: [PATCH] [ty] Enforce that `typing_extensions` must come from a stdlib search path (#20715) --- .../src/module_resolver/resolver.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/crates/ty_python_semantic/src/module_resolver/resolver.rs b/crates/ty_python_semantic/src/module_resolver/resolver.rs index 5705aefe11..f4cb91e28f 100644 --- a/crates/ty_python_semantic/src/module_resolver/resolver.rs +++ b/crates/ty_python_semantic/src/module_resolver/resolver.rs @@ -666,11 +666,15 @@ struct ModuleNameIngredient<'db> { /// Returns `true` if the module name refers to a standard library module which can't be shadowed /// by a first-party module. /// -/// This includes "builtin" modules, which can never be shadowed at runtime either, as well as the -/// `types` module, which tends to be imported early in Python startup, so can't be consistently -/// shadowed, and is important to type checking. +/// This includes "builtin" modules, which can never be shadowed at runtime either, as well as +/// certain other modules that are involved in an import cycle with `builtins` (`types`, +/// `typing_extensions`, etc.). This latter set of modules cannot be allowed to be shadowed by +/// first-party or "extra-path" modules, or we risk panics in unexpected places due to being +/// unable to resolve builtin symbols. This is similar behaviour to other type checkers such +/// as mypy: pub(super) fn is_non_shadowable(minor_version: u8, module_name: &str) -> bool { - module_name == "types" || ruff_python_stdlib::sys::is_builtin_module(minor_version, module_name) + matches!(module_name, "types" | "typing_extensions") + || ruff_python_stdlib::sys::is_builtin_module(minor_version, module_name) } /// Given a module name and a list of search paths in which to lookup modules,