Skip to content

Commit 49e63e2

Browse files
committed
[ty] Add allowed-unresolved-imports option
Add config to specify set of import paths to be resolved to Any. This config will silence import errors and replace the module with typing.Any. If the module can be found, its type information will still be replaced with typing.Any.
1 parent 4ffdaf5 commit 49e63e2

9 files changed

Lines changed: 355 additions & 10 deletions

File tree

crates/ty/docs/configuration.md

Lines changed: 82 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/ty_project/src/metadata/options.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1311,6 +1311,31 @@ pub struct AnalysisOptions {
13111311
"#
13121312
)]
13131313
pub allowed_unresolved_imports: Option<Vec<RangedValue<String>>>,
1314+
1315+
/// A list of module glob patterns whose imports should be replaced with `typing.Any`.
1316+
///
1317+
/// Unlike `allowed-unresolved-imports`, this setting replaces the module's type information
1318+
/// with `typing.Any` even if the module can be resolved. Import diagnostics are
1319+
/// unconditionally suppressed for matching modules.
1320+
///
1321+
/// - Prefix a pattern with `!` to exclude matching modules
1322+
///
1323+
/// When multiple patterns match, later entries take precedence.
1324+
///
1325+
/// Glob patterns can be used in combinations with each other. For example, to suppress errors for
1326+
/// any module where the first component contains the substring `test`, use `*test*.**`.
1327+
///
1328+
/// When multiple patterns match, later entries take precedence.
1329+
#[serde(skip_serializing_if = "Option::is_none")]
1330+
#[option(
1331+
default = r#"[]"#,
1332+
value_type = "list[str]",
1333+
example = r#"
1334+
# Replace all pandas and numpy imports with Any
1335+
replace-imports-with-any = ["pandas.**", "numpy.**"]
1336+
"#
1337+
)]
1338+
pub replace_imports_with_any: Option<Vec<RangedValue<String>>>,
13141339
}
13151340

