733 lines
15 KiB
Plaintext
733 lines
15 KiB
Plaintext
-- 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: []
|