/analysis/test/integration/descriptorTest.ml

https://github.com/facebook/pyre-check · OCaml · 650 lines · 555 code · 77 blank · 18 comment · 3 complexity · 2239fdc1598deb32fea74b41500b5353 MD5 · raw file

  1. (*
  2. * Copyright (c) Facebook, Inc. and its affiliates.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. *)
  7. open OUnit2
  8. open IntegrationTest
  9. let test_non_data_descriptors context =
  10. let assert_type_errors = assert_type_errors ~context in
  11. assert_type_errors
  12. {|
  13. from typing import overload, Union
  14. class Descriptor:
  15. def __get__(self, o: object, t: object = None) -> int:
  16. return 1
  17. class Host:
  18. d: Descriptor = Descriptor()
  19. def f() -> None:
  20. x = Host().d
  21. reveal_type(x)
  22. y = Host.d
  23. reveal_type(y)
  24. |}
  25. [
  26. "Revealed type [-1]: Revealed type for `x` is `int`.";
  27. "Revealed type [-1]: Revealed type for `y` is `int`.";
  28. ];
  29. (* Distinguishing being called from instance/from class *)
  30. assert_type_errors
  31. {|
  32. from typing import overload, Union
  33. class Descriptor:
  34. @overload
  35. def __get__(self, o: None, t: object = None) -> int: ...
  36. @overload
  37. def __get__(self, o: object, t: object = None) -> str: ...
  38. def __get__(self, o: object, t: object = None) -> Union[int, str]:
  39. if o:
  40. return "A"
  41. else:
  42. return 1
  43. class Host:
  44. d: Descriptor = Descriptor()
  45. def f() -> None:
  46. x = Host().d
  47. reveal_type(x)
  48. y = Host.d
  49. reveal_type(y)
  50. |}
  51. [
  52. "Revealed type [-1]: Revealed type for `x` is `str`.";
  53. "Revealed type [-1]: Revealed type for `y` is `int`.";
  54. ];
  55. (* Overloading based on host class *)
  56. assert_type_errors
  57. {|
  58. from typing import overload, Union, NoReturn
  59. class BaseA:
  60. a_prop: int = 1
  61. class BaseB:
  62. b_prop: str = "A"
  63. class Descriptor:
  64. @overload
  65. def __get__(self, o: BaseA, t: object = None) -> int: ...
  66. @overload
  67. def __get__(self, o: BaseB, t: object = None) -> str: ...
  68. @overload
  69. def __get__(self, o: object, t: object = None) -> bool: ...
  70. def __get__(self, o: object, t: object = None) -> Union[int, str, bool]:
  71. if isinstance(o, BaseA):
  72. return o.a_prop
  73. elif isinstance(o, BaseB):
  74. return o.b_prop
  75. else:
  76. return True
  77. class HostA(BaseA):
  78. d: Descriptor = Descriptor()
  79. class HostB(BaseB):
  80. d: Descriptor = Descriptor()
  81. class HostC:
  82. d: Descriptor = Descriptor()
  83. def f() -> None:
  84. x = HostA().d
  85. reveal_type(x)
  86. y = HostB().d
  87. reveal_type(y)
  88. z = HostC().d
  89. reveal_type(z)
  90. |}
  91. [
  92. "Revealed type [-1]: Revealed type for `x` is `int`.";
  93. "Revealed type [-1]: Revealed type for `y` is `str`.";
  94. "Revealed type [-1]: Revealed type for `z` is `bool`.";
  95. ];
  96. (* Generic descriptors *)
  97. assert_type_errors
  98. {|
  99. from typing import overload, Union, Generic, TypeVar, Callable
  100. T = TypeVar("T")
  101. THost = TypeVar("THost")
  102. class MyCallable(Generic[T]):
  103. @overload
  104. def __get__(self, o: None, t: object = None) -> T: ...
  105. @overload
  106. def __get__(self, o: THost, t: object = None) -> BoundMethod[T, THost]: ...
  107. class Host:
  108. d: MyCallable[Callable[[Host, int], str]] = MyCallable()
  109. def f() -> None:
  110. x = Host().d
  111. reveal_type(x)
  112. y = Host.d
  113. reveal_type(y)
  114. z = Host().d(1)
  115. reveal_type(z)
  116. Host.d(1)
  117. |}
  118. [
  119. "Missing overload implementation [42]: Overloaded function `MyCallable.__get__` must have an \
  120. implementation.";
  121. "Revealed type [-1]: Revealed type for `x` is `BoundMethod[typing.Callable[[Host, int], \
  122. str], Host]`.";
  123. "Revealed type [-1]: Revealed type for `y` is `typing.Callable[[Host, int], str]`.";
  124. "Revealed type [-1]: Revealed type for `z` is `str`.";
  125. "Missing argument [20]: PositionalOnly call expects argument in position 1.";
  126. ];
  127. assert_type_errors
  128. {|
  129. from typing import overload, Union
  130. class Descriptor:
  131. def __get__(self, o: object, t: object = None) -> str:
  132. return "A"
  133. def producer() -> Union[Descriptor, int]: ...
  134. class Host:
  135. d: Union[Descriptor, int] = producer()
  136. def f() -> None:
  137. x = Host().d
  138. reveal_type(x)
  139. y = Host.d
  140. reveal_type(y)
  141. |}
  142. [
  143. "Revealed type [-1]: Revealed type for `x` is `Union[int, str]`.";
  144. "Revealed type [-1]: Revealed type for `y` is `Union[int, str]`.";
  145. ];
  146. assert_type_errors
  147. {|
  148. from typing import overload, Union
  149. from dataclasses import dataclass
  150. class Descriptor:
  151. def __get__(self, o: object, t: object = None) -> str:
  152. return "A"
  153. @dataclass
  154. class DC:
  155. d: Descriptor = Descriptor()
  156. def f(d: DC) -> None:
  157. x = DC.d
  158. reveal_type(x)
  159. |}
  160. ["Revealed type [-1]: Revealed type for `x` is `str`."];
  161. assert_type_errors
  162. {|
  163. from typing import overload, Union
  164. class Descriptor:
  165. # TODO(T65806273): should error here
  166. __get__: int = 1
  167. class Host:
  168. d: Descriptor = Descriptor()
  169. def f() -> None:
  170. x = Host().d
  171. reveal_type(x)
  172. y = Host.d
  173. reveal_type(y)
  174. |}
  175. [
  176. "Revealed type [-1]: Revealed type for `x` is `typing.Any`.";
  177. "Revealed type [-1]: Revealed type for `y` is `typing.Any`.";
  178. ];
  179. assert_type_errors
  180. {|
  181. from typing import overload, Union
  182. class Inner:
  183. def __call__(self, descriptor: object, host: object, host_type: object = None) -> int:
  184. return 42
  185. def __get__(self, host: object, host_type: object = None) -> str:
  186. # This should not be relevant to anything since `__get__`s are accessed
  187. # magically without applying description
  188. return "irrelevant"
  189. class Descriptor:
  190. __get__: Inner = Inner()
  191. class Host:
  192. d: Descriptor = Descriptor()
  193. def f() -> None:
  194. x = Host().d
  195. reveal_type(x)
  196. y = Host.d
  197. reveal_type(y)
  198. |}
  199. [
  200. (* TODO(T65807186): This should be supported (should be `int`) *)
  201. "Revealed type [-1]: Revealed type for `x` is `typing.Any`.";
  202. "Revealed type [-1]: Revealed type for `y` is `typing.Any`.";
  203. ];
  204. assert_type_errors
  205. {|
  206. from typing import overload, Union, TypeVar, List
  207. T = TypeVar("T")
  208. class OnlyCanBeHostedOnLists:
  209. # TODO(T65807232): should error here, because not less than "virtual" object.__get__
  210. def __get__(self, host: List[T], host_type: object = None) -> T:
  211. return host[0]
  212. class ListHost(List[int]):
  213. first = OnlyCanBeHostedOnLists()
  214. class NonListHost():
  215. first = OnlyCanBeHostedOnLists()
  216. def f(l: ListHost, n: NonListHost) -> None:
  217. x = l.first
  218. reveal_type(x)
  219. y = n.first
  220. reveal_type(y)
  221. |}
  222. [
  223. "Revealed type [-1]: Revealed type for `x` is `int`.";
  224. "Revealed type [-1]: Revealed type for `y` is `typing.Any`.";
  225. ];
  226. assert_type_errors
  227. {|
  228. from typing import overload, Union, TypeVar, List
  229. class Parent:
  230. pass
  231. class EvilChild(Parent):
  232. # TODO(T65807232): should error here, because not less than "virtual" Parent.__get__
  233. def __get__(self, host: object, host_type: object = None) -> int:
  234. return 42
  235. def producer() -> Parent:
  236. return EvilChild()
  237. class Host:
  238. a: Parent = producer()
  239. def f() -> None:
  240. x = Host().a
  241. reveal_type(x)
  242. y = Host.a
  243. reveal_type(y)
  244. |}
  245. [
  246. (* These are technically wrong, but its not our fault *)
  247. "Revealed type [-1]: Revealed type for `x` is `Parent`.";
  248. "Revealed type [-1]: Revealed type for `y` is `Parent`.";
  249. ];
  250. assert_type_errors
  251. {|
  252. from typing import overload, Union, TypeVar, List
  253. class Descriptor:
  254. def __get__(self, o: object, t: object = None) -> int:
  255. return 1
  256. class Host:
  257. a = Descriptor()
  258. def f() -> None:
  259. h = Host()
  260. x = Host.a
  261. reveal_type(x)
  262. h.a = 5
  263. h.a = Descriptor()
  264. |}
  265. [
  266. "Revealed type [-1]: Revealed type for `x` is `int`.";
  267. "Incompatible attribute type [8]: Attribute `a` declared in class `Host` has type `int` but \
  268. is used as type `Descriptor`.";
  269. ];
  270. assert_type_errors
  271. {|
  272. from typing import overload, Union
  273. class Descriptor:
  274. @overload
  275. def __get__(self, o: None, t: object = None) -> int: ...
  276. @overload
  277. def __get__(self, o: object, t: object = None) -> str: ...
  278. def __get__(self, o: object, t: object = None) -> Union[int, str]:
  279. if o:
  280. return "A"
  281. else:
  282. return 1
  283. class MetaclassHost(type):
  284. d: Descriptor = Descriptor()
  285. class C(metaclass=MetaclassHost):
  286. pass
  287. def f() -> None:
  288. # This is str because Type[C] is an instance of MetaclassHost
  289. x = C.d
  290. reveal_type(x)
  291. y = MetaclassHost("A", (), {}).d
  292. reveal_type(y)
  293. z = MetaclassHost.d
  294. reveal_type(z)
  295. |}
  296. [
  297. "Revealed type [-1]: Revealed type for `x` is `str`.";
  298. "Revealed type [-1]: Revealed type for `y` is `str`.";
  299. "Revealed type [-1]: Revealed type for `z` is `int`.";
  300. ];
  301. assert_type_errors
  302. {|
  303. from typing import overload, Union, TypeVar, List, ClassMethod, Callable, Type, Any
  304. def maker() -> Any: ...
  305. class Host:
  306. cm: ClassMethod[Callable[[Type[Host], int, str], bool]] = maker()
  307. def f() -> None:
  308. x = Host().cm
  309. reveal_type(x)
  310. y = Host.cm
  311. reveal_type(y)
  312. z = Host().cm(1, "A")
  313. reveal_type(z)
  314. z = Host().cm(1, 2)
  315. |}
  316. [
  317. "Undefined import [21]: Could not find a name `ClassMethod` defined in module `typing`.";
  318. "Missing return annotation [3]: Return type must be specified as type other than `Any`.";
  319. "Revealed type [-1]: Revealed type for `x` is `BoundMethod[typing.Callable[[Type[Host], int, \
  320. str], bool], Type[Host]]`.";
  321. "Revealed type [-1]: Revealed type for `y` is `BoundMethod[typing.Callable[[Type[Host], int, \
  322. str], bool], Type[Host]]`.";
  323. "Revealed type [-1]: Revealed type for `z` is `bool`.";
  324. "Incompatible parameter type [6]: Expected `str` for 2nd positional only parameter to \
  325. anonymous call but got `int`.";
  326. ];
  327. assert_type_errors
  328. {|
  329. from typing import TypeVar, Type, Optional
  330. T = TypeVar("T")
  331. class X:
  332. @classmethod
  333. def x(cls, x: T) -> Optional[T]: ...
  334. @classmethod
  335. def foo(cls, y: int) -> None:
  336. z = cls.x(y)
  337. reveal_type(z)
  338. |}
  339. ["Revealed type [-1]: Revealed type for `z` is `Optional[int]`."];
  340. assert_type_errors
  341. {|
  342. from typing import overload, Union, TypeVar, List, StaticMethod, Callable, Type, Any
  343. def maker() -> Any: ...
  344. class Host:
  345. sm: StaticMethod[Callable[[int, str], bool]] = maker()
  346. def f() -> None:
  347. x = Host().sm
  348. reveal_type(x)
  349. y = Host.sm
  350. reveal_type(y)
  351. |}
  352. [
  353. "Undefined import [21]: Could not find a name `StaticMethod` defined in module `typing`.";
  354. "Missing return annotation [3]: Return type must be specified as type other than `Any`.";
  355. "Revealed type [-1]: Revealed type for `x` is `typing.Callable[[int, str], bool]`.";
  356. "Revealed type [-1]: Revealed type for `y` is `typing.Callable[[int, str], bool]`.";
  357. ];
  358. assert_type_errors
  359. {|
  360. from typing import overload, Union, TypeVar, List, StaticMethod, Callable, Type, Any
  361. def free_function(h: object, x: int) -> str:
  362. return "A"
  363. class Host:
  364. m: Callable[[object, int], str] = free_function
  365. def f() -> None:
  366. x = Host().m
  367. reveal_type(x)
  368. y = Host.m
  369. reveal_type(y)
  370. |}
  371. [
  372. "Undefined import [21]: Could not find a name `StaticMethod` defined in module `typing`.";
  373. "Revealed type [-1]: Revealed type for `x` is `BoundMethod[typing.Callable[[object, int], \
  374. str], Host]`.";
  375. "Revealed type [-1]: Revealed type for `y` is `typing.Callable[[object, int], str]`.";
  376. ];
  377. assert_type_errors
  378. {|
  379. from typing import overload, Union, TypeVar, List, StaticMethod, Callable, Type, Any
  380. class CallableClass:
  381. def __call__(self, h: object, x: int) -> str:
  382. return "A"
  383. class Host:
  384. direct: CallableClass = CallableClass()
  385. as_callable: Callable[[object, int], str] = CallableClass()
  386. def f() -> None:
  387. x = Host().direct
  388. reveal_type(x)
  389. y = Host.direct
  390. reveal_type(y)
  391. a = Host().as_callable
  392. reveal_type(a)
  393. b = Host.as_callable
  394. reveal_type(b)
  395. |}
  396. [
  397. "Undefined import [21]: Could not find a name `StaticMethod` defined in module `typing`.";
  398. "Revealed type [-1]: Revealed type for `x` is `CallableClass`.";
  399. "Revealed type [-1]: Revealed type for `y` is `CallableClass`.";
  400. (* This is wrong. Unfortunately its currently avoidable as long as we resolve defs to
  401. Callables *)
  402. "Revealed type [-1]: Revealed type for `a` is `BoundMethod[typing.Callable[[object, int], \
  403. str], Host]`.";
  404. "Revealed type [-1]: Revealed type for `b` is `typing.Callable[[object, int], str]`.";
  405. ];
  406. assert_type_errors
  407. {|
  408. from typing import overload, Union, TypeVar, List, StaticMethod, Callable, Type, Any
  409. def free_function(h: object, x: int) -> str:
  410. return "A"
  411. class Host:
  412. m: Union[Callable[[object, int], str], int] = free_function
  413. def f() -> None:
  414. x = Host().m
  415. reveal_type(x)
  416. y = Host.m
  417. reveal_type(y)
  418. |}
  419. [
  420. "Undefined import [21]: Could not find a name `StaticMethod` defined in module `typing`.";
  421. "Revealed type [-1]: Revealed type for `x` is `Union[BoundMethod[typing.Callable[[object, \
  422. int], str], Host], int]`.";
  423. "Revealed type [-1]: Revealed type for `y` is `Union[typing.Callable[[object, int], str], \
  424. int]`.";
  425. ];
  426. assert_type_errors
  427. {|
  428. from typing import NamedTuple
  429. class Descriptor:
  430. def __get__(self, o: object, t: object = None) -> int:
  431. return 1
  432. class N(NamedTuple):
  433. value: Descriptor = Descriptor()
  434. def f() -> None:
  435. foo = N()
  436. x = foo.value
  437. reveal_type(x)
  438. |}
  439. ["Revealed type [-1]: Revealed type for `x` is `Descriptor`."];
  440. ()
  441. let test_data_descriptors context =
  442. let assert_type_errors = assert_type_errors ~context in
  443. assert_type_errors
  444. {|
  445. from typing import overload, Union
  446. class Descriptor:
  447. def __get__(self, o: object, t: object = None) -> int:
  448. return 1
  449. def __set__(self, o: object, v: str) -> None:
  450. pass
  451. class Host:
  452. d: Descriptor = Descriptor()
  453. def f() -> None:
  454. x = Host().d
  455. reveal_type(x)
  456. y = Host.d
  457. reveal_type(y)
  458. Host().d = "A"
  459. # assignments to the class always ignore __set__
  460. Host.d = Descriptor()
  461. Host().d = Descriptor()
  462. Host.d = "A"
  463. reveal_type(Host().d)
  464. reveal_type(Host.d)
  465. |}
  466. [
  467. "Revealed type [-1]: Revealed type for `x` is `int`.";
  468. "Revealed type [-1]: Revealed type for `y` is `int`.";
  469. (* This is not a great error message but it is correct *)
  470. "Incompatible attribute type [8]: Attribute `d` declared in class `Host` has type `str` but \
  471. is used as type `Descriptor`.";
  472. "Incompatible attribute type [8]: Attribute `d` declared in class `Host` has type \
  473. `Descriptor` but is used as type `str`.";
  474. (* This is an even more confusing message, but is also correct *)
  475. "Revealed type [-1]: Revealed type for `test.Host().d` is `str` (inferred: `int`).";
  476. "Revealed type [-1]: Revealed type for `test.Host.d` is `Descriptor` (inferred: `int`).";
  477. ];
  478. (* Overloading based on host class *)
  479. assert_type_errors
  480. {|
  481. from typing import overload, Union, NoReturn
  482. class BaseA:
  483. a_prop: int = 1
  484. class BaseB:
  485. b_prop: str = "A"
  486. class Descriptor:
  487. @overload
  488. def __set__(self, o: BaseA, v: int) -> None: ...
  489. @overload
  490. def __set__(self, o: BaseB, v: str) -> None: ...
  491. @overload
  492. def __set__(self, o: object, v: bool) -> None: ...
  493. def __set__(self, o: object, v: object) -> None:
  494. pass
  495. class HostA(BaseA):
  496. d: Descriptor = Descriptor()
  497. class HostB(BaseB):
  498. d: Descriptor = Descriptor()
  499. class HostC:
  500. d: Descriptor = Descriptor()
  501. def f() -> None:
  502. reveal_type(HostA().d)
  503. reveal_type(HostB().d)
  504. reveal_type(HostC().d)
  505. |}
  506. [
  507. "Revealed type [-1]: Revealed type for `test.HostA().d` is `int` (inferred: `Descriptor`).";
  508. "Revealed type [-1]: Revealed type for `test.HostB().d` is `str` (inferred: `Descriptor`).";
  509. "Revealed type [-1]: Revealed type for `test.HostC().d` is `bool` (inferred: `Descriptor`).";
  510. ];
  511. assert_type_errors
  512. {|
  513. from typing import overload, Union
  514. from dataclasses import dataclass
  515. class Descriptor:
  516. x: str = ""
  517. def __get__(self, o: object, t: object = None) -> str:
  518. return "A" + self.x
  519. def __set__(self, o: object, value: str) -> None:
  520. self.x = value
  521. @dataclass
  522. class DC:
  523. d: Descriptor = Descriptor()
  524. def f() -> None:
  525. DC("A")
  526. |}
  527. [
  528. (* TODO(T65806273): This should be accepted, but we're currently ignoring descriptors when
  529. building dataclass constructors for perf reasons *)
  530. "Incompatible parameter type [6]: Expected `Descriptor` for 1st positional only parameter to \
  531. call `DC.__init__` but got `str`.";
  532. ];
  533. assert_type_errors
  534. {|
  535. from typing import overload, Union
  536. class Descriptor:
  537. def __set__(self, h: object, v: int) -> None:
  538. pass
  539. class MetaclassHost(type):
  540. d: Descriptor = Descriptor()
  541. class C(metaclass=MetaclassHost):
  542. pass
  543. def f() -> None:
  544. # This is correct because Type[C] is an instance of MetaclassHost
  545. C.d = 1
  546. C.d = Descriptor()
  547. reveal_type("separator")
  548. MetaclassHost("A", (), {}).d = 2
  549. MetaclassHost("A", (), {}).d = Descriptor()
  550. reveal_type("separator")
  551. MetaclassHost.d = 3
  552. MetaclassHost.d = Descriptor()
  553. |}
  554. [
  555. "Incompatible attribute type [8]: Attribute `d` declared in class `MetaclassHost` has type \
  556. `int` but is used as type `Descriptor`.";
  557. "Revealed type [-1]: Revealed type for `\"separator\"` is \
  558. `typing_extensions.Literal['separator']`.";
  559. "Incompatible attribute type [8]: Attribute `d` declared in class `MetaclassHost` has type \
  560. `int` but is used as type `Descriptor`.";
  561. "Revealed type [-1]: Revealed type for `\"separator\"` is \
  562. `typing_extensions.Literal['separator']`.";
  563. "Incompatible attribute type [8]: Attribute `d` declared in class `MetaclassHost` has type \
  564. `Descriptor` but is used as type `int`.";
  565. ];
  566. ()
  567. let () =
  568. "descriptors"
  569. >::: [
  570. "check_non_data_descriptors" >:: test_non_data_descriptors;
  571. "check_data_descriptors" >:: test_data_descriptors;
  572. ]
  573. |> Test.run