2
2
3
3
## Basic
4
4
5
- The "public type" of a symbol refers to the type that is inferred for a symbol from another scope.
6
- Since it is not generally possible to analyze the full control flow of a program, we currently make
7
- the assumption that the inner scope (such as the inner function below) could be executed at any
8
- position. The public type should therefore be the union of all possible types that the symbol could
9
- have.
5
+ The "public type" of a symbol refers to the type that is inferred for a symbol from an enclosing
6
+ scope. Since it is not generally possible to analyze the full control flow of a program, we
7
+ currently make the simplifying assumption that the inner scope (such as the ` inner ` function below)
8
+ could be executed at any position in the enclosing scope. The public type should therefore be the
9
+ union of all possible types that the symbol could have.
10
10
11
11
In the following example, depending on when ` inner() ` is called, the type of ` x ` could either be ` A `
12
12
or ` B ` :
@@ -21,10 +21,12 @@ def outer() -> None:
21
21
22
22
def inner () -> None :
23
23
reveal_type(x) # revealed: Unknown | A | B
24
+ # This call would observe `x` as `A`.
24
25
inner()
25
26
26
27
x = B()
27
28
29
+ # This call would observe `x` as `B`.
28
30
inner()
29
31
```
30
32
@@ -61,7 +63,7 @@ def outer() -> None:
61
63
inner()
62
64
63
65
if False :
64
- x = B()
66
+ x = B() # this binding of `x` is unreachable
65
67
inner()
66
68
67
69
x = C()
@@ -77,7 +79,7 @@ def outer(flag: bool) -> None:
77
79
if flag:
78
80
return
79
81
80
- x = B()
82
+ x = B() # this binding of `x` is unreachable
81
83
82
84
x = C()
83
85
inner()
@@ -108,7 +110,9 @@ def outer(flag: bool) -> None:
108
110
inner()
109
111
```
110
112
111
- The public type is available even if the end of the outer scope is unreachable:
113
+ The public type is available, even if the end of the outer scope is unreachable. This is a
114
+ regression test. A previous version of ty used the end-of-scope position to determine the public
115
+ type, which would have resulted in wrong types here:
112
116
113
117
``` py
114
118
def outer () -> None :
@@ -133,52 +137,38 @@ def outer(flag: bool) -> None:
133
137
# unreachable
134
138
135
139
inner()
136
- ```
137
-
138
- This works at arbitrary levels of nesting:
139
-
140
- ``` py
141
- def outer () -> None :
142
- x = A()
143
-
144
- def intermediate () -> None :
145
- def inner () -> None :
146
- reveal_type(x) # revealed: Unknown | A | B
147
- inner()
148
- intermediate()
149
-
150
- x = B()
151
-
152
- intermediate()
153
140
154
141
def outer (x : A) -> None :
155
142
def inner () -> None :
156
143
reveal_type(x) # revealed: A
157
144
raise
158
145
```
159
146
160
- ## Interplay with type narrowing
147
+ Arbitrary many levels of nesting are supported:
161
148
162
149
``` py
163
- class A : ...
150
+ def f0 () -> None :
151
+ x = A()
164
152
165
- def outer (x : A | None ):
166
- def inner () -> None :
167
- reveal_type(x) # revealed: A | None
168
- inner()
169
- if x is None :
170
- inner()
153
+ def f1 () -> None :
154
+ def f2 () -> None :
155
+ def f3 () -> None :
156
+ def f4 () -> None :
157
+ reveal_type(x) # revealed: Unknown | A | B
158
+ f4()
159
+ f3()
160
+ f2()
161
+ f1()
171
162
172
- def outer (x : A | None ):
173
- if x is not None :
174
- def inner () -> None :
175
- # TODO : should ideally be `A`
176
- reveal_type(x) # revealed: A | None
177
- inner()
163
+ x = B()
164
+
165
+ f1()
178
166
```
179
167
180
168
## At module level
181
169
170
+ The behavior is the same if the outer scope is the global scope of a module:
171
+
182
172
``` py
183
173
def flag () -> bool :
184
174
return True
@@ -199,51 +189,97 @@ if flag():
199
189
200
190
## Limitations
201
191
192
+ ### Type narrowing
193
+
194
+ We currently do not further analyze control flow, so we do not support cases where the inner scope
195
+ is only executed in a branch where the type of ` x ` is narrowed:
196
+
197
+ ``` py
198
+ class A : ...
199
+
200
+ def outer (x : A | None ):
201
+ if x is not None :
202
+ def inner () -> None :
203
+ # TODO : should ideally be `A`
204
+ reveal_type(x) # revealed: A | None
205
+ inner()
206
+ ```
207
+
208
+ ### Shadowing
209
+
210
+ Similarly, since we do not analyze control flow in the outer scope here, we assume that ` inner() `
211
+ could be called between the two assignments to ` x ` :
212
+
213
+ ``` py
214
+ def outer () -> None :
215
+ def inner () -> None :
216
+ # TODO : this should ideally be `Unknown | Literal[1]`, but no other type checker supports this either
217
+ reveal_type(x) # revealed: Unknown | None | Literal[1]
218
+ x = None
219
+
220
+ # [additional code here]
221
+
222
+ x = 1
223
+
224
+ inner()
225
+ ```
226
+
227
+ This is currently even true if the ` inner ` function is only defined after the second assignment to
228
+ ` x ` :
229
+
202
230
``` py
203
- def outer ():
231
+ def outer () -> None :
204
232
x = None
205
233
206
- # [… ]
234
+ # [additional code here ]
207
235
208
236
x = 1
209
237
210
- def inner ():
211
- # TODO : this should ideally be `Unknown | Literal[1]`
238
+ def inner () -> None :
239
+ # TODO : this should be `Unknown | Literal[1]`. Mypy and pyright support this.
212
240
reveal_type(x) # revealed: Unknown | None | Literal[1]
213
241
inner()
214
242
```
215
243
216
- Similar :
244
+ A similar case derived from an ecosystem example, involving declared types :
217
245
218
246
``` py
219
247
class C : ...
220
248
221
- def _f_ (x : C | None ):
249
+ def outer (x : C | None ):
222
250
x = x or C()
223
251
224
252
reveal_type(x) # revealed: C
225
253
226
- def g () :
254
+ def inner () -> None :
227
255
# TODO : this should ideally be `C`
228
256
reveal_type(x) # revealed: C | None
257
+ inner()
229
258
```
230
259
231
- Writes to the outer-scope variable are not detected. Other typecheckers also don't support this:
260
+ ### Assignments to nonlocal variables
261
+
262
+ Writes to the outer-scope variable are currently not detected:
232
263
233
264
``` py
234
- def outer ():
265
+ def outer () -> None :
235
266
x = None
236
267
237
268
def set_x () -> None :
269
+ nonlocal x
238
270
x = 1
239
271
set_x()
240
272
241
273
def inner () -> None :
274
+ # TODO : this should ideally be `Unknown | None | Literal[1]`.
242
275
reveal_type(x) # revealed: Unknown | None
243
276
inner()
244
277
```
245
278
246
- ## Overloads
279
+ ## Handling of overloads
280
+
281
+ Overloads need special treatment, because here, we do not want to consider * all* possible
282
+ definitions of ` f ` . This would otherwise result in a union of all three definitions of ` f ` :
247
283
248
284
``` py
249
285
from typing import overload
@@ -255,10 +291,12 @@ def f(x: str) -> str: ...
255
291
def f (x : int | str ) -> int | str :
256
292
raise NotImplementedError
257
293
294
+ reveal_type(f) # revealed: Overload[(x: int) -> int, (x: str) -> str]
258
295
reveal_type(f(1 )) # revealed: int
259
296
reveal_type(f(" a" )) # revealed: str
260
297
261
298
def _ ():
299
+ reveal_type(f) # revealed: Overload[(x: int) -> int, (x: str) -> str]
262
300
reveal_type(f(1 )) # revealed: int
263
301
reveal_type(f(" a" )) # revealed: str
264
302
```
0 commit comments