1
1
import builtins
2
2
import collections
3
3
import contextlib
4
+ import inspect
4
5
import logging
5
6
import re
6
7
import sys
49
50
LINE_KW ,
50
51
NAME_KW ,
51
52
NS_KW ,
53
+ SYM_ABSTRACT_META_KEY ,
52
54
SYM_ASYNC_META_KEY ,
53
55
SYM_CLASSMETHOD_META_KEY ,
54
56
SYM_DEFAULT_META_KEY ,
@@ -623,6 +625,7 @@ def has_meta_prop(o: Union[IMeta, Var]) -> bool:
623
625
return has_meta_prop
624
626
625
627
628
+ _is_artificially_abstract = _meta_getter (SYM_ABSTRACT_META_KEY )
626
629
_is_async = _meta_getter (SYM_ASYNC_META_KEY )
627
630
_is_mutable = _meta_getter (SYM_MUTABLE_META_KEY )
628
631
_is_py_classmethod = _meta_getter (SYM_CLASSMETHOD_META_KEY )
@@ -1512,24 +1515,57 @@ def __deftype_or_reify_impls( # pylint: disable=too-many-branches,too-many-loca
1512
1515
_var_is_protocol = _meta_getter (VAR_IS_PROTOCOL_META_KEY )
1513
1516
1514
1517
1518
+ def __is_deftype_member (mem ) -> bool :
1519
+ """Return True if `mem` names a valid `deftype*` member."""
1520
+ return (
1521
+ inspect .isfunction (mem )
1522
+ or isinstance (mem , (property , staticmethod ))
1523
+ or inspect .ismethod (mem )
1524
+ )
1525
+
1526
+
1527
+ def __is_reify_member (mem ) -> bool :
1528
+ """Return True if `mem` names a valid `reify*` member."""
1529
+ return inspect .isfunction (mem ) or isinstance (mem , property )
1530
+
1531
+
1515
1532
def __deftype_and_reify_impls_are_all_abstract ( # pylint: disable=too-many-branches,too-many-locals
1516
1533
special_form : sym .Symbol ,
1517
1534
fields : Iterable [str ],
1518
1535
interfaces : Iterable [DefTypeBase ],
1519
1536
members : Iterable [DefTypeMember ],
1520
- ) -> bool :
1521
- """Return True if all `deftype*` or `reify*` super-types can be verified abstract
1522
- statically. Return False otherwise.
1537
+ ) -> Tuple [bool , lset .Set [DefTypeBase ]]:
1538
+ """Return a tuple of two items indicating the abstractness of the `deftype*` or
1539
+ `reify*` super-types. The first element is a boolean value which, if True,
1540
+ indicates that all bases have been statically verified abstract. If False, that
1541
+ value indicates at least one base could not be statically verified. The second
1542
+ element is the set of all super-types which have been marked as artificially
1543
+ abstract.
1523
1544
1524
1545
In certain cases, such as in macro definitions and potentially inside of
1525
1546
functions, the compiler will be unable to resolve the named super-type as an
1526
1547
object during compilation and these checks will need to be deferred to runtime.
1527
1548
In these cases, the compiler will wrap the emitted class in a decorator that
1528
1549
performs the checks when the class is compiled by the Python compiler.
1529
1550
1551
+ The Python ecosystem is much less strict with its use of `abc.ABC` to define
1552
+ interfaces than Java (which has a native `interface` construct), so even in cases
1553
+ where a type may be _in practice_ an interface or ABC, the compiler would not
1554
+ permit you to declare such types as supertypes because they do not themselves
1555
+ inherit from `abc.ABC`. In these cases, users can mark the type as artificially
1556
+ abstract with the `:abstract` metadata key.
1557
+
1530
1558
For normal compile-time errors, an `AnalyzerException` will be raised."""
1531
1559
assert special_form in {SpecialForm .DEFTYPE , SpecialForm .REIFY }
1532
1560
1561
+ unverifiably_abstract = set ()
1562
+ artificially_abstract : Set [DefTypeBase ] = set ()
1563
+ artificially_abstract_base_members : Set [str ] = set ()
1564
+ is_member = {
1565
+ SpecialForm .DEFTYPE : __is_deftype_member ,
1566
+ SpecialForm .REIFY : __is_reify_member ,
1567
+ }[special_form ]
1568
+
1533
1569
field_names = frozenset (fields )
1534
1570
member_names = frozenset (deftype_or_reify_python_member_names (members ))
1535
1571
all_member_names = field_names .union (member_names )
@@ -1547,7 +1583,10 @@ def __deftype_and_reify_impls_are_all_abstract( # pylint: disable=too-many-bran
1547
1583
f"{ special_form } interface Var '{ interface .form } ' is not bound"
1548
1584
"and cannot be checked for abstractness; deferring to runtime" ,
1549
1585
)
1550
- return False
1586
+ unverifiably_abstract .add (interface )
1587
+ if _is_artificially_abstract (interface .form ):
1588
+ artificially_abstract .add (interface )
1589
+ continue
1551
1590
1552
1591
# Protocols are defined as maps, with the interface being simply a member
1553
1592
# of the map, denoted by the keyword `:interface`.
@@ -1561,54 +1600,72 @@ def __deftype_and_reify_impls_are_all_abstract( # pylint: disable=too-many-bran
1561
1600
if interface_type is object :
1562
1601
continue
1563
1602
1564
- if not is_abstract (interface_type ):
1565
- raise AnalyzerException (
1566
- f"{ special_form } interface must be Python abstract class or object" ,
1567
- form = interface .form ,
1568
- lisp_ast = interface ,
1603
+ if is_abstract (interface_type ):
1604
+ interface_names : FrozenSet [str ] = interface_type .__abstractmethods__
1605
+ interface_property_names : FrozenSet [str ] = frozenset (
1606
+ method
1607
+ for method in interface_names
1608
+ if isinstance (getattr (interface_type , method ), property )
1569
1609
)
1610
+ interface_method_names = interface_names - interface_property_names
1611
+ if not interface_method_names .issubset (member_names ):
1612
+ missing_methods = ", " .join (interface_method_names - member_names )
1613
+ raise AnalyzerException (
1614
+ f"{ special_form } definition missing interface members for "
1615
+ f"interface { interface .form } : { missing_methods } " ,
1616
+ form = interface .form ,
1617
+ lisp_ast = interface ,
1618
+ )
1619
+ elif not interface_property_names .issubset (all_member_names ):
1620
+ missing_fields = ", " .join (interface_property_names - field_names )
1621
+ raise AnalyzerException (
1622
+ f"{ special_form } definition missing interface properties for "
1623
+ f"interface { interface .form } : { missing_fields } " ,
1624
+ form = interface .form ,
1625
+ lisp_ast = interface ,
1626
+ )
1570
1627
1571
- interface_names : FrozenSet [str ] = interface_type .__abstractmethods__
1572
- interface_property_names : FrozenSet [str ] = frozenset (
1573
- method
1574
- for method in interface_names
1575
- if isinstance (getattr (interface_type , method ), property )
1576
- )
1577
- interface_method_names = interface_names - interface_property_names
1578
- if not interface_method_names .issubset (member_names ):
1579
- missing_methods = ", " .join (interface_method_names - member_names )
1580
- raise AnalyzerException (
1581
- f"{ special_form } definition missing interface members for "
1582
- f"interface { interface .form } : { missing_methods } " ,
1583
- form = interface .form ,
1584
- lisp_ast = interface ,
1628
+ all_interface_methods .update (interface_names )
1629
+ elif _is_artificially_abstract (interface .form ):
1630
+ # Given that artificially abstract bases aren't real `abc.ABC`s and do
1631
+ # not annotate their `abstractmethod`s, we can't assert right now that
1632
+ # any the type will satisfy the artificially abstract base. However,
1633
+ # we can collect any defined methods into a set for artificial bases
1634
+ # and assert that any extra methods are included in that set below.
1635
+ artificially_abstract .add (interface )
1636
+ artificially_abstract_base_members .update (
1637
+ map (
1638
+ lambda v : v [0 ],
1639
+ inspect .getmembers (interface_type , predicate = is_member ),
1640
+ )
1585
1641
)
1586
- elif not interface_property_names .issubset (all_member_names ):
1587
- missing_fields = ", " .join (interface_property_names - field_names )
1642
+ else :
1588
1643
raise AnalyzerException (
1589
- f"{ special_form } definition missing interface properties for "
1590
- f"interface { interface .form } : { missing_fields } " ,
1644
+ f"{ special_form } interface must be Python abstract class or object" ,
1591
1645
form = interface .form ,
1592
1646
lisp_ast = interface ,
1593
1647
)
1594
1648
1595
- all_interface_methods .update (interface_names )
1596
-
1597
- extra_methods = member_names - all_interface_methods - OBJECT_DUNDER_METHODS
1598
- if extra_methods :
1599
- extra_method_str = ", " .join (extra_methods )
1600
- raise AnalyzerException (
1601
- f"{ special_form } definition for interface includes members not "
1602
- f"part of defined interfaces: { extra_method_str } "
1603
- )
1649
+ # We cannot compute if there are extra methods defined if there are any
1650
+ # unverifiably abstract bases, so we just skip this check.
1651
+ if not unverifiably_abstract :
1652
+ extra_methods = member_names - all_interface_methods - OBJECT_DUNDER_METHODS
1653
+ if extra_methods and not extra_methods .issubset (
1654
+ artificially_abstract_base_members
1655
+ ):
1656
+ extra_method_str = ", " .join (extra_methods )
1657
+ raise AnalyzerException (
1658
+ f"{ special_form } definition for interface includes members not "
1659
+ f"part of defined interfaces: { extra_method_str } "
1660
+ )
1604
1661
1605
- return True
1662
+ return not unverifiably_abstract , lset . set ( artificially_abstract )
1606
1663
1607
1664
1608
1665
__DEFTYPE_DEFAULT_SENTINEL = object ()
1609
1666
1610
1667
1611
- def _deftype_ast ( # pylint: disable=too-many-branches
1668
+ def _deftype_ast ( # pylint: disable=too-many-branches,too-many-locals
1612
1669
ctx : AnalyzerContext , form : ISeq
1613
1670
) -> DefType :
1614
1671
assert form .first == SpecialForm .DEFTYPE
@@ -1684,7 +1741,10 @@ def _deftype_ast( # pylint: disable=too-many-branches
1684
1741
interfaces , members = __deftype_or_reify_impls (
1685
1742
ctx , runtime .nthrest (form , 3 ), SpecialForm .DEFTYPE
1686
1743
)
1687
- verified_abstract = __deftype_and_reify_impls_are_all_abstract (
1744
+ (
1745
+ verified_abstract ,
1746
+ artificially_abstract ,
1747
+ ) = __deftype_and_reify_impls_are_all_abstract (
1688
1748
SpecialForm .DEFTYPE , map (lambda f : f .name , fields ), interfaces , members
1689
1749
)
1690
1750
return DefType (
@@ -1694,6 +1754,7 @@ def _deftype_ast( # pylint: disable=too-many-branches
1694
1754
fields = vec .vector (param_nodes ),
1695
1755
members = vec .vector (members ),
1696
1756
verified_abstract = verified_abstract ,
1757
+ artificially_abstract = artificially_abstract ,
1697
1758
is_frozen = is_frozen ,
1698
1759
env = ctx .get_node_env (pos = ctx .syntax_position ),
1699
1760
)
@@ -2552,14 +2613,18 @@ def _reify_ast(ctx: AnalyzerContext, form: ISeq) -> Reify:
2552
2613
interfaces , members = __deftype_or_reify_impls (
2553
2614
ctx , runtime .nthrest (form , 1 ), SpecialForm .REIFY
2554
2615
)
2555
- verified_abstract = __deftype_and_reify_impls_are_all_abstract (
2616
+ (
2617
+ verified_abstract ,
2618
+ artificially_abstract ,
2619
+ ) = __deftype_and_reify_impls_are_all_abstract (
2556
2620
SpecialForm .REIFY , (), interfaces , members
2557
2621
)
2558
2622
return Reify (
2559
2623
form = form ,
2560
2624
interfaces = vec .vector (interfaces ),
2561
2625
members = vec .vector (members ),
2562
2626
verified_abstract = verified_abstract ,
2627
+ artificially_abstract = artificially_abstract ,
2563
2628
env = ctx .get_node_env (pos = ctx .syntax_position ),
2564
2629
)
2565
2630
0 commit comments