Rename custom-typeshed-dir, target-version and current-directory CLI options (#14930)

## Summary

This PR renames the `--custom-typeshed-dir`, `target-version`, and
`--current-directory` cli options to `--typeshed`,
`--python-version`, and `--project` as discussed in the CLI proposal
document.
I added aliases for `--target-version` (for Ruff compat) and
`--custom-typeshed-dir` (for Alex)

## Test Plan

Long help

```
An extremely fast Python type checker.

Usage: red_knot [OPTIONS] [COMMAND]

Commands:
  server  Start the language server
  help    Print this message or the help of the given subcommand(s)

Options:
      --project <PROJECT>
          Run the command within the given project directory.
          
          All `pyproject.toml` files will be discovered by walking up the directory tree from the project root, as will the project's virtual environment (`.venv`).
          
          Other command-line arguments (such as relative paths) will be resolved relative to the current working directory."#,

      --venv-path <PATH>
          Path to the virtual environment the project uses.
          
          If provided, red-knot will use the `site-packages` directory of this virtual environment to resolve type information for the project's third-party dependencies.

      --typeshed-path <PATH>
          Custom directory to use for stdlib typeshed stubs

      --extra-search-path <PATH>
          Additional path to use as a module-resolution source (can be passed multiple times)

      --python-version <VERSION>
          Python version to assume when resolving types
          
          [possible values: 3.7, 3.8, 3.9, 3.10, 3.11, 3.12, 3.13]

  -v, --verbose...
          Use verbose output (or `-vv` and `-vvv` for more verbose output)

  -W, --watch
          Run in watch mode by re-running whenever files change

  -h, --help
          Print help (see a summary with '-h')

  -V, --version
          Print version
```

Short help 

```
An extremely fast Python type checker.

Usage: red_knot [OPTIONS] [COMMAND]

Commands:
  server  Start the language server
  help    Print this message or the help of the given subcommand(s)

Options:
      --project <PROJECT>         Run the command within the given project directory
      --venv-path <PATH>          Path to the virtual environment the project uses
      --typeshed-path <PATH>      Custom directory to use for stdlib typeshed stubs
      --extra-search-path <PATH>  Additional path to use as a module-resolution source (can be passed multiple times)
      --python-version <VERSION>  Python version to assume when resolving types [possible values: 3.7, 3.8, 3.9, 3.10, 3.11, 3.12, 3.13]
  -v, --verbose...                Use verbose output (or `-vv` and `-vvv` for more verbose output)
  -W, --watch                     Run in watch mode by re-running whenever files change
  -h, --help                      Print help (see more with '--help')
  -V, --version                   Print version

```

---------

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
This commit is contained in:
Micha Reiser 2024-12-13 09:21:52 +01:00 committed by GitHub
parent d7ce548893
commit c1837e4189
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
33 changed files with 280 additions and 297 deletions

View file

@ -166,12 +166,12 @@ pub(crate) mod tests {
.context("Failed to write test files")?;
let mut search_paths = SearchPathSettings::new(src_root);
search_paths.custom_typeshed = self.custom_typeshed;
search_paths.typeshed = self.custom_typeshed;
Program::from_settings(
&db,
&ProgramSettings {
target_version: self.python_version,
python_version: self.python_version,
search_paths,
},
)

View file

@ -283,9 +283,9 @@ fn query_stdlib_version(
let Some(module_name) = stdlib_path_to_module_name(relative_path) else {
return TypeshedVersionsQueryResult::DoesNotExist;
};
let ResolverContext { db, target_version } = context;
let ResolverContext { db, python_version } = context;
typeshed_versions(*db).query_module(&module_name, *target_version)
typeshed_versions(*db).query_module(&module_name, *python_version)
}
/// Enumeration describing the various ways in which validation of a search path might fail.
@ -658,7 +658,7 @@ mod tests {
let TestCase {
db, src, stdlib, ..
} = TestCaseBuilder::new()
.with_custom_typeshed(MockedTypeshed::default())
.with_mocked_typeshed(MockedTypeshed::default())
.build();
assert_eq!(
@ -779,7 +779,7 @@ mod tests {
#[should_panic(expected = "Extension must be `pyi`; got `py`")]
fn stdlib_path_invalid_join_py() {
let TestCase { db, stdlib, .. } = TestCaseBuilder::new()
.with_custom_typeshed(MockedTypeshed::default())
.with_mocked_typeshed(MockedTypeshed::default())
.build();
SearchPath::custom_stdlib(&db, stdlib.parent().unwrap())
.unwrap()
@ -791,7 +791,7 @@ mod tests {
#[should_panic(expected = "Extension must be `pyi`; got `rs`")]
fn stdlib_path_invalid_join_rs() {
let TestCase { db, stdlib, .. } = TestCaseBuilder::new()
.with_custom_typeshed(MockedTypeshed::default())
.with_mocked_typeshed(MockedTypeshed::default())
.build();
SearchPath::custom_stdlib(&db, stdlib.parent().unwrap())
.unwrap()
@ -822,7 +822,7 @@ mod tests {
#[test]
fn relativize_stdlib_path_errors() {
let TestCase { db, stdlib, .. } = TestCaseBuilder::new()
.with_custom_typeshed(MockedTypeshed::default())
.with_mocked_typeshed(MockedTypeshed::default())
.build();
let root = SearchPath::custom_stdlib(&db, stdlib.parent().unwrap()).unwrap();
@ -867,11 +867,11 @@ mod tests {
fn typeshed_test_case(
typeshed: MockedTypeshed,
target_version: PythonVersion,
python_version: PythonVersion,
) -> (TestDb, SearchPath) {
let TestCase { db, stdlib, .. } = TestCaseBuilder::new()
.with_custom_typeshed(typeshed)
.with_target_version(target_version)
.with_mocked_typeshed(typeshed)
.with_python_version(python_version)
.build();
let stdlib = SearchPath::custom_stdlib(&db, stdlib.parent().unwrap()).unwrap();
(db, stdlib)

View file

@ -160,7 +160,7 @@ impl SearchPaths {
let SearchPathSettings {
extra_paths,
src_root,
custom_typeshed,
typeshed,
site_packages: site_packages_paths,
} = settings;
@ -180,17 +180,13 @@ impl SearchPaths {
tracing::debug!("Adding first-party search path '{src_root}'");
static_paths.push(SearchPath::first_party(system, src_root.to_path_buf())?);
let (typeshed_versions, stdlib_path) = if let Some(custom_typeshed) = custom_typeshed {
let custom_typeshed = canonicalize(custom_typeshed, system);
tracing::debug!("Adding custom-stdlib search path '{custom_typeshed}'");
let (typeshed_versions, stdlib_path) = if let Some(typeshed) = typeshed {
let typeshed = canonicalize(typeshed, system);
tracing::debug!("Adding custom-stdlib search path '{typeshed}'");
files.try_add_root(
db.upcast(),
&custom_typeshed,
FileRootKind::LibrarySearchPath,
);
files.try_add_root(db.upcast(), &typeshed, FileRootKind::LibrarySearchPath);
let versions_path = custom_typeshed.join("stdlib/VERSIONS");
let versions_path = typeshed.join("stdlib/VERSIONS");
let versions_content = system.read_to_string(&versions_path).map_err(|error| {
SearchPathValidationError::FailedToReadVersionsFile {
@ -201,7 +197,7 @@ impl SearchPaths {
let parsed: TypeshedVersions = versions_content.parse()?;
let search_path = SearchPath::custom_stdlib(db, &custom_typeshed)?;
let search_path = SearchPath::custom_stdlib(db, &typeshed)?;
(parsed, search_path)
} else {
@ -530,10 +526,10 @@ struct ModuleNameIngredient<'db> {
/// attempt to resolve the module name
fn resolve_name(db: &dyn Db, name: &ModuleName) -> Option<(SearchPath, File, ModuleKind)> {
let program = Program::get(db);
let target_version = program.target_version(db);
let resolver_state = ResolverContext::new(db, target_version);
let python_version = program.python_version(db);
let resolver_state = ResolverContext::new(db, python_version);
let is_builtin_module =
ruff_python_stdlib::sys::is_builtin_module(target_version.minor, name.as_str());
ruff_python_stdlib::sys::is_builtin_module(python_version.minor, name.as_str());
for search_path in search_paths(db) {
// When a builtin module is imported, standard module resolution is bypassed:
@ -690,12 +686,12 @@ impl PackageKind {
pub(super) struct ResolverContext<'db> {
pub(super) db: &'db dyn Db,
pub(super) target_version: PythonVersion,
pub(super) python_version: PythonVersion,
}
impl<'db> ResolverContext<'db> {
pub(super) fn new(db: &'db dyn Db, target_version: PythonVersion) -> Self {
Self { db, target_version }
pub(super) fn new(db: &'db dyn Db, python_version: PythonVersion) -> Self {
Self { db, python_version }
}
pub(super) fn vendored(&self) -> &VendoredFileSystem {
@ -771,8 +767,8 @@ mod tests {
let TestCase { db, stdlib, .. } = TestCaseBuilder::new()
.with_src_files(SRC)
.with_custom_typeshed(TYPESHED)
.with_target_version(PythonVersion::PY38)
.with_mocked_typeshed(TYPESHED)
.with_python_version(PythonVersion::PY38)
.build();
let builtins_module_name = ModuleName::new_static("builtins").unwrap();
@ -789,8 +785,8 @@ mod tests {
};
let TestCase { db, stdlib, .. } = TestCaseBuilder::new()
.with_custom_typeshed(TYPESHED)
.with_target_version(PythonVersion::PY38)
.with_mocked_typeshed(TYPESHED)
.with_python_version(PythonVersion::PY38)
.build();
let functools_module_name = ModuleName::new_static("functools").unwrap();
@ -842,8 +838,8 @@ mod tests {
};
let TestCase { db, stdlib, .. } = TestCaseBuilder::new()
.with_custom_typeshed(TYPESHED)
.with_target_version(PythonVersion::PY38)
.with_mocked_typeshed(TYPESHED)
.with_python_version(PythonVersion::PY38)
.build();
let existing_modules = create_module_names(&["asyncio", "functools", "xml.etree"]);
@ -887,8 +883,8 @@ mod tests {
};
let TestCase { db, .. } = TestCaseBuilder::new()
.with_custom_typeshed(TYPESHED)
.with_target_version(PythonVersion::PY38)
.with_mocked_typeshed(TYPESHED)
.with_python_version(PythonVersion::PY38)
.build();
let nonexisting_modules = create_module_names(&[
@ -931,8 +927,8 @@ mod tests {
};
let TestCase { db, stdlib, .. } = TestCaseBuilder::new()
.with_custom_typeshed(TYPESHED)
.with_target_version(PythonVersion::PY39)
.with_mocked_typeshed(TYPESHED)
.with_python_version(PythonVersion::PY39)
.build();
let existing_modules = create_module_names(&[
@ -973,8 +969,8 @@ mod tests {
};
let TestCase { db, .. } = TestCaseBuilder::new()
.with_custom_typeshed(TYPESHED)
.with_target_version(PythonVersion::PY39)
.with_mocked_typeshed(TYPESHED)
.with_python_version(PythonVersion::PY39)
.build();
let nonexisting_modules = create_module_names(&["importlib", "xml", "xml.etree"]);
@ -997,8 +993,8 @@ mod tests {
let TestCase { db, src, .. } = TestCaseBuilder::new()
.with_src_files(SRC)
.with_custom_typeshed(TYPESHED)
.with_target_version(PythonVersion::PY38)
.with_mocked_typeshed(TYPESHED)
.with_python_version(PythonVersion::PY38)
.build();
let functools_module_name = ModuleName::new_static("functools").unwrap();
@ -1022,7 +1018,7 @@ mod tests {
fn stdlib_uses_vendored_typeshed_when_no_custom_typeshed_supplied() {
let TestCase { db, stdlib, .. } = TestCaseBuilder::new()
.with_vendored_typeshed()
.with_target_version(PythonVersion::default())
.with_python_version(PythonVersion::default())
.build();
let pydoc_data_topics_name = ModuleName::new_static("pydoc_data.topics").unwrap();
@ -1290,11 +1286,11 @@ mod tests {
Program::from_settings(
&db,
&ProgramSettings {
target_version: PythonVersion::PY38,
python_version: PythonVersion::PY38,
search_paths: SearchPathSettings {
extra_paths: vec![],
src_root: src.clone(),
custom_typeshed: Some(custom_typeshed),
typeshed: Some(custom_typeshed),
site_packages: SitePackages::Known(vec![site_packages]),
},
},
@ -1333,7 +1329,7 @@ mod tests {
fn deleting_an_unrelated_file_doesnt_change_module_resolution() {
let TestCase { mut db, src, .. } = TestCaseBuilder::new()
.with_src_files(&[("foo.py", "x = 1"), ("bar.py", "x = 2")])
.with_target_version(PythonVersion::PY38)
.with_python_version(PythonVersion::PY38)
.build();
let foo_module_name = ModuleName::new_static("foo").unwrap();
@ -1420,8 +1416,8 @@ mod tests {
site_packages,
..
} = TestCaseBuilder::new()
.with_custom_typeshed(TYPESHED)
.with_target_version(PythonVersion::PY38)
.with_mocked_typeshed(TYPESHED)
.with_python_version(PythonVersion::PY38)
.build();
let functools_module_name = ModuleName::new_static("functools").unwrap();
@ -1468,8 +1464,8 @@ mod tests {
src,
..
} = TestCaseBuilder::new()
.with_custom_typeshed(TYPESHED)
.with_target_version(PythonVersion::PY38)
.with_mocked_typeshed(TYPESHED)
.with_python_version(PythonVersion::PY38)
.build();
let functools_module_name = ModuleName::new_static("functools").unwrap();
@ -1508,8 +1504,8 @@ mod tests {
..
} = TestCaseBuilder::new()
.with_src_files(SRC)
.with_custom_typeshed(TYPESHED)
.with_target_version(PythonVersion::PY38)
.with_mocked_typeshed(TYPESHED)
.with_python_version(PythonVersion::PY38)
.build();
let functools_module_name = ModuleName::new_static("functools").unwrap();
@ -1795,11 +1791,11 @@ not_a_directory
Program::from_settings(
&db,
&ProgramSettings {
target_version: PythonVersion::default(),
python_version: PythonVersion::default(),
search_paths: SearchPathSettings {
extra_paths: vec![],
src_root: SystemPathBuf::from("/src"),
custom_typeshed: None,
typeshed: None,
site_packages: SitePackages::Known(vec![
venv_site_packages,
system_site_packages,

View file

@ -18,7 +18,7 @@ pub(crate) struct TestCase<T> {
// so this is a single directory instead of a `Vec` of directories,
// like it is in `ruff_db::Program`.
pub(crate) site_packages: SystemPathBuf,
pub(crate) target_version: PythonVersion,
pub(crate) python_version: PythonVersion,
}
/// A `(file_name, file_contents)` tuple
@ -67,7 +67,7 @@ pub(crate) struct UnspecifiedTypeshed;
/// ```rs
/// let test_case = TestCaseBuilder::new()
/// .with_src_files(...)
/// .with_target_version(...)
/// .with_python_version(...)
/// .build();
/// ```
///
@ -85,13 +85,13 @@ pub(crate) struct UnspecifiedTypeshed;
/// const TYPESHED = MockedTypeshed { ... };
///
/// let test_case = resolver_test_case()
/// .with_custom_typeshed(TYPESHED)
/// .with_target_version(...)
/// .with_mocked_typeshed(TYPESHED)
/// .with_python_version(...)
/// .build();
///
/// let test_case2 = resolver_test_case()
/// .with_vendored_typeshed()
/// .with_target_version(...)
/// .with_python_version(...)
/// .build();
/// ```
///
@ -100,7 +100,7 @@ pub(crate) struct UnspecifiedTypeshed;
/// to `()`.
pub(crate) struct TestCaseBuilder<T> {
typeshed_option: T,
target_version: PythonVersion,
python_version: PythonVersion,
first_party_files: Vec<FileSpec>,
site_packages_files: Vec<FileSpec>,
}
@ -118,9 +118,9 @@ impl<T> TestCaseBuilder<T> {
self
}
/// Specify the target Python version the module resolver should assume
pub(crate) fn with_target_version(mut self, target_version: PythonVersion) -> Self {
self.target_version = target_version;
/// Specify the Python version the module resolver should assume
pub(crate) fn with_python_version(mut self, python_version: PythonVersion) -> Self {
self.python_version = python_version;
self
}
@ -146,7 +146,7 @@ impl TestCaseBuilder<UnspecifiedTypeshed> {
pub(crate) fn new() -> TestCaseBuilder<UnspecifiedTypeshed> {
Self {
typeshed_option: UnspecifiedTypeshed,
target_version: PythonVersion::default(),
python_version: PythonVersion::default(),
first_party_files: vec![],
site_packages_files: vec![],
}
@ -156,33 +156,33 @@ impl TestCaseBuilder<UnspecifiedTypeshed> {
pub(crate) fn with_vendored_typeshed(self) -> TestCaseBuilder<VendoredTypeshed> {
let TestCaseBuilder {
typeshed_option: _,
target_version,
python_version,
first_party_files,
site_packages_files,
} = self;
TestCaseBuilder {
typeshed_option: VendoredTypeshed,
target_version,
python_version,
first_party_files,
site_packages_files,
}
}
/// Use a mock typeshed directory for this test case
pub(crate) fn with_custom_typeshed(
pub(crate) fn with_mocked_typeshed(
self,
typeshed: MockedTypeshed,
) -> TestCaseBuilder<MockedTypeshed> {
let TestCaseBuilder {
typeshed_option: _,
target_version,
python_version,
first_party_files,
site_packages_files,
} = self;
TestCaseBuilder {
typeshed_option: typeshed,
target_version,
python_version,
first_party_files,
site_packages_files,
}
@ -194,15 +194,15 @@ impl TestCaseBuilder<UnspecifiedTypeshed> {
src,
stdlib: _,
site_packages,
target_version,
} = self.with_custom_typeshed(MockedTypeshed::default()).build();
python_version,
} = self.with_mocked_typeshed(MockedTypeshed::default()).build();
TestCase {
db,
src,
stdlib: (),
site_packages,
target_version,
python_version,
}
}
}
@ -211,7 +211,7 @@ impl TestCaseBuilder<MockedTypeshed> {
pub(crate) fn build(self) -> TestCase<SystemPathBuf> {
let TestCaseBuilder {
typeshed_option,
target_version,
python_version,
first_party_files,
site_packages_files,
} = self;
@ -226,11 +226,11 @@ impl TestCaseBuilder<MockedTypeshed> {
Program::from_settings(
&db,
&ProgramSettings {
target_version,
python_version,
search_paths: SearchPathSettings {
extra_paths: vec![],
src_root: src.clone(),
custom_typeshed: Some(typeshed.clone()),
typeshed: Some(typeshed.clone()),
site_packages: SitePackages::Known(vec![site_packages.clone()]),
},
},
@ -242,7 +242,7 @@ impl TestCaseBuilder<MockedTypeshed> {
src,
stdlib: typeshed.join("stdlib"),
site_packages,
target_version,
python_version,
}
}
@ -268,7 +268,7 @@ impl TestCaseBuilder<VendoredTypeshed> {
pub(crate) fn build(self) -> TestCase<VendoredPathBuf> {
let TestCaseBuilder {
typeshed_option: VendoredTypeshed,
target_version,
python_version,
first_party_files,
site_packages_files,
} = self;
@ -282,7 +282,7 @@ impl TestCaseBuilder<VendoredTypeshed> {
Program::from_settings(
&db,
&ProgramSettings {
target_version,
python_version,
search_paths: SearchPathSettings {
site_packages: SitePackages::Known(vec![site_packages.clone()]),
..SearchPathSettings::new(src.clone())
@ -296,7 +296,7 @@ impl TestCaseBuilder<VendoredTypeshed> {
src,
stdlib: VendoredPathBuf::from("stdlib"),
site_packages,
target_version,
python_version,
}
}
}

View file

@ -112,10 +112,10 @@ impl TypeshedVersions {
pub(in crate::module_resolver) fn query_module(
&self,
module: &ModuleName,
target_version: PythonVersion,
python_version: PythonVersion,
) -> TypeshedVersionsQueryResult {
if let Some(range) = self.exact(module) {
if range.contains(target_version) {
if range.contains(python_version) {
TypeshedVersionsQueryResult::Exists
} else {
TypeshedVersionsQueryResult::DoesNotExist
@ -125,7 +125,7 @@ impl TypeshedVersions {
while let Some(module_to_try) = module {
if let Some(range) = self.exact(&module_to_try) {
return {
if range.contains(target_version) {
if range.contains(python_version) {
TypeshedVersionsQueryResult::MaybeExists
} else {
TypeshedVersionsQueryResult::DoesNotExist

View file

@ -10,7 +10,7 @@ use crate::Db;
#[salsa::input(singleton)]
pub struct Program {
pub target_version: PythonVersion,
pub python_version: PythonVersion,
#[return_ref]
pub(crate) search_paths: SearchPaths,
@ -19,16 +19,16 @@ pub struct Program {
impl Program {
pub fn from_settings(db: &dyn Db, settings: &ProgramSettings) -> anyhow::Result<Self> {
let ProgramSettings {
target_version,
python_version,
search_paths,
} = settings;
tracing::info!("Target version: Python {target_version}");
tracing::info!("Python version: Python {python_version}");
let search_paths = SearchPaths::from_settings(db, search_paths)
.with_context(|| "Invalid search path settings")?;
Ok(Program::builder(settings.target_version, search_paths)
Ok(Program::builder(settings.python_version, search_paths)
.durability(Durability::HIGH)
.new(db))
}
@ -56,7 +56,7 @@ impl Program {
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct ProgramSettings {
pub target_version: PythonVersion,
pub python_version: PythonVersion,
pub search_paths: SearchPathSettings,
}
@ -75,7 +75,7 @@ pub struct SearchPathSettings {
/// Optional path to a "custom typeshed" directory on disk for us to use for standard-library types.
/// If this is not provided, we will fallback to our vendored typeshed stubs for the stdlib,
/// bundled as a zip file in the binary
pub custom_typeshed: Option<SystemPathBuf>,
pub typeshed: Option<SystemPathBuf>,
/// The path to the user's `site-packages` directory, where third-party packages from ``PyPI`` are installed.
pub site_packages: SitePackages,
@ -86,7 +86,7 @@ impl SearchPathSettings {
Self {
src_root,
extra_paths: vec![],
custom_typeshed: None,
typeshed: None,
site_packages: SitePackages::Known(vec![]),
}
}

View file

@ -321,7 +321,7 @@ fn site_packages_directory_from_sys_prefix(
// the parsed version
//
// Note: the `python3.x` part of the `site-packages` path can't be computed from
// the `--target-version` the user has passed, as they might be running Python 3.12 locally
// the `--python-version` the user has passed, as they might be running Python 3.12 locally
// even if they've requested that we type check their code "as if" they're running 3.8.
for entry_result in system
.read_directory(&sys_prefix_path.join("lib"))

View file

@ -1344,10 +1344,10 @@ impl<'db> Type<'db> {
Type::Instance(InstanceType { class }) => {
let ty = match (class.known(db), name) {
(Some(KnownClass::VersionInfo), "major") => {
Type::IntLiteral(Program::get(db).target_version(db).major.into())
Type::IntLiteral(Program::get(db).python_version(db).major.into())
}
(Some(KnownClass::VersionInfo), "minor") => {
Type::IntLiteral(Program::get(db).target_version(db).minor.into())
Type::IntLiteral(Program::get(db).python_version(db).minor.into())
}
// TODO MRO? get_own_instance_member, get_instance_member
_ => todo_type!("instance attributes"),
@ -1799,7 +1799,7 @@ impl<'db> Type<'db> {
/// This is not exactly the type that `sys.version_info` has at runtime,
/// but it's a useful fallback for us in order to infer `Literal` types from `sys.version_info` comparisons.
fn version_info_tuple(db: &'db dyn Db) -> Self {
let target_version = Program::get(db).target_version(db);
let python_version = Program::get(db).python_version(db);
let int_instance_ty = KnownClass::Int.to_instance(db);
// TODO: just grab this type from typeshed (it's a `sys._ReleaseLevel` type alias there)
@ -1817,8 +1817,8 @@ impl<'db> Type<'db> {
};
let version_info_elements = &[
Type::IntLiteral(target_version.major.into()),
Type::IntLiteral(target_version.minor.into()),
Type::IntLiteral(python_version.major.into()),
Type::IntLiteral(python_version.minor.into()),
int_instance_ty,
release_level_ty,
int_instance_ty,
@ -2035,7 +2035,7 @@ impl<'db> KnownClass {
CoreStdlibModule::Typing
}
Self::NoDefaultType => {
let python_version = Program::get(db).target_version(db);
let python_version = Program::get(db).python_version(db);
// typing_extensions has a 3.13+ re-export for the `typing.NoDefault`
// singleton, but not for `typing._NoDefaultType`. So we need to switch