13161341
impl AnalysisOptions {
@@ -1322,11 +1347,13 @@ impl AnalysisOptions {
13221347
let Self {
13231348
respect_type_ignore_comments,
13241349
allowed_unresolved_imports,
1350+
replace_imports_with_any,
13251351
} = self;
13261352

13271353
let AnalysisSettings {
13281354
respect_type_ignore_comments: respect_type_ignore_default,
13291355
allowed_unresolved_imports: allowed_unresolved_imports_default,
1356+
replace_imports_with_any: replace_imports_with_any_default,
13301357
} = AnalysisSettings::default();
13311358

13321359
let allowed_unresolved_imports =
@@ -1340,10 +1367,22 @@ impl AnalysisOptions {
13401367
allowed_unresolved_imports_default
13411368
};
13421369

1370+
let replace_imports_with_any =
1371+
if let Some(replace_imports_with_any) = replace_imports_with_any {
1372+
build_module_glob_set(db, replace_imports_with_any, "replace_imports_with_any")
1373+
.unwrap_or_else(|error| {
1374+
diagnostics.push(*error);
1375+
ModuleGlobSet::empty()
1376+
})
1377+
} else {
1378+
replace_imports_with_any_default
1379+
};
1380+
13431381
AnalysisSettings {
13441382
respect_type_ignore_comments: respect_type_ignore_comments
13451383
.unwrap_or(respect_type_ignore_default),
13461384
allowed_unresolved_imports,
1385+
replace_imports_with_any,
13471386
}
13481387
}
13491388
}
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
# Replace imports with Any
2+
3+
When a module cannot be found and matches the pattern, the import is replaced with `Any` and no
4+
diagnostic is emitted.
5+
6+
The syntax uses globe patterns. See `allowed-unresolved-imports` for syntax.
7+
8+
## Unresolvable module is replaced with Any
9+
10+
```toml
11+
[analysis]
12+
replace-imports-with-any = ["foo.**"]
13+
```
14+
15+
```py
16+
import foo
17+
from foo import bar
18+
from foo.sub import baz
19+
20+
reveal_type(foo) # revealed: Any
21+
reveal_type(bar) # revealed: Any
22+
reveal_type(baz) # revealed: Any
23+
```
24+
25+
## Resolvable module is also replaced with Any
26+
27+
Even when the module exists and has type information, its types are replaced with `Any`.
28+
29+
```toml
30+
[analysis]
31+
replace-imports-with-any = ["pkg.**"]
32+
```
33+
34+
`pkg/__init__.py`:
35+
36+
```py
37+
x: int = 1
38+
```
39+
40+
`pkg/sub.py`:
41+
42+
```py
43+
y: str = "hello"
44+
```
45+
46+
`main.py`:
47+
48+
```py
49+
from pkg import x
50+
from pkg.sub import y
51+
import pkg
52+
53+
reveal_type(x) # revealed: Any
54+
reveal_type(y) # revealed: Any
55+
reveal_type(pkg) # revealed: Any
56+
```
57+
58+
## Globe Pattern
59+
60+
```toml
61+
[analysis]
62+
replace-imports-with-any = ["aws*.**"]
63+
```
64+
65+
```py
66+
import aws
67+
import awscli
68+
import awscli.customizations
69+
70+
reveal_type(aws) # revealed: Any
71+
reveal_type(awscli) # revealed: Any
72+
reveal_type(awscli.customizations) # revealed: Any
73+
```
74+
75+
## Negative pattern
76+
77+
```toml
78+
[analysis]
79+
replace-imports-with-any = ["pkg.**", "!pkg.keep"]
80+
```
81+
82+
`pkg/__init__.py`:
83+
84+
```py
85+
```
86+
87+
`pkg/keep.py`:
88+
89+
```py
90+
value: int = 1
91+
```
92+
93+
`main.py`:
94+
95+
```py
96+
from pkg.keep import value
97+
from pkg.skip import other
98+
99+
reveal_type(value) # revealed: int
100+
reveal_type(other) # revealed: Any
101+
```
102+
103+
## Relative Imports
104+
105+
The match happens on the module absolute path. If the absolute path that a relative import is
106+
pointing to matches the condition it will be applied.
107+
108+
```toml
109+
[analysis]
110+
replace-imports-with-any = ["**.foo", "bar"]
111+
```
112+
113+
`package/__init__.py`:
114+
115+
```py
116+
```
117+
118+
`package/foo.py`:
119+
120+
```py
121+
val = 1
122+
```
123+
124+
`package/main.py`:
125+
126+
```py
127+
from .foo import val
128+
129+
# .bar would not match "bar" rule because the absolute import is package.bar
130+
from .bar import val2 # error: [unresolved-import]
131+
132+
reveal_type(val) # revealed: Any
133+
```
134+
135+
## Non-matching modules are unaffected
136+
137+
```toml
138+
[analysis]
139+
replace-imports-with-any = ["skipped.**"]
140+
```
141+
142+
`real_module.py`:
143+
144+
```py
145+
value: int = 42
146+
```
147+
148+
`main.py`:
149+
150+
```py
151+
from real_module import value
152+
153+
reveal_type(value) # revealed: int
154+
```

crates/ty_python_semantic/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,13 +90,16 @@ pub struct AnalysisSettings {
9090
pub respect_type_ignore_comments: bool,
9191

9292
pub allowed_unresolved_imports: ModuleGlobSet,
93+
94+
pub replace_imports_with_any: ModuleGlobSet,
9395
}
9496

9597
impl Default for AnalysisSettings {
9698
fn default() -> Self {
9799
Self {
98100
respect_type_ignore_comments: true,
99101
allowed_unresolved_imports: ModuleGlobSet::empty(),
102+
replace_imports_with_any: ModuleGlobSet::empty(),
100103
}
101104
}
102105
}

0 commit comments

Comments
 (0)