-- 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: []