Skip to content

Commit 7855f03

Browse files
authored
[red-knot] Support custom typeshed Markdown tests (#15683)
## Summary - Add feature to specify a custom typeshed from within Markdown-based tests - Port "builtins" unit tests from `infer.rs` to Markdown tests, part of #13696 ## Test Plan - Tests for the custom typeshed feature - New Markdown tests for deleted Rust unit tests
1 parent 84301a7 commit 7855f03

File tree

13 files changed

+275
-150
lines changed

13 files changed

+275
-150
lines changed

crates/red_knot/tests/file_watching.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ where
322322
.search_paths
323323
.extra_paths
324324
.iter()
325-
.chain(program_settings.search_paths.typeshed.as_ref())
325+
.chain(program_settings.search_paths.custom_typeshed.as_ref())
326326
{
327327
std::fs::create_dir_all(path.as_std_path())
328328
.with_context(|| format!("Failed to create search path `{path}`"))?;

crates/red_knot_project/src/metadata/options.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ impl Options {
9292
.map(|path| path.absolute(project_root, system))
9393
.collect(),
9494
src_roots,
95-
typeshed: typeshed.map(|path| path.absolute(project_root, system)),
95+
custom_typeshed: typeshed.map(|path| path.absolute(project_root, system)),
9696
site_packages: python
9797
.map(|venv_path| SitePackages::Derived {
9898
venv_path: venv_path.absolute(project_root, system),
Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,70 @@
1-
# Importing builtin module
1+
# Builtins
2+
3+
## Importing builtin module
4+
5+
Builtin symbols can be explicitly imported:
26

37
```py
48
import builtins
59

6-
x = builtins.chr
7-
reveal_type(x) # revealed: Literal[chr]
10+
reveal_type(builtins.chr) # revealed: Literal[chr]
11+
```
12+
13+
## Implicit use of builtin
14+
15+
Or used implicitly:
16+
17+
```py
18+
reveal_type(chr) # revealed: Literal[chr]
19+
reveal_type(str) # revealed: Literal[str]
20+
```
21+
22+
## Builtin symbol from custom typeshed
23+
24+
If we specify a custom typeshed, we can use the builtin symbol from it, and no longer access the
25+
builtins from the "actual" vendored typeshed:
26+
27+
```toml
28+
[environment]
29+
typeshed = "/typeshed"
30+
```
31+
32+
```pyi path=/typeshed/stdlib/builtins.pyi
33+
class Custom: ...
34+
35+
custom_builtin: Custom
36+
```
37+
38+
```pyi path=/typeshed/stdlib/typing_extensions.pyi
39+
def reveal_type(obj, /): ...
40+
```
41+
42+
```py
43+
reveal_type(custom_builtin) # revealed: Custom
44+
45+
# error: [unresolved-reference]
46+
reveal_type(str) # revealed: Unknown
47+
```
48+
49+
## Unknown builtin (later defined)
50+
51+
`foo` has a type of `Unknown` in this example, as it relies on `bar` which has not been defined at
52+
that point:
53+
54+
```toml
55+
[environment]
56+
typeshed = "/typeshed"
57+
```
58+
59+
```pyi path=/typeshed/stdlib/builtins.pyi
60+
foo = bar
61+
bar = 1
62+
```
63+
64+
```pyi path=/typeshed/stdlib/typing_extensions.pyi
65+
def reveal_type(obj, /): ...
66+
```
67+
68+
```py
69+
reveal_type(foo) # revealed: Unknown
870
```
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# Custom typeshed
2+
3+
The `environment.typeshed` configuration option can be used to specify a custom typeshed directory
4+
for Markdown-based tests. Custom typeshed stubs can then be placed in the specified directory using
5+
fenced code blocks with language `pyi`, and will be used instead of the vendored copy of typeshed.
6+
7+
A fenced code block with language `text` can be used to provide a `stdlib/VERSIONS` file in the
8+
custom typeshed root. If no such file is created explicitly, it will be automatically created with
9+
entries enabling all specified `<typeshed-root>/stdlib` files for all supported Python versions.
10+
11+
## Basic example (auto-generated `VERSIONS` file)
12+
13+
First, we specify `/typeshed` as the custom typeshed directory:
14+
15+
```toml
16+
[environment]
17+
typeshed = "/typeshed"
18+
```
19+
20+
We can then place custom stub files in `/typeshed/stdlib`, for example:
21+
22+
```pyi path=/typeshed/stdlib/builtins.pyi
23+
class BuiltinClass: ...
24+
25+
builtin_symbol: BuiltinClass
26+
```
27+
28+
```pyi path=/typeshed/stdlib/sys/__init__.pyi
29+
version = "my custom Python"
30+
```
31+
32+
And finally write a normal Python code block that makes use of the custom stubs:
33+
34+
```py
35+
b: BuiltinClass = builtin_symbol
36+
37+
class OtherClass: ...
38+
39+
o: OtherClass = builtin_symbol # error: [invalid-assignment]
40+
41+
# Make sure that 'sys' has a proper entry in the auto-generated 'VERSIONS' file
42+
import sys
43+
```
44+
45+
## Custom `VERSIONS` file
46+
47+
If we want to specify a custom `VERSIONS` file, we can do so by creating a fenced code block with
48+
language `text`. In the following test, we set the Python version to `3.10` and then make sure that
49+
we can *not* import `new_module` with a version requirement of `3.11-`:
50+
51+
```toml
52+
[environment]
53+
python-version = "3.10"
54+
typeshed = "/typeshed"
55+
```
56+
57+
```pyi path=/typeshed/stdlib/old_module.pyi
58+
class OldClass: ...
59+
```
60+
61+
```pyi path=/typeshed/stdlib/new_module.pyi
62+
class NewClass: ...
63+
```
64+
65+
```text path=/typeshed/stdlib/VERSIONS
66+
old_module: 3.0-
67+
new_module: 3.11-
68+
```
69+
70+
```py
71+
from old_module import OldClass
72+
73+
# error: [unresolved-import] "Cannot resolve import `new_module`"
74+
from new_module import NewClass
75+
```
76+
77+
## Using `reveal_type` with a custom typeshed
78+
79+
When providing a custom typeshed directory, basic things like `reveal_type` will stop working
80+
because we rely on being able to import it from `typing_extensions`. The actual definition of
81+
`reveal_type` in typeshed is slightly involved (depends on generics, `TypeVar`, etc.), but a very
82+
simple untyped definition is enough to make `reveal_type` work in tests:
83+
84+
```toml
85+
[environment]
86+
typeshed = "/typeshed"
87+
```
88+
89+
```pyi path=/typeshed/stdlib/typing_extensions.pyi
90+
def reveal_type(obj, /): ...
91+
```
92+
93+
```py
94+
reveal_type(()) # revealed: tuple[()]
95+
```

crates/red_knot_python_semantic/src/db.rs

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ pub(crate) mod tests {
136136
/// Target Python platform
137137
python_platform: PythonPlatform,
138138
/// Path to a custom typeshed directory
139-
custom_typeshed: Option<SystemPathBuf>,
139+
typeshed: Option<SystemPathBuf>,
140140
/// Path and content pairs for files that should be present
141141
files: Vec<(&'a str, &'a str)>,
142142
}
@@ -146,7 +146,7 @@ pub(crate) mod tests {
146146
Self {
147147
python_version: PythonVersion::default(),
148148
python_platform: PythonPlatform::default(),
149-
custom_typeshed: None,
149+
typeshed: None,
150150
files: vec![],
151151
}
152152
}
@@ -156,11 +156,6 @@ pub(crate) mod tests {
156156
self
157157
}
158158

159-
pub(crate) fn with_custom_typeshed(mut self, path: &str) -> Self {
160-
self.custom_typeshed = Some(SystemPathBuf::from(path));
161-
self
162-
}
163-
164159
pub(crate) fn with_file(mut self, path: &'a str, content: &'a str) -> Self {
165160
self.files.push((path, content));
166161
self
@@ -176,7 +171,7 @@ pub(crate) mod tests {
176171
.context("Failed to write test files")?;
177172

178173
let mut search_paths = SearchPathSettings::new(vec![src_root]);
179-
search_paths.typeshed = self.custom_typeshed;
174+
search_paths.custom_typeshed = self.typeshed;
180175

181176
Program::from_settings(
182177
&db,

crates/red_knot_python_semantic/src/module_resolver/resolver.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ impl SearchPaths {
169169
let SearchPathSettings {
170170
extra_paths,
171171
src_roots,
172-
typeshed,
172+
custom_typeshed: typeshed,
173173
site_packages: site_packages_paths,
174174
} = settings;
175175

@@ -1308,7 +1308,7 @@ mod tests {
13081308
search_paths: SearchPathSettings {
13091309
extra_paths: vec![],
13101310
src_roots: vec![src.clone()],
1311-
typeshed: Some(custom_typeshed),
1311+
custom_typeshed: Some(custom_typeshed),
13121312
site_packages: SitePackages::Known(vec![site_packages]),
13131313
},
13141314
},
@@ -1814,7 +1814,7 @@ not_a_directory
18141814
search_paths: SearchPathSettings {
18151815
extra_paths: vec![],
18161816
src_roots: vec![SystemPathBuf::from("/src")],
1817-
typeshed: None,
1817+
custom_typeshed: None,
18181818
site_packages: SitePackages::Known(vec![
18191819
venv_site_packages,
18201820
system_site_packages,

crates/red_knot_python_semantic/src/module_resolver/testing.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ pub(crate) struct UnspecifiedTypeshed;
7373
///
7474
/// For tests checking that standard-library module resolution is working
7575
/// correctly, you should usually create a [`MockedTypeshed`] instance
76-
/// and pass it to the [`TestCaseBuilder::with_custom_typeshed`] method.
76+
/// and pass it to the [`TestCaseBuilder::with_mocked_typeshed`] method.
7777
/// If you need to check something that involves the vendored typeshed stubs
7878
/// we include as part of the binary, you can instead use the
7979
/// [`TestCaseBuilder::with_vendored_typeshed`] method.
@@ -238,7 +238,7 @@ impl TestCaseBuilder<MockedTypeshed> {
238238
search_paths: SearchPathSettings {
239239
extra_paths: vec![],
240240
src_roots: vec![src.clone()],
241-
typeshed: Some(typeshed.clone()),
241+
custom_typeshed: Some(typeshed.clone()),
242242
site_packages: SitePackages::Known(vec![site_packages.clone()]),
243243
},
244244
},

crates/red_knot_python_semantic/src/program.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ pub struct SearchPathSettings {
108108
/// Optional path to a "custom typeshed" directory on disk for us to use for standard-library types.
109109
/// If this is not provided, we will fallback to our vendored typeshed stubs for the stdlib,
110110
/// bundled as a zip file in the binary
111-
pub typeshed: Option<SystemPathBuf>,
111+
pub custom_typeshed: Option<SystemPathBuf>,
112112

113113
/// The path to the user's `site-packages` directory, where third-party packages from ``PyPI`` are installed.
114114
pub site_packages: SitePackages,
@@ -119,7 +119,7 @@ impl SearchPathSettings {
119119
Self {
120120
src_roots,
121121
extra_paths: vec![],
122-
typeshed: None,
122+
custom_typeshed: None,
123123
site_packages: SitePackages::Known(vec![]),
124124
}
125125
}

crates/red_knot_python_semantic/src/types/infer.rs

Lines changed: 1 addition & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -6003,14 +6003,13 @@ fn perform_membership_test_comparison<'db>(
60036003

60046004
#[cfg(test)]
60056005
mod tests {
6006-
use crate::db::tests::{setup_db, TestDb, TestDbBuilder};
6006+
use crate::db::tests::{setup_db, TestDb};
60076007
use crate::semantic_index::definition::Definition;
60086008
use crate::semantic_index::symbol::FileScopeId;
60096009
use crate::semantic_index::{global_scope, semantic_index, symbol_table, use_def_map};
60106010
use crate::types::check_types;
60116011
use crate::{HasType, SemanticModel};
60126012
use ruff_db::files::{system_path_to_file, File};
6013-
use ruff_db::parsed::parsed_module;
60146013
use ruff_db::system::DbWithTestSystem;
60156014
use ruff_db::testing::assert_function_query_was_not_run;
60166015

@@ -6281,56 +6280,6 @@ mod tests {
62816280
Ok(())
62826281
}
62836282

6284-
#[test]
6285-
fn builtin_symbol_vendored_stdlib() -> anyhow::Result<()> {
6286-
let mut db = setup_db();
6287-
6288-
db.write_file("/src/a.py", "c = chr")?;
6289-
6290-
assert_public_type(&db, "/src/a.py", "c", "Literal[chr]");
6291-
6292-
Ok(())
6293-
}
6294-
6295-
#[test]
6296-
fn builtin_symbol_custom_stdlib() -> anyhow::Result<()> {
6297-
let db = TestDbBuilder::new()
6298-
.with_custom_typeshed("/typeshed")
6299-
.with_file("/src/a.py", "c = copyright")
6300-
.with_file(
6301-
"/typeshed/stdlib/builtins.pyi",
6302-
"def copyright() -> None: ...",
6303-
)
6304-
.with_file("/typeshed/stdlib/VERSIONS", "builtins: 3.8-")
6305-
.build()?;
6306-
6307-
assert_public_type(&db, "/src/a.py", "c", "Literal[copyright]");
6308-
6309-
Ok(())
6310-
}
6311-
6312-
#[test]
6313-
fn unknown_builtin_later_defined() -> anyhow::Result<()> {
6314-
let db = TestDbBuilder::new()
6315-
.with_custom_typeshed("/typeshed")
6316-
.with_file("/src/a.py", "x = foo")
6317-
.with_file("/typeshed/stdlib/builtins.pyi", "foo = bar; bar = 1")
6318-
.with_file("/typeshed/stdlib/VERSIONS", "builtins: 3.8-")
6319-
.build()?;
6320-
6321-
assert_public_type(&db, "/src/a.py", "x", "Unknown");
6322-
6323-
Ok(())
6324-
}
6325-
6326-
#[test]
6327-
fn str_builtin() -> anyhow::Result<()> {
6328-
let mut db = setup_db();
6329-
db.write_file("/src/a.py", "x = str")?;
6330-
assert_public_type(&db, "/src/a.py", "x", "Literal[str]");
6331-
Ok(())
6332-
}
6333-
63346283
#[test]
63356284
fn deferred_annotation_builtin() -> anyhow::Result<()> {
63366285
let mut db = setup_db();

0 commit comments

Comments
 (0)