|
17 | 17 | import os
|
18 | 18 | import pkgutil
|
19 | 19 | import re
|
| 20 | +import struct |
20 | 21 | import symtable
|
21 | 22 | import sys
|
22 | 23 | import traceback
|
@@ -466,6 +467,67 @@ class SubClass(runtime): # type: ignore[misc]
|
466 | 467 | )
|
467 | 468 |
|
468 | 469 |
|
| 470 | +SIZEOF_PYOBJECT = struct.calcsize("P") |
| 471 | + |
| 472 | + |
| 473 | +def _shape_differs(t1: type[object], t2: type[object]) -> bool: |
| 474 | + """Check whether two types differ in shape. |
| 475 | +
|
| 476 | + Mirrors the shape_differs() function in typeobject.c in CPython.""" |
| 477 | + if sys.version_info >= (3, 12): |
| 478 | + return t1.__basicsize__ != t2.__basicsize__ or t1.__itemsize__ != t2.__itemsize__ |
| 479 | + else: |
| 480 | + # CPython had more complicated logic before 3.12: |
| 481 | + # https://github.com/python/cpython/blob/f3c6f882cddc8dc30320d2e73edf019e201394fc/Objects/typeobject.c#L2224 |
| 482 | + # We attempt to mirror it here well enough to support the most common cases. |
| 483 | + if t1.__itemsize__ or t2.__itemsize__: |
| 484 | + return t1.__basicsize__ != t2.__basicsize__ or t1.__itemsize__ != t2.__itemsize__ |
| 485 | + t_size = t1.__basicsize__ |
| 486 | + if not t2.__weakrefoffset__ and t1.__weakrefoffset__ + SIZEOF_PYOBJECT == t_size: |
| 487 | + t_size -= SIZEOF_PYOBJECT |
| 488 | + if not t2.__dictoffset__ and t1.__dictoffset__ + SIZEOF_PYOBJECT == t_size: |
| 489 | + t_size -= SIZEOF_PYOBJECT |
| 490 | + if not t2.__weakrefoffset__ and t2.__weakrefoffset__ == t_size: |
| 491 | + t_size -= SIZEOF_PYOBJECT |
| 492 | + return t_size != t2.__basicsize__ |
| 493 | + |
| 494 | + |
| 495 | +def _is_disjoint_base(typ: type[object]) -> bool: |
| 496 | + """Return whether a type is a disjoint base at runtime, mirroring CPython's logic in typeobject.c. |
| 497 | +
|
| 498 | + See PEP 800.""" |
| 499 | + if typ is object: |
| 500 | + return True |
| 501 | + base = typ.__base__ |
| 502 | + assert base is not None, f"Type {typ} has no base" |
| 503 | + return _shape_differs(typ, base) |
| 504 | + |
| 505 | + |
| 506 | +def _verify_disjoint_base( |
| 507 | + stub: nodes.TypeInfo, runtime: type[object], object_path: list[str] |
| 508 | +) -> Iterator[Error]: |
| 509 | + # If it's final, doesn't matter whether it's a disjoint base or not |
| 510 | + if stub.is_final: |
| 511 | + return |
| 512 | + is_disjoint_runtime = _is_disjoint_base(runtime) |
| 513 | + if is_disjoint_runtime and not stub.is_disjoint_base: |
| 514 | + yield Error( |
| 515 | + object_path, |
| 516 | + "is a disjoint base at runtime, but isn't marked with @disjoint_base in the stub", |
| 517 | + stub, |
| 518 | + runtime, |
| 519 | + stub_desc=repr(stub), |
| 520 | + ) |
| 521 | + elif not is_disjoint_runtime and stub.is_disjoint_base: |
| 522 | + yield Error( |
| 523 | + object_path, |
| 524 | + "is marked with @disjoint_base in the stub, but isn't a disjoint base at runtime", |
| 525 | + stub, |
| 526 | + runtime, |
| 527 | + stub_desc=repr(stub), |
| 528 | + ) |
| 529 | + |
| 530 | + |
469 | 531 | def _verify_metaclass(
|
470 | 532 | stub: nodes.TypeInfo, runtime: type[Any], object_path: list[str], *, is_runtime_typeddict: bool
|
471 | 533 | ) -> Iterator[Error]:
|
@@ -534,6 +596,7 @@ def verify_typeinfo(
|
534 | 596 | return
|
535 | 597 |
|
536 | 598 | yield from _verify_final(stub, runtime, object_path)
|
| 599 | + yield from _verify_disjoint_base(stub, runtime, object_path) |
537 | 600 | is_runtime_typeddict = stub.typeddict_type is not None and is_typeddict(runtime)
|
538 | 601 | yield from _verify_metaclass(
|
539 | 602 | stub, runtime, object_path, is_runtime_typeddict=is_runtime_typeddict
|
|
0 commit comments