Skip to content

Commit 17245b2

Browse files
committed
WIP: resolve review comment
1 parent 9755445 commit 17245b2

File tree

4 files changed

+228
-90
lines changed

4 files changed

+228
-90
lines changed

crates/red_knot_python_semantic/resources/mdtest/import/conventions.md

Lines changed: 67 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ from math import Iterable
4444
reveal_type(Iterable) # revealed: Unknown
4545
```
4646

47-
## Explicitly re-exported symbols in stub files
47+
## Re-exported symbols in stub files
4848

4949
When a symbol is re-exported, imporing it should not raise an error. This tests both `import ...`
5050
and `from ... import ...` forms.
@@ -248,10 +248,10 @@ from c import Foo
248248
class Foo: ...
249249
```
250250

251-
## Implicit re-exports in `__init__.py`
251+
## Non-exports in `__init__.py`
252252

253253
Red knot does not special case `__init__.py` files, so if a symbol is imported in `__init__.py`
254-
without an explicit re-export, it should raise an error.
254+
which is not re-exported, it should raise an error.
255255

256256
TODO: When we support rule selection, the `implicit-reexport` rule should be disabled by default and
257257
this test case should be updated to explicitly enable it.
@@ -289,9 +289,9 @@ class Foo: ...
289289
```py
290290
```
291291

292-
## Explicit re-exports in `__init__.py`
292+
## Re-exports in `__init__.py`
293293

294-
But, if they're explicitly re-exported, it should not raise an error.
294+
But, if they're re-exported, it should not raise an error.
295295

296296
TODO: When we support rule selection, the `implicit-reexport` rule should be disabled by default and
297297
this test case should be updated to explicitly enable it.
@@ -364,3 +364,65 @@ class Foo: ...
364364

365365
```pyi
366366
```
367+
368+
## Conditional re-export in stub file
369+
370+
The following scenarios are when a re-export happens conditionally in a stub file.
371+
372+
### Global import
373+
374+
```toml
375+
log = 'salsa=warn,red_knot=debug'
376+
```
377+
378+
```py
379+
from a import Foo
380+
```
381+
382+
`a.pyi`:
383+
384+
```pyi
385+
def coinflip() -> bool:
386+
return True
387+
388+
if coinflip():
389+
class Foo: ...
390+
else:
391+
from b import Foo
392+
393+
```
394+
395+
`b.pyi`:
396+
397+
```pyi
398+
class Foo: ...
399+
```
400+
401+
### Both branch is an import
402+
403+
Here, both the branches of the condition are import statements where one of them re-exports while
404+
the other does not.
405+
406+
```py
407+
# error: "Member `Foo` of module `a` is possibly unbound"
408+
from a import Foo
409+
410+
reveal_type(Foo) # revealed: Literal[Foo]
411+
```
412+
413+
`a.pyi`:
414+
415+
```pyi
416+
def coinflip() -> bool: ...
417+
418+
if coinflip():
419+
from b import Foo
420+
else:
421+
from b import Foo as Foo
422+
```
423+
424+
`b.pyi`:
425+
426+
```pyi
427+
class Foo: ...
428+
```

crates/red_knot_python_semantic/src/symbol.rs

Lines changed: 76 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -18,24 +18,68 @@ impl Boundness {
1818
}
1919
}
2020

21+
/// Indicates whether a symbol is re-exported using the [import conventions].
22+
///
23+
/// [import conventions]: https://typing.readthedocs.io/en/latest/spec/distributing.html#import-conventions
2124
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2225
pub(crate) enum ReExport {
23-
Implicit,
24-
Explicit,
25-
None,
26+
/// Symbol is either defined by an import statement and is re-exported, or it could be defined
27+
/// by any other statement or expression.
28+
///
29+
/// For example, in the following code:
30+
/// ```py
31+
/// import foo as foo
32+
/// from foo import Bar as Bar
33+
///
34+
/// baz = 1
35+
/// ```
36+
///
37+
/// All the symbols (`foo`, `Bar`, and `baz`) are re-exported.
38+
Yes,
39+
40+
/// Symbol is defined by an import statement and is not re-exported.
41+
///
42+
/// For example, in the following code:
43+
/// ```py
44+
/// import foo
45+
/// from foo import Bar
46+
/// ```
47+
///
48+
/// Both `foo` (module) and `Bar` are not re-exported.
49+
No,
50+
51+
/// Symbol is maybe re-exported.
52+
///
53+
/// For example, in the following code:
54+
/// ```py
55+
/// if flag:
56+
/// import foo
57+
/// else:
58+
/// import foo as foo
59+
/// ```
60+
///
61+
/// The `foo` symbol is maybe re-exported, depending on the value of `flag`.
62+
///
63+
/// Or, in the following code:
64+
/// ```py
65+
/// import foo
66+
///
67+
/// if flag:
68+
/// foo = 1
69+
/// ```
70+
///
71+
/// The `foo` symbol is maybe re-exported if the truthiness of `flag` is ambiguous.
72+
Maybe,
2673
}
2774

