Skip to content

Commit

Permalink
Merge branch 'abstract-property'
Browse files Browse the repository at this point in the history
Implement @abstractproperty. Closes #263.
  • Loading branch information
JukkaL committed Feb 4, 2016
2 parents 891543d + 623e921 commit a103f25
Show file tree
Hide file tree
Showing 7 changed files with 277 additions and 11 deletions.
14 changes: 14 additions & 0 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -1898,6 +1898,20 @@ def visit_decorator(self, e: Decorator) -> Type:
sig = set_callable_name(sig, e.func)
e.var.type = sig
e.var.is_ready = True
if e.func.is_property:
self.check_incompatible_property_override(e)

def check_incompatible_property_override(self, e: Decorator) -> None:
if not e.var.is_settable_property:
name = e.func.name()
for base in e.func.info.mro[1:]:
base_attr = base.names.get(name)
if not base_attr:
continue
if (isinstance(base_attr.node, OverloadedFuncDef) and
base_attr.node.is_property and
base_attr.node.items[0].var.is_settable_property):
self.fail(messages.READ_ONLY_PROPERTY_OVERRIDES_READ_WRITE, e)

def visit_with_stmt(self, s: WithStmt) -> Type:
echk = self.expr_checker
Expand Down
4 changes: 3 additions & 1 deletion mypy/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@
CANNOT_ASSIGN_TO_TYPE = 'Cannot assign to a type'
INCONSISTENT_ABSTRACT_OVERLOAD = \
'Overloaded method has both abstract and non-abstract variants'
READ_ONLY_PROPERTY_OVERRIDES_READ_WRITE = \
'Read-only property cannot override read-write property'
INSTANCE_LAYOUT_CONFLICT = 'Instance layout conflict in multiple inheritance'
FORMAT_REQUIRES_MAPPING = 'Format requires a mapping'
GENERIC_TYPE_NOT_VALID_AS_EXPRESSION = \
Expand Down Expand Up @@ -732,7 +734,7 @@ def cannot_instantiate_abstract_class(self, class_name: str,
context: Context) -> None:
attrs = format_string_list("'%s'" % a for a in abstract_attributes[:5])
self.fail("Cannot instantiate abstract class '%s' with abstract "
"method%s %s" % (class_name, plural_s(abstract_attributes),
"attribute%s %s" % (class_name, plural_s(abstract_attributes),
attrs),
context)

Expand Down
18 changes: 15 additions & 3 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,8 @@ def analyze_property_with_multi_part_definition(self, defn: OverloadedFuncDef) -
if node.name == 'setter':
# The first item represents the entire property.
defn.items[0].var.is_settable_property = True
# Get abstractness from the original definition.
item.func.is_abstract = items[0].func.is_abstract
else:
self.fail("Decorated property not supported", item)
item.func.accept(self)
Expand Down Expand Up @@ -1498,10 +1500,13 @@ def visit_decorator(self, dec: Decorator) -> None:
dec.func.is_class = True
dec.var.is_classmethod = True
self.check_decorated_function_is_method('classmethod', dec)
elif refers_to_fullname(d, 'builtins.property'):
elif (refers_to_fullname(d, 'builtins.property') or
refers_to_fullname(d, 'abc.abstractproperty')):
removed.append(i)
dec.func.is_property = True
dec.var.is_property = True
if refers_to_fullname(d, 'abc.abstractproperty'):
dec.func.is_abstract = True
self.check_decorated_function_is_method('property', dec)
if len(dec.func.arguments) > 1:
self.fail('Too many arguments', dec.func)
Expand Down Expand Up @@ -2353,10 +2358,17 @@ def visit_decorator(self, dec: Decorator) -> None:
"""
super().visit_decorator(dec)
if dec.var.is_property:
# Decorators are expected to have a callable type (it's a little odd).
if dec.func.type is None:
dec.var.type = AnyType()
dec.var.type = CallableType(
[AnyType()],
[ARG_POS],
[None],
AnyType(),
self.builtin_type('function'),
name=dec.var.name())
elif isinstance(dec.func.type, CallableType):
dec.var.type = dec.func.type.ret_type
dec.var.type = dec.func.type
return
decorator_preserves_type = True
for expr in dec.decorators:
Expand Down
220 changes: 214 additions & 6 deletions mypy/test/data/check-abstract.test
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ class B(metaclass=ABCMeta):
@abstractmethod
def f(self): pass
A() # OK
B() # E: Cannot instantiate abstract class 'B' with abstract method 'f'
B() # E: Cannot instantiate abstract class 'B' with abstract attribute 'f'
[out]

[case testInstantiatingClassWithInheritedAbstractMethod]
Expand All @@ -154,7 +154,7 @@ class A(metaclass=ABCMeta):
@abstractmethod
def g(self): pass
class B(A): pass
B()# E: Cannot instantiate abstract class 'B' with abstract methods 'f' and 'g'
B()# E: Cannot instantiate abstract class 'B' with abstract attributes 'f' and 'g'
[out]


Expand Down Expand Up @@ -376,8 +376,8 @@ class D(A, B):
class E(A, B):
def f(self) -> None: pass
def g(self) -> None: pass
C() # E: Cannot instantiate abstract class 'C' with abstract method 'g'
D() # E: Cannot instantiate abstract class 'D' with abstract method 'f'
C() # E: Cannot instantiate abstract class 'C' with abstract attribute 'g'
D() # E: Cannot instantiate abstract class 'D' with abstract attribute 'f'
E()

[case testInconsistentMro]
Expand Down Expand Up @@ -405,7 +405,7 @@ class B(A):
def f(self, x: int) -> int: pass
@overload
def f(self, x: str) -> str: pass
A() # E: Cannot instantiate abstract class 'A' with abstract method 'f'
A() # E: Cannot instantiate abstract class 'A' with abstract attribute 'f'
B()
B().f(1)
a = B() # type: A
Expand All @@ -430,7 +430,7 @@ class B(A):
def f(self, x: int) -> int: pass
@overload
def f(self, x: str) -> str: pass
A() # E: Cannot instantiate abstract class 'A' with abstract method 'f'
A() # E: Cannot instantiate abstract class 'A' with abstract attribute 'f'
B()
B().f(1)
a = B() # type: A
Expand Down Expand Up @@ -521,3 +521,211 @@ class B:
class C:
def __add__(self, other: int) -> B: pass
[out]


-- Abstract properties
-- -------------------


[case testReadOnlyAbstractProperty]
from abc import abstractproperty, ABCMeta
class A(metaclass=ABCMeta):
@abstractproperty
def x(self) -> int: pass
def f(a: A) -> None:
a.x() # E: "int" not callable
a.x = 1 # E: Property "x" defined in "A" is read-only
[out]
main: note: In function "f":

[case testReadOnlyAbstractPropertyForwardRef]
from abc import abstractproperty, ABCMeta
def f(a: A) -> None:
a.x() # E: "int" not callable
a.x = 1 # E: Property "x" defined in "A" is read-only
class A(metaclass=ABCMeta):
@abstractproperty
def x(self) -> int: pass
[out]
main: note: In function "f":

[case testReadWriteAbstractProperty]
from abc import abstractproperty, ABCMeta
def f(a: A) -> None:
a.x.y # E: "int" has no attribute "y"
a.x = 1
class A(metaclass=ABCMeta):
@abstractproperty
def x(self) -> int: pass
@x.setter
def x(self, x: int) -> None: pass
[out]
main: note: In function "f":

[case testInstantiateClassWithReadOnlyAbstractProperty]
from abc import abstractproperty, ABCMeta
class A(metaclass=ABCMeta):
@abstractproperty
def x(self) -> int: pass
class B(A): pass
b = B() # E: Cannot instantiate abstract class 'B' with abstract attribute 'x'

[case testInstantiateClassWithReadWriteAbstractProperty]
from abc import abstractproperty, ABCMeta
class A(metaclass=ABCMeta):
@abstractproperty
def x(self) -> int: pass
@x.setter
def x(self, x: int) -> None: pass
class B(A): pass
b = B() # E: Cannot instantiate abstract class 'B' with abstract attribute 'x'

[case testImplementAbstractPropertyViaProperty]
from abc import abstractproperty, ABCMeta
class A(metaclass=ABCMeta):
@abstractproperty
def x(self) -> int: pass
class B(A):
@property
def x(self) -> int: pass
b = B()
b.x() # E: "int" not callable
[builtins fixtures/property.py]

[case testImplementReradWriteAbstractPropertyViaProperty]
from abc import abstractproperty, ABCMeta
class A(metaclass=ABCMeta):
@abstractproperty
def x(self) -> int: pass
@x.setter
def x(self, v: int) -> None: pass
class B(A):
@property
def x(self) -> int: pass
@x.setter
def x(self, v: int) -> None: pass
b = B()
b.x.y # E: "int" has no attribute "y"
[builtins fixtures/property.py]

[case testImplementAbstractPropertyViaPropertyInvalidType]
from abc import abstractproperty, ABCMeta
class A(metaclass=ABCMeta):
@abstractproperty
def x(self) -> int: pass
class B(A):
@property
def x(self) -> str: pass # E
b = B()
b.x() # E
[builtins fixtures/property.py]
[out]
main: note: In class "B":
main:7: error: Return type of "x" incompatible with supertype "A"
main: note: At top level:
main:9: error: "str" not callable

[case testCantImplementAbstractPropertyViaInstanceVariable]
from abc import abstractproperty, ABCMeta
class A(metaclass=ABCMeta):
@abstractproperty
def x(self) -> int: pass
class B(A):
def __init__(self) -> None:
self.x = 1 # E
b = B() # E
b.x.y # E
[builtins fixtures/property.py]
[out]
main: note: In member "__init__" of class "B":
main:7: error: Property "x" defined in "B" is read-only
main: note: At top level:
main:8: error: Cannot instantiate abstract class 'B' with abstract attribute 'x'
main:9: error: "int" has no attribute "y"

[case testSuperWithAbstractProperty]
from abc import abstractproperty, ABCMeta
class A(metaclass=ABCMeta):
@abstractproperty
def x(self) -> int: pass
class B(A):
@property
def x(self) -> int:
return super().x.y # E: "int" has no attribute "y"
[builtins fixtures/property.py]
[out]
main: note: In member "x" of class "B":

[case testSuperWithReadWriteAbstractProperty]
from abc import abstractproperty, ABCMeta
class A(metaclass=ABCMeta):
@abstractproperty
def x(self) -> int: pass
@x.setter
def x(self, v: int) -> None: pass
class B(A):
@property
def x(self) -> int:
return super().x.y # E
@x.setter
def x(self, v: int) -> None:
super().x = '' # E
[builtins fixtures/property.py]
[out]
main: note: In member "x" of class "B":
main:10: error: "int" has no attribute "y"
main: note: In function "x":
main:13: error: Invalid assignment target

[case testOnlyImplementGetterOfReadWriteAbstractProperty]
from abc import abstractproperty, ABCMeta
class A(metaclass=ABCMeta):
@abstractproperty
def x(self) -> int: pass
@x.setter
def x(self, v: int) -> None: pass
class B(A):
@property # E
def x(self) -> int: pass
b = B()
b.x.y # E
[builtins fixtures/property.py]
[out]
main: note: In class "B":
main:8: error: Read-only property cannot override read-write property
main: note: At top level:
main:11: error: "int" has no attribute "y"

[case testDynamicallyTypedReadOnlyAbstractProperty]
from abc import abstractproperty, ABCMeta
class A(metaclass=ABCMeta):
@abstractproperty
def x(self): pass
def f(a: A) -> None:
a.x.y
a.x = 1 # E: Property "x" defined in "A" is read-only
[out]
main: note: In function "f":

[case testDynamicallyTypedReadOnlyAbstractPropertyForwardRef]
from abc import abstractproperty, ABCMeta
def f(a: A) -> None:
a.x.y
a.x = 1 # E: Property "x" defined in "A" is read-only
class A(metaclass=ABCMeta):
@abstractproperty
def x(self): pass
[out]
main: note: In function "f":

[case testDynamicallyTypedReadWriteAbstractProperty]
from abc import abstractproperty, ABCMeta
def f(a: A) -> None:
a.x.y
a.x = 1
class A(metaclass=ABCMeta):
@abstractproperty
def x(self): pass
@x.setter
def x(self, x): pass
[out]
1 change: 1 addition & 0 deletions mypy/test/data/lib-stub/abc.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
class ABCMeta: pass
abstractmethod = object()
abstractproperty = object()
16 changes: 16 additions & 0 deletions mypy/test/data/python2eval.test
Original file line number Diff line number Diff line change
Expand Up @@ -379,3 +379,19 @@ _program.py:2: error: No overload variant of "f" matches argument types [builtin
def f(x): # type: (str) -> None
pass
f(bytearray('foo'))

[case testAbstractProperty_python2]
from abc import abstractproperty, ABCMeta
class A:
__metaclass__ = ABCMeta
@abstractproperty
def x(self): # type: () -> int
pass
class B(A):
@property
def x(self): # type: () -> int
return 3
b = B()
print b.x + 1
[out]
4
15 changes: 14 additions & 1 deletion mypy/test/data/pythoneval.test
Original file line number Diff line number Diff line change
Expand Up @@ -943,7 +943,6 @@ print(r"""\"""$""")
[out]
'
\"""$
--' (hack to fix syntax highlighting)

[case testSubclassBothGenericAndNonGenericABC]
from typing import Generic, TypeVar
Expand Down Expand Up @@ -1103,3 +1102,17 @@ T = TypeVar('T')
def sorted(x: Iterable[T], *, key: Callable[[T], object] = None) -> None: ...
a = None # type: List[Dict[str, str]]
sorted(a, key=lambda y: y[''])

[case testAbstractProperty]
from abc import abstractproperty, ABCMeta
class A(metaclass=ABCMeta):
@abstractproperty
def x(self) -> int: pass
class B(A):
@property
def x(self) -> int:
return 3
b = B()
print(b.x + 1)
[out]
4

0 comments on commit a103f25

Please sign in to comment.