Skip to content

Commit

Permalink
Backport ExceptionGroup
Browse files Browse the repository at this point in the history
Resolves #789.
  • Loading branch information
evhub committed Oct 10, 2023
1 parent 0c1cada commit 440334b
Show file tree
Hide file tree
Showing 9 changed files with 58 additions and 27 deletions.
1 change: 1 addition & 0 deletions DOCS.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ The full list of optional dependencies is:
- `watch`: enables use of the `--watch` flag.
- `mypy`: enables use of the `--mypy` flag.
- `backports`: installs libraries that backport newer Python features to older versions, which Coconut will automatically use instead of the standard library if the standard library is not available. Specifically:
- Installs [`exceptiongroup`](https://pypi.org/project/exceptiongroup/) to backport [`ExceptionGroup` and `BaseExceptionGroup`](https://docs.python.org/3/library/exceptions.html#ExceptionGroup).
- Installs [`dataclasses`](https://pypi.org/project/dataclasses/) to backport [`dataclasses`](https://docs.python.org/3/library/dataclasses.html).
- Installs [`typing`](https://pypi.org/project/typing/) to backport [`typing`](https://docs.python.org/3/library/typing.html) ([`typing_extensions`](https://pypi.org/project/typing-extensions/) is always installed for backporting individual `typing` objects).
- Installs [`aenum`](https://pypi.org/project/aenum) to backport [`enum`](https://docs.python.org/3/library/enum.html).
Expand Down
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ setup-pypy3:
install: setup
python -m pip install -e .[tests]

.PHONY: install-purepy
install-purepy: setup
python -m pip install --no-deps --upgrade -e . "pyparsing<3"

.PHONY: install-py2
install-py2: setup-py2
python2 -m pip install -e .[tests]
Expand Down
4 changes: 3 additions & 1 deletion coconut/compiler/header.py
Original file line number Diff line number Diff line change
Expand Up @@ -934,7 +934,9 @@ def getheader(which, use_hash, target, no_tco, strict, no_wrap):
newline=True,
).format(**format_dict)

if target_info >= (3, 9):
if target_info >= (3, 11):
header += _get_root_header("311")
elif target_info >= (3, 9):
header += _get_root_header("39")
if target_info >= (3, 7):
header += _get_root_header("37")
Expand Down
10 changes: 4 additions & 6 deletions coconut/compiler/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ def evaluate_tokens(tokens, **kwargs):

class ComputationNode(object):
"""A single node in the computation graph."""
__slots__ = ("action", "original", "loc", "tokens") + (("been_called",) if DEVELOP else ())
__slots__ = ("action", "original", "loc", "tokens")
pprinting = False

def __new__(cls, action, original, loc, tokens, ignore_no_tokens=False, ignore_one_token=False, greedy=False, trim_arity=True):
Expand All @@ -236,8 +236,6 @@ def __new__(cls, action, original, loc, tokens, ignore_no_tokens=False, ignore_o
self.original = original
self.loc = loc
self.tokens = tokens
if DEVELOP:
self.been_called = False
if greedy:
return self.evaluate()
else:
Expand All @@ -253,9 +251,9 @@ def name(self):
def evaluate(self):
"""Get the result of evaluating the computation graph at this node.
Very performance sensitive."""
if DEVELOP: # avoid the overhead of the call if not develop
internal_assert(not self.been_called, "inefficient reevaluation of action " + self.name + " with tokens", self.tokens)
self.been_called = True
# note that this should never cache, since if a greedy Wrap that doesn't add to the packrat context
# hits the cache, it'll get the same ComputationNode object, but since it's greedy that object needs
# to actually be reevaluated
evaluated_toks = evaluate_tokens(self.tokens)
if logger.tracing: # avoid the overhead of the call if not tracing
logger.log_trace(self.name, self.original, self.loc, evaluated_toks, self.tokens)
Expand Down
9 changes: 8 additions & 1 deletion coconut/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -583,7 +583,9 @@ def get_path_env_var(env_var, default):
'__file__',
'__annotations__',
'__debug__',
# # don't include builtins that aren't always made available by Coconut:
# we treat these as coconut_exceptions so the highlighter will always know about them:
# 'ExceptionGroup', 'BaseExceptionGroup',
# don't include builtins that aren't always made available by Coconut:
# 'BlockingIOError', 'ChildProcessError', 'ConnectionError',
# 'BrokenPipeError', 'ConnectionAbortedError', 'ConnectionRefusedError',
# 'ConnectionResetError', 'FileExistsError', 'FileNotFoundError',
Expand Down Expand Up @@ -807,8 +809,11 @@ def get_path_env_var(env_var, default):

coconut_exceptions = (
"MatchError",
"ExceptionGroup",
"BaseExceptionGroup",
)

highlight_builtins = coconut_specific_builtins + interp_only_builtins
all_builtins = frozenset(python_builtins + coconut_specific_builtins + coconut_exceptions)

magic_methods = (
Expand Down Expand Up @@ -938,6 +943,7 @@ def get_path_env_var(env_var, default):
("dataclasses", "py==36"),
("typing", "py<35"),
("async_generator", "py35"),
("exceptiongroup", "py37"),
),
"dev": (
("pre-commit", "py3"),
Expand Down Expand Up @@ -994,6 +1000,7 @@ def get_path_env_var(env_var, default):
("xonsh", "py38"): (0, 14),
("pytest", "py36"): (7,),
("async_generator", "py35"): (1, 10),
("exceptiongroup", "py37"): (1,),

# pinned reqs: (must be added to pinned_reqs below)

Expand Down
5 changes: 2 additions & 3 deletions coconut/highlighter.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@
from pygments.util import shebang_matches

from coconut.constants import (
coconut_specific_builtins,
interp_only_builtins,
highlight_builtins,
new_operators,
tabideal,
default_encoding,
Expand Down Expand Up @@ -94,7 +93,7 @@ class CoconutLexer(Python3Lexer):
(words(reserved_vars, prefix=r"(?<!\\):?", suffix=r"\b"), Keyword),
]
tokens["builtins"] += [
(words(coconut_specific_builtins + interp_only_builtins, suffix=r"\b"), Name.Builtin),
(words(highlight_builtins, suffix=r"\b"), Name.Builtin),
(words(coconut_exceptions, suffix=r"\b"), Name.Exception),
]
tokens["numbers"] = [
Expand Down
34 changes: 29 additions & 5 deletions coconut/root.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
VERSION = "3.0.3"
VERSION_NAME = None
# False for release, int >= 1 for develop
DEVELOP = 8
DEVELOP = 9
ALPHA = False # for pre releases rather than post releases

assert DEVELOP is False or DEVELOP >= 1, "DEVELOP must be False or an int >= 1"
Expand All @@ -45,6 +45,16 @@ def _indent(code, by=1, tabsize=4, strip=False, newline=False, initial_newline=F
) + ("\n" if newline else "")


def _get_target_info(target):
"""Return target information as a version tuple."""
if not target or target == "universal":
return ()
elif len(target) == 1:
return (int(target),)
else:
return (int(target[0]), int(target[1:]))


# -----------------------------------------------------------------------------------------------------------------------
# HEADER:
# -----------------------------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -264,15 +274,24 @@ def _coconut_reduce_partial(self):
_coconut_copy_reg.pickle(_coconut_functools.partial, _coconut_reduce_partial)
'''

_py3_before_py311_extras = '''try:
from exceptiongroup import ExceptionGroup, BaseExceptionGroup
except ImportError:
class you_need_to_install_exceptiongroup(object):
__slots__ = ()
ExceptionGroup = BaseExceptionGroup = you_need_to_install_exceptiongroup()
'''


# whenever new versions are added here, header.py must be updated to use them
ROOT_HEADER_VERSIONS = (
"universal",
"2",
"3",
"27",
"3",
"37",
"39",
"311",
)


Expand All @@ -284,6 +303,7 @@ def _get_root_header(version="universal"):
''' + _indent(_get_root_header("2")) + '''else:
''' + _indent(_get_root_header("3"))

version_info = _get_target_info(version)
header = ""

if version.startswith("3"):
Expand All @@ -293,7 +313,7 @@ def _get_root_header(version="universal"):
# if a new assignment is added below, a new builtins import should be added alongside it
header += _base_py2_header

if version in ("37", "39"):
if version_info >= (3, 7):
header += r'''py_breakpoint = breakpoint
'''
elif version == "3":
Expand All @@ -311,7 +331,7 @@ def _get_root_header(version="universal"):
header += r'''if _coconut_sys.version_info < (3, 7):
''' + _indent(_below_py37_extras) + r'''elif _coconut_sys.version_info < (3, 9):
''' + _indent(_py37_py38_extras)
elif version == "37":
elif (3, 7) <= version_info < (3, 9):
header += r'''if _coconut_sys.version_info < (3, 9):
''' + _indent(_py37_py38_extras)
elif version.startswith("2"):
Expand All @@ -320,7 +340,11 @@ def _get_root_header(version="universal"):
dict.items = _coconut_OrderedDict.viewitems
'''
else:
assert version == "39", version
assert version_info >= (3, 9), version

if (3,) <= version_info < (3, 11):
header += r'''if _coconut_sys.version_info < (3, 11):
''' + _indent(_py3_before_py311_extras)

return header

Expand Down
4 changes: 3 additions & 1 deletion coconut/tests/src/cocotest/agnostic/specific.coco
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ from io import StringIO
if TYPE_CHECKING:
from typing import Any

from .util import mod # NOQA
from .util import mod, assert_raises # NOQA


def non_py26_test() -> bool:
Expand Down Expand Up @@ -181,6 +181,8 @@ def py37_spec_test() -> bool:
class HasVarGen[*Ts] # type: ignore
assert HasVarGen `issubclass` object
assert typing.Protocol.__module__ == "typing_extensions"
assert_raises((def -> raise ExceptionGroup("derp", [Exception("herp")])), ExceptionGroup)
assert_raises((def -> raise BaseExceptionGroup("derp", [BaseException("herp")])), BaseExceptionGroup)
return True


Expand Down
14 changes: 4 additions & 10 deletions coconut/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
except ImportError:
lru_cache = None

from coconut.root import _get_target_info
from coconut.constants import (
fixpath,
default_encoding,
Expand Down Expand Up @@ -265,6 +266,9 @@ def ensure_dir(dirpath):
# -----------------------------------------------------------------------------------------------------------------------


get_target_info = _get_target_info


def ver_tuple_to_str(req_ver):
"""Converts a requirement version tuple into a version string."""
return ".".join(str(x) for x in req_ver)
Expand All @@ -287,16 +291,6 @@ def get_next_version(req_ver, point_to_increment=-1):
return req_ver[:point_to_increment] + (req_ver[point_to_increment] + 1,)


def get_target_info(target):
"""Return target information as a version tuple."""
if not target:
return ()
elif len(target) == 1:
return (int(target),)
else:
return (int(target[0]), int(target[1:]))


def get_displayable_target(target):
"""Get a displayable version of the target."""
try:
Expand Down

0 comments on commit 440334b

Please sign in to comment.