| -- Test cases for always defined attributes. | |
| -- | |
| -- If class C has attributes x and y that are always defined, the output will | |
| -- have a line like this: | |
| -- | |
| -- C: [x, y] | |
| [case testAlwaysDefinedSimple] | |
| class C: | |
| def __init__(self, x: int) -> None: | |
| self.x = x | |
| [out] | |
| C: [x] | |
| [case testAlwaysDefinedFail] | |
| class MethodCall: | |
| def __init__(self, x: int) -> None: | |
| self.f() | |
| self.x = x | |
| def f(self) -> None: | |
| pass | |
| class FuncCall: | |
| def __init__(self, x: int) -> None: | |
| f(x) | |
| self.x = x | |
| f(self) | |
| self.y = x | |
| class GetAttr: | |
| x: int | |
| def __init__(self, x: int) -> None: | |
| a = self.x | |
| self.x = x | |
| class _Base: | |
| def __init__(self) -> None: | |
| f(self) | |
| class CallSuper(_Base): | |
| def __init__(self, x: int) -> None: | |
| super().__init__() | |
| self.x = x | |
| class Lambda: | |
| def __init__(self, x: int) -> None: | |
| f = lambda x: x + 1 | |
| self.x = x | |
| g = lambda x: self | |
| self.y = x | |
| class If: | |
| def __init__(self, x: int) -> None: | |
| self.a = 1 | |
| if x: | |
| self.x = x | |
| else: | |
| self.y = 1 | |
| class Deletable: | |
| __deletable__ = ('x', 'y') | |
| def __init__(self) -> None: | |
| self.x = 0 | |
| self.y = 1 | |
| self.z = 2 | |
| class PrimitiveWithSelf: | |
| def __init__(self, s: str) -> None: | |
| self.x = getattr(self, s) | |
| def f(a) -> None: pass | |
| [out] | |
| MethodCall: [] | |
| FuncCall: [x] | |
| GetAttr: [] | |
| CallSuper: [] | |
| Lambda: [] | |
| If: [a] | |
| Deletable: [z] | |
| PrimitiveWithSelf: [] | |
| [case testAlwaysDefinedConditional] | |
| class IfAlways: | |
| def __init__(self, x: int, y: int) -> None: | |
| if x: | |
| self.x = x | |
| self.y = y | |
| elif y: | |
| self.x = y | |
| self.y = x | |
| else: | |
| self.x = 0 | |
| self.y = 0 | |
| self.z = 0 | |
| class IfSometimes1: | |
| def __init__(self, x: int, y: int) -> None: | |
| if x: | |
| self.x = x | |
| self.y = y | |
| elif y: | |
| self.z = y | |
| self.y = x | |
| else: | |
| self.y = 0 | |
| self.a = 0 | |
| class IfSometimes2: | |
| def __init__(self, x: int, y: int) -> None: | |
| if x: | |
| self.x = x | |
| self.y = y | |
| class IfStopAnalysis1: | |
| def __init__(self, x: int, y: int) -> None: | |
| if x: | |
| self.x = x | |
| f(self) | |
| else: | |
| self.x = x | |
| self.y = y | |
| class IfStopAnalysis2: | |
| def __init__(self, x: int, y: int) -> None: | |
| if x: | |
| self.x = x | |
| else: | |
| self.x = x | |
| f(self) | |
| self.y = y | |
| class IfStopAnalysis3: | |
| def __init__(self, x: int, y: int) -> None: | |
| if x: | |
| self.x = x | |
| else: | |
| f(self) | |
| self.x = x | |
| self.y = y | |
| class IfConditionalAndNonConditional1: | |
| def __init__(self, x: int) -> None: | |
| self.x = 0 | |
| if x: | |
| self.x = x | |
| class IfConditionalAndNonConditional2: | |
| def __init__(self, x: int) -> None: | |
| # x is not considered always defined, since the second assignment may | |
| # either initialize or update. | |
| if x: | |
| self.x = x | |
| self.x = 0 | |
| def f(a) -> None: pass | |
| [out] | |
| IfAlways: [x, y, z] | |
| IfSometimes1: [y] | |
| IfSometimes2: [y] | |
| IfStopAnalysis1: [x] | |
| IfStopAnalysis2: [x] | |
| IfStopAnalysis3: [] | |
| IfConditionalAndNonConditional1: [x] | |
| IfConditionalAndNonConditional2: [] | |
| [case testAlwaysDefinedExpressions] | |
| from typing import Dict, Final, List, Set, Optional, cast | |
| import other | |
| class C: pass | |
| class Collections: | |
| def __init__(self, x: int) -> None: | |
| self.l = [x] | |
| self.d: Dict[str, str] = {} | |
| self.s: Set[int] = set() | |
| self.d2 = {'x': x} | |
| self.s2 = {x} | |
| self.l2 = [f(), None] * x | |
| self.t = tuple(self.l2) | |
| class Comparisons: | |
| def __init__(self, y: int, c: C, s: str, o: Optional[str]) -> None: | |
| self.n1 = y < 5 | |
| self.n2 = y == 5 | |
| self.c1 = y is c | |
| self.c2 = y is not c | |
| self.o1 = o is None | |
| self.o2 = o is not None | |
| self.s = s < 'x' | |
| class BinaryOps: | |
| def __init__(self, x: int, s: str) -> None: | |
| self.a = x + 2 | |
| self.b = x & 2 | |
| self.c = x * 2 | |
| self.d = -x | |
| self.e = 'x' + s | |
| self.f = x << x | |
| g = 2 | |
| class LocalsAndGlobals: | |
| def __init__(self, x: int) -> None: | |
| t = x + 1 | |
| self.a = t - t | |
| self.g = g | |
| class Booleans: | |
| def __init__(self, x: int, b: bool) -> None: | |
| self.a = True | |
| self.b = False | |
| self.c = not b | |
| self.d = b or b | |
| self.e = b and b | |
| F: Final = 3 | |
| class ModuleFinal: | |
| def __init__(self) -> None: | |
| self.a = F | |
| self.b = other.Y | |
| class ClassFinal: | |
| F: Final = 3 | |
| def __init__(self) -> None: | |
| self.a = ClassFinal.F | |
| class Literals: | |
| def __init__(self) -> None: | |
| self.a = 'x' | |
| self.b = b'x' | |
| self.c = 2.2 | |
| class ListComprehension: | |
| def __init__(self, x: List[int]) -> None: | |
| self.a = [i + 1 for i in x] | |
| class Helper: | |
| def __init__(self, arg) -> None: | |
| self.x = 0 | |
| def foo(self, arg) -> int: | |
| return 1 | |
| class AttrAccess: | |
| def __init__(self, o: Helper) -> None: | |
| self.x = o.x | |
| o.x = o.x + 1 | |
| self.y = o.foo(self.x) | |
| o.foo(self) | |
| self.z = 1 | |
| class Construct: | |
| def __init__(self) -> None: | |
| self.x = Helper(1) | |
| self.y = Helper(self) | |
| class IsInstance: | |
| def __init__(self, x: object) -> None: | |
| if isinstance(x, str): | |
| self.x = 0 | |
| elif isinstance(x, Helper): | |
| self.x = 1 | |
| elif isinstance(x, (list, tuple)): | |
| self.x = 2 | |
| else: | |
| self.x = 3 | |
| class Cast: | |
| def __init__(self, x: object) -> None: | |
| self.x = cast(int, x) | |
| self.s = cast(str, x) | |
| self.c = cast(Cast, x) | |
| class PropertyAccessGetter: | |
| def __init__(self, other: PropertyAccessGetter) -> None: | |
| self.x = other.p | |
| self.y = 1 | |
| self.z = self.p | |
| @property | |
| def p(self) -> int: | |
| return 0 | |
| class PropertyAccessSetter: | |
| def __init__(self, other: PropertyAccessSetter) -> None: | |
| other.p = 1 | |
| self.y = 1 | |
| self.z = self.p | |
| @property | |
| def p(self) -> int: | |
| return 0 | |
| @p.setter | |
| def p(self, x: int) -> None: | |
| pass | |
| def f() -> int: | |
| return 0 | |
| [file other.py] | |
| # Not compiled | |
| from typing import Final | |
| Y: Final = 3 | |
| [out] | |
| C: [] | |
| Collections: [d, d2, l, l2, s, s2, t] | |
| Comparisons: [c1, c2, n1, n2, o1, o2, s] | |
| BinaryOps: [a, b, c, d, e, f] | |
| LocalsAndGlobals: [a, g] | |
| Booleans: [a, b, c, d, e] | |
| ModuleFinal: [a, b] | |
| ClassFinal: [a] | |
| Literals: [a, b, c] | |
| ListComprehension: [a] | |
| Helper: [x] | |
| AttrAccess: [x, y] | |
| Construct: [x] | |
| IsInstance: [x] | |
| Cast: [c, s, x] | |
| PropertyAccessGetter: [x, y] | |
| PropertyAccessSetter: [y] | |
| [case testAlwaysDefinedExpressions2] | |
| from typing import List, Tuple | |
| class C: | |
| def __init__(self) -> None: | |
| self.x = 0 | |
| class AttributeRef: | |
| def __init__(self, c: C) -> None: | |
| self.aa = c.x | |
| self.bb = self.aa | |
| if c is not None: | |
| self.z = 0 | |
| self.cc = 0 | |
| self.dd = self.z | |
| class ListOps: | |
| def __init__(self, x: List[int], n: int) -> None: | |
| self.a = len(x) | |
| self.b = x[n] | |
| self.c = [y + 1 for y in x] | |
| class TupleOps: | |
| def __init__(self, t: Tuple[int, str]) -> None: | |
| x, y = t | |
| self.x = x | |
| self.y = t[0] | |
| s = x, y | |
| self.z = s | |
| class IfExpr: | |
| def __init__(self, x: int) -> None: | |
| self.a = 1 if x < 5 else 2 | |
| class Base: | |
| def __init__(self, x: int) -> None: | |
| self.x = x | |
| class Derived1(Base): | |
| def __init__(self, y: int) -> None: | |
| self.aa = y | |
| super().__init__(y) | |
| self.bb = y | |
| class Derived2(Base): | |
| pass | |
| class Conditionals: | |
| def __init__(self, b: bool, n: int) -> None: | |
| if not (n == 5 or n >= n + 1): | |
| self.a = b | |
| else: | |
| self.a = not b | |
| if b: | |
| self.b = 2 | |
| else: | |
| self.b = 4 | |
| [out] | |
| C: [x] | |
| AttributeRef: [aa, bb, cc, dd] | |
| ListOps: [a, b, c] | |
| TupleOps: [x, y, z] | |
| IfExpr: [a] | |
| Base: [x] | |
| Derived1: [aa, bb, x] | |
| Derived2: [x] | |
| Conditionals: [a, b] | |
| [case testAlwaysDefinedStatements] | |
| from typing import Any, List, Optional, Iterable | |
| class Return: | |
| def __init__(self, x: int) -> None: | |
| self.x = x | |
| if x > 5: | |
| self.y = 1 | |
| return | |
| self.y = 2 | |
| self.z = x | |
| class While: | |
| def __init__(self, x: int) -> None: | |
| n = 2 | |
| while x > 0: | |
| n *=2 | |
| x -= 1 | |
| self.a = n | |
| while x < 5: | |
| self.b = 1 | |
| self.b += 1 | |
| class Try: | |
| def __init__(self, x: List[int]) -> None: | |
| self.a = 0 | |
| try: | |
| self.b = x[0] | |
| except: | |
| self.c = x | |
| self.d = 0 | |
| try: | |
| self.e = x[0] | |
| except: | |
| self.e = 1 | |
| class TryFinally: | |
| def __init__(self, x: List[int]) -> None: | |
| self.a = 0 | |
| try: | |
| self.b = x[0] | |
| finally: | |
| self.c = x | |
| self.d = 0 | |
| try: | |
| self.e = x[0] | |
| finally: | |
| self.e = 1 | |
| class Assert: | |
| def __init__(self, x: Optional[str], y: int) -> None: | |
| assert x is not None | |
| assert y < 5 | |
| self.a = x | |
| class For: | |
| def __init__(self, it: Iterable[int]) -> None: | |
| self.x = 0 | |
| for x in it: | |
| self.x += x | |
| for x in it: | |
| self.y = x | |
| class Assignment1: | |
| def __init__(self, other: Assignment1) -> None: | |
| self.x = 0 | |
| self = other # Give up after assignment to self | |
| self.y = 1 | |
| class Assignment2: | |
| def __init__(self) -> None: | |
| self.x = 0 | |
| other = self # Give up after self is aliased | |
| self.y = other.x | |
| class With: | |
| def __init__(self, x: Any) -> None: | |
| self.a = 0 | |
| with x: | |
| self.b = 1 | |
| self.c = 2 | |
| def f() -> None: | |
| pass | |
| [out] | |
| Return: [x, y] | |
| While: [a] | |
| -- We could infer 'e' as always defined, but this is tricky, since always defined attribute | |
| -- analysis must be performed earlier than exception handling transform. This would be | |
| -- easy to infer *after* exception handling transform. | |
| Try: [a, d] | |
| -- Again, 'e' could be always defined, but it would be a bit tricky to do it. | |
| TryFinally: [a, c, d] | |
| Assert: [a] | |
| For: [x] | |
| Assignment1: [x] | |
| Assignment2: [x] | |
| -- TODO: Why is not 'b' included? | |
| With: [a, c] | |
| [case testAlwaysDefinedAttributeDefaults] | |
| class Basic: | |
| x = 0 | |
| class ClassBodyAndInit: | |
| x = 0 | |
| s = 'x' | |
| def __init__(self, n: int) -> None: | |
| self.n = 0 | |
| class AttrWithDefaultAndInit: | |
| x = 0 | |
| def __init__(self, x: int) -> None: | |
| self.x = x | |
| class Base: | |
| x = 0 | |
| y = 1 | |
| class Derived(Base): | |
| y = 2 | |
| z = 3 | |
| [out] | |
| Basic: [x] | |
| ClassBodyAndInit: [n, s, x] | |
| AttrWithDefaultAndInit: [x] | |
| Base: [x, y] | |
| Derived: [x, y, z] | |
| [case testAlwaysDefinedWithInheritance] | |
| class Base: | |
| def __init__(self, x: int) -> None: | |
| self.x = x | |
| class Deriv1(Base): | |
| def __init__(self, x: int, y: str) -> None: | |
| super().__init__(x) | |
| self.y = y | |
| class Deriv2(Base): | |
| def __init__(self, x: int, y: str) -> None: | |
| self.y = y | |
| super().__init__(x) | |
| class Deriv22(Deriv2): | |
| def __init__(self, x: int, y: str, z: bool) -> None: | |
| super().__init__(x, y) | |
| self.z = False | |
| class Deriv3(Base): | |
| def __init__(self) -> None: | |
| super().__init__(1) | |
| class Deriv4(Base): | |
| def __init__(self) -> None: | |
| self.y = 1 | |
| self.x = 2 | |
| def f(a): pass | |
| class BaseUnsafe: | |
| def __init__(self, x: int, y: int) -> None: | |
| self.x = x | |
| f(self) # Unknown function | |
| self.y = y | |
| class DerivUnsafe(BaseUnsafe): | |
| def __init__(self, z: int, zz: int) -> None: | |
| self.z = z | |
| super().__init__(1, 2) # Calls unknown function | |
| self.zz = zz | |
| class BaseWithDefault: | |
| x = 1 | |
| def __init__(self) -> None: | |
| self.y = 1 | |
| class DerivedWithDefault(BaseWithDefault): | |
| def __init__(self) -> None: | |
| super().__init__() | |
| self.z = 1 | |
| class AlwaysDefinedInBase: | |
| def __init__(self) -> None: | |
| self.x = 1 | |
| self.y = 1 | |
| class UndefinedInDerived(AlwaysDefinedInBase): | |
| def __init__(self, x: bool) -> None: | |
| self.x = 1 | |
| if x: | |
| self.y = 2 | |
| class UndefinedInDerived2(UndefinedInDerived): | |
| def __init__(self, x: bool): | |
| if x: | |
| self.y = 2 | |
| [out] | |
| Base: [x] | |
| Deriv1: [x, y] | |
| Deriv2: [x, y] | |
| Deriv22: [x, y, z] | |
| Deriv3: [x] | |
| Deriv4: [x, y] | |
| BaseUnsafe: [x] | |
| DerivUnsafe: [x, z] | |
| BaseWithDefault: [x, y] | |
| DerivedWithDefault: [x, y, z] | |
| AlwaysDefinedInBase: [] | |
| UndefinedInDerived: [] | |
| UndefinedInDerived2: [] | |
| [case testAlwaysDefinedWithInheritance2] | |
| from mypy_extensions import trait, mypyc_attr | |
| from interpreted import PythonBase | |
| class BasePartiallyDefined: | |
| def __init__(self, x: int) -> None: | |
| self.a = 0 | |
| if x: | |
| self.x = x | |
| class Derived1(BasePartiallyDefined): | |
| def __init__(self, x: int) -> None: | |
| super().__init__(x) | |
| self.y = x | |
| class BaseUndefined: | |
| x: int | |
| class DerivedAlwaysDefined(BaseUndefined): | |
| def __init__(self) -> None: | |
| super().__init__() | |
| self.z = 0 | |
| self.x = 2 | |
| @trait | |
| class MyTrait: | |
| def f(self) -> None: pass | |
| class SimpleTraitImpl(MyTrait): | |
| def __init__(self) -> None: | |
| super().__init__() | |
| self.x = 0 | |
| @trait | |
| class TraitWithAttr: | |
| x: int | |
| y: str | |
| class TraitWithAttrImpl(TraitWithAttr): | |
| def __init__(self) -> None: | |
| self.y = 'x' | |
| @trait | |
| class TraitWithAttr2: | |
| z: int | |
| class TraitWithAttrImpl2(TraitWithAttr, TraitWithAttr2): | |
| def __init__(self) -> None: | |
| self.y = 'x' | |
| self.z = 2 | |
| @mypyc_attr(allow_interpreted_subclasses=True) | |
| class BaseWithGeneralSubclassing: | |
| x = 0 | |
| y: int | |
| def __init__(self, s: str) -> None: | |
| self.s = s | |
| class Derived2(BaseWithGeneralSubclassing): | |
| def __init__(self) -> None: | |
| super().__init__('x') | |
| self.z = 0 | |
| class SubclassPythonclass(PythonBase): | |
| def __init__(self) -> None: | |
| self.y = 1 | |
| class BaseWithSometimesDefined: | |
| def __init__(self, b: bool) -> None: | |
| if b: | |
| self.x = 0 | |
| class Derived3(BaseWithSometimesDefined): | |
| def __init__(self, b: bool) -> None: | |
| super().__init__(b) | |
| self.x = 1 | |
| [file interpreted.py] | |
| class PythonBase: | |
| def __init__(self) -> None: | |
| self.x = 0 | |
| [out] | |
| BasePartiallyDefined: [a] | |
| Derived1: [a, y] | |
| BaseUndefined: [] | |
| DerivedAlwaysDefined: [x, z] | |
| MyTrait: [] | |
| SimpleTraitImpl: [x] | |
| TraitWithAttr: [] | |
| TraitWithAttrImpl: [y] | |
| TraitWithAttr2: [] | |
| TraitWithAttrImpl2: [y, z] | |
| BaseWithGeneralSubclassing: [] | |
| -- TODO: 's' could also be always defined | |
| Derived2: [x, z] | |
| -- Always defined attribute analysis is turned off when inheriting a non-native class. | |
| SubclassPythonclass: [] | |
| BaseWithSometimesDefined: [] | |
| -- TODO: 'x' could also be always defined, but it is a bit tricky to support | |
| Derived3: [] | |
| [case testAlwaysDefinedWithNesting] | |
| class NestedFunc: | |
| def __init__(self) -> None: | |
| self.x = 0 | |
| def f() -> None: | |
| self.y = 0 | |
| f() | |
| self.z = 1 | |
| [out] | |
| -- TODO: Support nested functions. | |
| NestedFunc: [] | |
| f___init___NestedFunc_obj: [] | |
| [case testAlwaysDefinedWithVecSetItem] | |
| from librt.vecs import vec | |
| class C: | |
| def __init__(self, v: vec[C]) -> None: | |
| self.v = v | |
| self.v[0] = self | |
| [out] | |
| C: [v] |