| -- 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, List, Set, Optional, cast |
| from typing_extensions import Final |
| |
| 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_extensions 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: [F, 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: [] |