Skip to content

Commit d2082a9

Browse files
shannonzhufacebook-github-bot
authored andcommitted
Add assert refinement to temporary annotations
Summary: If there are no interleaving calls between refinement of a non-final attribute, store the refined annotation in temporary annotations. its_happening Reviewed By: grievejia Differential Revision: D30412120 fbshipit-source-id: c64a530cf5356796d84f783baffebf28808c4ed2
1 parent de7ce2a commit d2082a9

File tree

6 files changed

+208
-70
lines changed

6 files changed

+208
-70
lines changed

source/analysis/test/integration/annotationTest.ml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -765,6 +765,18 @@ let test_check_immutable_annotations context =
765765
return constant
766766
return 0
767767
|}
768+
[];
769+
assert_type_errors
770+
{|
771+
import typing
772+
constant: typing.Optional[int]
773+
def call() -> None: pass
774+
def foo() -> int:
775+
if constant is not None:
776+
call()
777+
return constant
778+
return 0
779+
|}
768780
["Incompatible return type [7]: Expected `int` but got `typing.Optional[int]`."];
769781
assert_type_errors
770782
{|
@@ -1190,6 +1202,20 @@ let test_check_refinement context =
11901202
else:
11911203
return 1
11921204
|}
1205+
[];
1206+
assert_type_errors
1207+
{|
1208+
import typing
1209+
def call() -> None: pass
1210+
class A:
1211+
a: typing.Optional[int] = None
1212+
def bar(self) -> int:
1213+
if self.a is not None:
1214+
call()
1215+
return self.a
1216+
else:
1217+
return 1
1218+
|}
11931219
["Incompatible return type [7]: Expected `int` but got `typing.Optional[int]`."];
11941220
assert_type_errors
11951221
{|

source/analysis/test/integration/attributeTest.ml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,6 @@ let test_check_attributes context =
312312
[
313313
"Uninitialized attribute [13]: Attribute `bar` is declared in class `Foo` "
314314
^ "to have type `typing.Optional[int]` but is never initialized.";
315-
"Incompatible return type [7]: Expected `int` but got `typing.Optional[int]`.";
316315
];
317316
assert_type_errors
318317
{|

source/analysis/test/integration/globalTest.ml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,18 @@ let test_check_globals context =
350350
return GLOBAL
351351
return 0
352352
|}
353+
[];
354+
assert_type_errors
355+
{|
356+
import typing
357+
GLOBAL: typing.Optional[int]
358+
def call() -> None: pass
359+
def foo() -> int:
360+
if GLOBAL:
361+
call()
362+
return GLOBAL
363+
return 0
364+
|}
353365
["Incompatible return type [7]: Expected `int` but got `typing.Optional[int]`."];
354366
assert_type_errors
355367
{|

source/analysis/test/integration/isinstanceTest.ml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,19 @@ let test_check_isinstance context =
2929
if isinstance(MY_GLOBAL, str):
3030
reveal_type(MY_GLOBAL)
3131
|}
32+
["Revealed type [-1]: Revealed type for `MY_GLOBAL` is `str`."];
33+
assert_type_errors
34+
{|
35+
import typing
36+
MY_GLOBAL: typing.Union[int, str] = 1
37+
38+
def call() -> None: pass
39+
40+
def foo() -> None:
41+
if isinstance(MY_GLOBAL, str):
42+
call()
43+
reveal_type(MY_GLOBAL)
44+
|}
3245
["Revealed type [-1]: Revealed type for `MY_GLOBAL` is `typing.Union[int, str]`."];
3346
assert_type_errors
3447
{|
@@ -41,6 +54,21 @@ let test_check_isinstance context =
4154
if isinstance(f.x, str):
4255
reveal_type(f.x)
4356
|}
57+
["Revealed type [-1]: Revealed type for `f.x` is `str`."];
58+
assert_type_errors
59+
{|
60+
import typing
61+
class Foo:
62+
def __init__(self) -> None:
63+
self.x: typing.Union[int, str] = 1
64+
65+
def call() -> None: pass
66+
67+
def foo(f: Foo) -> None:
68+
if isinstance(f.x, str):
69+
call()
70+
reveal_type(f.x)
71+
|}
4472
["Revealed type [-1]: Revealed type for `f.x` is `typing.Union[int, str]`."];
4573
assert_default_type_errors
4674
{|

source/analysis/test/integration/refinementTest.ml

Lines changed: 73 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -146,17 +146,22 @@ let test_assert_is_none context =
146146
{|
147147
from dataclasses import dataclass
148148
from typing import Optional, Final
149+
149150
class NormalClass():
150151
x: Optional[int] = None
152+
151153
class ClassWithFinalAttribute():
152154
def __init__(self, x: Optional[int]) -> None:
153155
self.x: Final[Optional[int]] = x
156+
154157
@dataclass
155158
class UnfrozenDataClass():
156159
x: Optional[int]
160+
157161
@dataclass(frozen=True)
158162
class FrozenDataClass():
159163
x: Optional[int]
164+
160165
class ReadOnlyPropertyClass():
161166
state: bool = True
162167
@property
@@ -166,12 +171,14 @@ let test_assert_is_none context =
166171
return None
167172
else:
168173
return 8
174+
169175
def foo() -> None:
170176
normal_class: Final[NormalClass]
171177
class_with_final_attribute: Final[ClassWithFinalAttribute]
172178
unfrozen_dataclass: Final[UnfrozenDataClass]
173179
frozen_dataclass: Final[FrozenDataClass]
174180
read_only_property_class: Final[ReadOnlyPropertyClass]
181+
175182
if normal_class.x is not None:
176183
reveal_type(normal_class.x)
177184
if class_with_final_attribute.x is not None:
@@ -184,30 +191,36 @@ let test_assert_is_none context =
184191
reveal_type(read_only_property_class.x)
185192
|}
186193
[
187-
"Revealed type [-1]: Revealed type for `normal_class.x` is `Optional[int]`.";
194+
"Revealed type [-1]: Revealed type for `normal_class.x` is `Optional[int]` (inferred: `int`).";
188195
"Revealed type [-1]: Revealed type for `class_with_final_attribute.x` is `Optional[int]` \
189196
(inferred: `int`, final).";
190-
"Revealed type [-1]: Revealed type for `unfrozen_dataclass.x` is `Optional[int]`.";
197+
"Revealed type [-1]: Revealed type for `unfrozen_dataclass.x` is `Optional[int]` (inferred: \
198+
`int`).";
191199
"Revealed type [-1]: Revealed type for `frozen_dataclass.x` is `Optional[int]` (inferred: \
192200
`int`, final).";
193201
"Revealed type [-1]: Revealed type for `read_only_property_class.x` is `Optional[int]` \
194-
(final).";
202+
(inferred: `int`, final).";
195203
];
196204
assert_type_errors
197205
{|
198206
from dataclasses import dataclass
199207
from typing import Optional, Final
208+
200209
class NormalClass():
201210
x: Optional[int] = None
211+
202212
class ClassWithFinalAttribute():
203213
def __init__(self, x: Optional[int]) -> None:
204214
self.x: Final[Optional[int]] = x
215+
205216
@dataclass
206217
class UnfrozenDataClass():
207218
x: Optional[int]
219+
208220
@dataclass(frozen=True)
209221
class FrozenDataClass():
210222
x: Optional[int]
223+
211224
class ReadOnlyPropertyClass():
212225
state: bool = True
213226
@property
@@ -217,21 +230,29 @@ let test_assert_is_none context =
217230
return None
218231
else:
219232
return 8
233+
234+
def interleaving_call() -> None: pass
235+
220236
def foo() -> None:
221237
normal_class: Final[NormalClass] = ...
222238
class_with_final_attribute: Final[ClassWithFinalAttribute] = ...
223239
unfrozen_dataclass: Final[UnfrozenDataClass] = ...
224240
frozen_dataclass: Final[FrozenDataClass] = ...
225241
read_only_property_class: Final[ReadOnlyPropertyClass] = ...
226242
if normal_class.x is None:
243+
interleaving_call()
227244
reveal_type(normal_class.x)
228245
if class_with_final_attribute.x is None:
246+
interleaving_call()
229247
reveal_type(class_with_final_attribute.x)
230248
if unfrozen_dataclass.x is None:
249+
interleaving_call()
231250
reveal_type(unfrozen_dataclass.x)
232251
if frozen_dataclass.x is None:
252+
interleaving_call()
233253
reveal_type(frozen_dataclass.x)
234254
if read_only_property_class.x is None:
255+
interleaving_call()
235256
reveal_type(read_only_property_class.x)
236257
|}
237258
[
@@ -246,17 +267,22 @@ let test_assert_is_none context =
246267
{|
247268
from dataclasses import dataclass
248269
from typing import Optional, Final
270+
249271
class NormalClass():
250272
x: float = 3.14
273+
251274
class ClassWithFinalAttribute():
252275
def __init__(self, x: float) -> None:
253276
self.x: Final[float] = x
277+
254278
@dataclass
255279
class UnfrozenDataClass():
256280
x: float
281+
257282
@dataclass(frozen=True)
258283
class FrozenDataClass():
259284
x: float
285+
260286
class ReadOnlyPropertyClass():
261287
state: bool = True
262288
@property
@@ -266,21 +292,29 @@ let test_assert_is_none context =
266292
return 8.2
267293
else:
268294
return 8
295+
296+
def interleaving_call() -> None: pass
297+
269298
def foo() -> None:
270299
normal_class: Final[NormalClass] = ...
271300
class_with_final_attribute: Final[ClassWithFinalAttribute] = ...
272301
unfrozen_dataclass: Final[UnfrozenDataClass] = ...
273302
frozen_dataclass: Final[FrozenDataClass] = ...
274303
read_only_property_class: Final[ReadOnlyPropertyClass] = ...
275304
if isinstance(normal_class.x, int):
305+
interleaving_call()
276306
reveal_type(normal_class.x)
277307
if isinstance(class_with_final_attribute.x, int):
308+
interleaving_call()
278309
reveal_type(class_with_final_attribute.x)
279310
if isinstance(unfrozen_dataclass.x, int):
311+
interleaving_call()
280312
reveal_type(unfrozen_dataclass.x)
281313
if isinstance(frozen_dataclass.x, int):
314+
interleaving_call()
282315
reveal_type(frozen_dataclass.x)
283316
if isinstance(read_only_property_class.x, int):
317+
interleaving_call()
284318
reveal_type(read_only_property_class.x)
285319
|}
286320
[
@@ -294,21 +328,29 @@ let test_assert_is_none context =
294328
{|
295329
from dataclasses import dataclass
296330
from typing import Optional, Final
331+
297332
@dataclass(frozen=True)
298333
class InnerFrozenDataClass():
299334
x: Optional[int]
335+
300336
@dataclass(frozen=True)
301337
class FrozenDataClass():
302338
inner: InnerFrozenDataClass
339+
303340
@dataclass
304341
class UnfrozenDataClass():
305342
inner: InnerFrozenDataClass
343+
344+
def interleaving_call() -> None: pass
345+
306346
def foo() -> None:
307347
unfrozen_dataclass: Final[UnfrozenDataClass] = ...
308348
frozen_dataclass: Final[FrozenDataClass] = ...
309349
if unfrozen_dataclass.inner.x is not None:
350+
interleaving_call()
310351
reveal_type(unfrozen_dataclass.inner.x)
311352
if frozen_dataclass.inner.x is not None:
353+
interleaving_call()
312354
reveal_type(frozen_dataclass.inner.x)
313355
|}
314356
[
@@ -344,12 +386,20 @@ let test_check_global_refinement context =
344386
def __init__(self, x: typing.Optional[int]) -> None:
345387
self.x = x
346388

389+
def call() -> None: pass
390+
347391
def foo() -> None:
348392
a = A(3)
349393
if a.x:
350394
reveal_type(a.x)
395+
if a.x:
396+
call()
397+
reveal_type(a.x)
351398
|}
352-
["Revealed type [-1]: Revealed type for `a.x` is `typing.Optional[int]`."];
399+
[
400+
"Revealed type [-1]: Revealed type for `a.x` is `typing.Optional[int]` (inferred: `int`).";
401+
"Revealed type [-1]: Revealed type for `a.x` is `typing.Optional[int]`.";
402+
];
353403
assert_type_errors
354404
{|
355405
import typing
@@ -364,31 +414,44 @@ let test_check_global_refinement context =
364414
self.assertIsNotNone(a.x)
365415
reveal_type(a.x)
366416
|}
367-
["Revealed type [-1]: Revealed type for `a.x` is `typing.Optional[int]`."];
417+
["Revealed type [-1]: Revealed type for `a.x` is `typing.Optional[int]` (inferred: `int`)."];
368418
assert_type_errors
369419
{|
370420
import typing
371421
MY_GLOBAL: typing.Optional[int] = 1
372422

423+
def call() -> None: pass
424+
373425
def foo() -> None:
374426
if MY_GLOBAL:
375427
reveal_type(MY_GLOBAL)
428+
call()
429+
reveal_type(MY_GLOBAL)
376430
|}
377-
["Revealed type [-1]: Revealed type for `MY_GLOBAL` is `typing.Optional[int]`."];
431+
[
432+
"Revealed type [-1]: Revealed type for `MY_GLOBAL` is `typing.Optional[int]` (inferred: \
433+
`int`).";
434+
"Revealed type [-1]: Revealed type for `MY_GLOBAL` is `typing.Optional[int]`.";
435+
];
378436
assert_type_errors
379437
{|
380438
import typing
381439
x: typing.Optional[int] = 1
382440

441+
def call() -> None: pass
442+
383443
def foo() -> None:
384444
global x
385445
x = 1
386446
if x is not None:
387447
reveal_type(x)
448+
call()
449+
reveal_type(x)
388450
|}
389451
[
390452
"Revealed type [-1]: Revealed type for `x` is `typing.Optional[int]` (inferred: \
391453
`typing_extensions.Literal[1]`).";
454+
"Revealed type [-1]: Revealed type for `x` is `typing.Optional[int]`.";
392455
]
393456

394457

@@ -801,6 +864,10 @@ let test_check_final_attribute_refinement context =
801864
def foo(a: Actor) -> None:
802865
if a.name is not None:
803866
expects_str(a.name)
867+
868+
if a.name is not None:
869+
expects_str("unrelated call")
870+
expects_str(a.name)
804871
|}
805872
[
806873
"Incompatible parameter type [6]: Expected `str` for 1st positional only parameter to call \

0 commit comments

Comments
 (0)