2875
impl ReExport {
29-
pub(crate) const fn is_implicit(self) -> bool {
30-
matches!(self, ReExport::Implicit)
31-
}
32-
3376
pub(crate) fn or(self, other: ReExport) -> ReExport {
34-
match (self, other) {
35-
(ReExport::Implicit, _) | (_, ReExport::Implicit) => ReExport::Implicit,
36-
(ReExport::Explicit, ReExport::Explicit) => ReExport::Explicit,
37-
(non_none, ReExport::None) | (ReExport::None, non_none) => non_none,
38-
}
77+
let result = match (self, other) {
78+
(ReExport::Yes, ReExport::Yes) => ReExport::Yes,
79+
(ReExport::No, ReExport::No) => ReExport::No,
80+
_ => ReExport::Maybe,
81+
};
82+
result
3983
}
4084
}
4185

@@ -139,53 +183,53 @@ mod tests {
139183
);
140184
assert_eq!(
141185
Symbol::Unbound
142-
.or_fall_back_to(&db, &Symbol::Type(ty1, ReExport::None, PossiblyUnbound)),
143-
Symbol::Type(ty1, ReExport::None, PossiblyUnbound)
186+
.or_fall_back_to(&db, &Symbol::Type(ty1, ReExport::Yes, PossiblyUnbound)),
187+
Symbol::Type(ty1, ReExport::Yes, PossiblyUnbound)
144188
);
145189
assert_eq!(
146-
Symbol::Unbound.or_fall_back_to(&db, &Symbol::Type(ty1, ReExport::None, Bound)),
147-
Symbol::Type(ty1, ReExport::None, Bound)
190+
Symbol::Unbound.or_fall_back_to(&db, &Symbol::Type(ty1, ReExport::Yes, Bound)),
191+
Symbol::Type(ty1, ReExport::Yes, Bound)
148192
);
149193

150194
// Start from a possibly unbound symbol
151195
assert_eq!(
152-
Symbol::Type(ty1, ReExport::None, PossiblyUnbound)
196+
Symbol::Type(ty1, ReExport::Yes, PossiblyUnbound)
153197
.or_fall_back_to(&db, &Symbol::Unbound),
154-
Symbol::Type(ty1, ReExport::None, PossiblyUnbound)
198+
Symbol::Type(ty1, ReExport::Yes, PossiblyUnbound)
155199
);
156200
assert_eq!(
157-
Symbol::Type(ty1, ReExport::None, PossiblyUnbound)
158-
.or_fall_back_to(&db, &Symbol::Type(ty2, ReExport::None, PossiblyUnbound)),
201+
Symbol::Type(ty1, ReExport::Yes, PossiblyUnbound)
202+
.or_fall_back_to(&db, &Symbol::Type(ty2, ReExport::Yes, PossiblyUnbound)),
159203
Symbol::Type(
160204
UnionType::from_elements(&db, [ty2, ty1]),
161-
ReExport::None,
205+
ReExport::Yes,
162206
PossiblyUnbound
163207
)
164208
);
165209
assert_eq!(
166-
Symbol::Type(ty1, ReExport::None, PossiblyUnbound)
167-
.or_fall_back_to(&db, &Symbol::Type(ty2, ReExport::None, Bound)),
210+
Symbol::Type(ty1, ReExport::Yes, PossiblyUnbound)
211+
.or_fall_back_to(&db, &Symbol::Type(ty2, ReExport::Yes, Bound)),
168212
Symbol::Type(
169213
UnionType::from_elements(&db, [ty2, ty1]),
170-
ReExport::None,
214+
ReExport::Yes,
171215
Bound
172216
)
173217
);
174218

175219
// Start from a definitely bound symbol
176220
assert_eq!(
177-
Symbol::Type(ty1, ReExport::None, Bound).or_fall_back_to(&db, &Symbol::Unbound),
178-
Symbol::Type(ty1, ReExport::None, Bound)
221+
Symbol::Type(ty1, ReExport::Yes, Bound).or_fall_back_to(&db, &Symbol::Unbound),
222+
Symbol::Type(ty1, ReExport::Yes, Bound)
179223
);
180224
assert_eq!(
181-
Symbol::Type(ty1, ReExport::None, Bound)
182-
.or_fall_back_to(&db, &Symbol::Type(ty2, ReExport::None, PossiblyUnbound)),
183-
Symbol::Type(ty1, ReExport::None, Bound)
225+
Symbol::Type(ty1, ReExport::Yes, Bound)
226+
.or_fall_back_to(&db, &Symbol::Type(ty2, ReExport::Yes, PossiblyUnbound)),
227+
Symbol::Type(ty1, ReExport::Yes, Bound)
184228
);
185229
assert_eq!(
186-
Symbol::Type(ty1, ReExport::None, Bound)
187-
.or_fall_back_to(&db, &Symbol::Type(ty2, ReExport::None, Bound)),
188-
Symbol::Type(ty1, ReExport::None, Bound)
230+
Symbol::Type(ty1, ReExport::Yes, Bound)
231+
.or_fall_back_to(&db, &Symbol::Type(ty2, ReExport::Yes, Bound)),
232+
Symbol::Type(ty1, ReExport::Yes, Bound)
189233
);
190234
}
191235
}

0 commit comments

Comments
 (0)