Major fixes and new features
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
@@ -0,0 +1,732 @@
|
||||
-- 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: []
|
||||
Reference in New Issue
Block a user