diff --git a/DOCS.md b/DOCS.md index 664a1b0a..a3d7441a 100644 --- a/DOCS.md +++ b/DOCS.md @@ -1737,7 +1737,7 @@ The syntax for a statement lambda is ``` [async|match|copyclosure] def (arguments) => statement; statement; ... ``` -where `arguments` can be standard function arguments or [pattern-matching function definition](#pattern-matching-functions) arguments and `statement` can be an assignment statement or a keyword statement. Note that the `async`, `match`, and [`copyclosure`](#copyclosure-functions) keywords can be combined and can be in any order. +where `arguments` can be standard function arguments or [pattern-matching function definition](#pattern-matching-functions) arguments and `statement` can be any non-compound statement—that is, any statement that doesn't open a code block below it (so `def x => assert x` is fine but `def x => if x: True` is not). Note that the `async`, `match`, and [`copyclosure`](#copyclosure-functions) keywords can be combined and can be in any order. If the last `statement` (not followed by a semicolon) in a statement lambda is an `expression`, it will automatically be returned. @@ -3805,9 +3805,9 @@ _Can’t be done quickly without Coconut’s iterable indexing, which requires m #### `reduce` -**reduce**(_function_, _iterable_[, _initial_], /) +**reduce**(_function_, _iterable_[, _initial_]) -Coconut re-introduces Python 2's `reduce` built-in, using the `functools.reduce` version. +Coconut re-introduces Python 2's `reduce` built-in, using the `functools.reduce` version. Additionally, unlike `functools.reduce`, Coconut's `reduce` always supports keyword arguments. ##### Python Docs @@ -3937,9 +3937,9 @@ result = itertools.zip_longest(range(5), range(10)) #### `takewhile` -**takewhile**(_predicate_, _iterable_, /) +**takewhile**(_predicate_, _iterable_) -Coconut provides `itertools.takewhile` as a built-in under the name `takewhile`. +Coconut provides `itertools.takewhile` as a built-in under the name `takewhile`. Additionally, unlike `itertools.takewhile`, Coconut's `takewhile` always supports keyword arguments. ##### Python Docs @@ -3971,9 +3971,9 @@ negatives = itertools.takewhile(lambda x: x < 0, numiter) #### `dropwhile` -**dropwhile**(_predicate_, _iterable_, /) +**dropwhile**(_predicate_, _iterable_) -Coconut provides `itertools.dropwhile` as a built-in under the name `dropwhile`. +Coconut provides `itertools.dropwhile` as a built-in under the name `dropwhile`. Additionally, unlike `itertools.dropwhile`, Coconut's `dropwhile` always supports keyword arguments. ##### Python Docs diff --git a/coconut/compiler/templates/header.py_template b/coconut/compiler/templates/header.py_template index 0c4d503e..33fb4b4b 100644 --- a/coconut/compiler/templates/header.py_template +++ b/coconut/compiler/templates/header.py_template @@ -62,7 +62,7 @@ class _coconut{object}:{COMMENT.EVERYTHING_HERE_MUST_BE_COPIED_TO_STUB_FILE} fmappables = list, tuple, dict, set, frozenset, bytes, bytearray abc.Sequence.register(collections.deque) Ellipsis, NotImplemented, NotImplementedError, Exception, AttributeError, ImportError, IndexError, KeyError, NameError, TypeError, ValueError, StopIteration, RuntimeError, all, any, bool, bytes, callable, chr, classmethod, complex, dict, enumerate, filter, float, frozenset, getattr, hasattr, hash, id, int, isinstance, issubclass, iter, len, list, locals, globals, map, min, max, next, object, ord, property, range, reversed, set, setattr, slice, str, sum, super, tuple, type, vars, zip, repr, print{comma_bytearray} = Ellipsis, NotImplemented, NotImplementedError, Exception, AttributeError, ImportError, IndexError, KeyError, NameError, TypeError, ValueError, StopIteration, RuntimeError, all, any, bool, bytes, callable, chr, classmethod, complex, dict, enumerate, filter, float, frozenset, getattr, hasattr, hash, id, int, isinstance, issubclass, iter, len, list, locals, globals, map, {lstatic}min{rstatic}, {lstatic}max{rstatic}, next, object, ord, property, range, reversed, set, setattr, slice, str, sum, {lstatic}super{rstatic}, tuple, type, vars, zip, {lstatic}repr{rstatic}, {lstatic}print{rstatic}{comma_bytearray} -@_coconut.functools.wraps(_coconut.functools.partial) +@_coconut_wraps(_coconut.functools.partial) def _coconut_partial(_coconut_func, *args, **kwargs): partial_func = _coconut.functools.partial(_coconut_func, *args, **kwargs) partial_func.__name__ = _coconut.getattr(_coconut_func, "__name__", None) @@ -182,7 +182,7 @@ class _coconut_tail_call(_coconut_baseclass): return (self.__class__, (self.func, self.args, self.kwargs)) _coconut_tco_func_dict = _coconut.weakref.WeakValueDictionary() def _coconut_tco(func): - @_coconut.functools.wraps(func) + @_coconut_wraps(func) def tail_call_optimized_func(*args, **kwargs): call_func = func while True:{COMMENT.weakrefs_necessary_for_ignoring_functools_wraps_decorators} @@ -209,7 +209,7 @@ def _coconut_tco(func): tail_call_optimized_func.__qualname__ = _coconut.getattr(func, "__qualname__", None) _coconut_tco_func_dict[_coconut.id(tail_call_optimized_func)] = tail_call_optimized_func return tail_call_optimized_func -@_coconut.functools.wraps(_coconut.itertools.tee) +@_coconut_wraps(_coconut.itertools.tee) def tee(iterable, n=2): if n < 0: raise _coconut.ValueError("tee: n cannot be negative") @@ -2220,7 +2220,22 @@ class _coconut_SupportsInv(_coconut.typing.Protocol): """ def __invert__(self): raise _coconut.NotImplementedError("Protocol methods cannot be called at runtime ((~) in a typing context is a Protocol)") +@_coconut_wraps(_coconut.functools.reduce) +def reduce(function, iterable, initial=_coconut_sentinel): + if initial is _coconut_sentinel: + return _coconut.functools.reduce(function, iterable) + return _coconut.functools.reduce(function, iterable, initial) +class takewhile(_coconut.itertools.takewhile{comma_object}): + __slots__ = () + __doc__ = _coconut.itertools.takewhile.__doc__ + def __new__(cls, predicate, iterable): + return _coconut.itertools.takewhile.__new__(cls, predicate, iterable) +class dropwhile(_coconut.itertools.dropwhile{comma_object}): + __slots__ = () + __doc__ = _coconut.itertools.dropwhile.__doc__ + def __new__(cls, predicate, iterable): + return _coconut.itertools.dropwhile.__new__(cls, predicate, iterable) {def_async_map} {def_aliases} _coconut_self_match_types = {self_match_types} -_coconut_Expected, _coconut_MatchError, _coconut_cartesian_product, _coconut_count, _coconut_cycle, _coconut_enumerate, _coconut_flatten, _coconut_fmap, _coconut_filter, _coconut_groupsof, _coconut_ident, _coconut_lift, _coconut_map, _coconut_mapreduce, _coconut_multiset, _coconut_range, _coconut_reiterable, _coconut_reversed, _coconut_scan, _coconut_starmap, _coconut_tee, _coconut_windowsof, _coconut_zip, _coconut_zip_longest, TYPE_CHECKING, reduce, takewhile, dropwhile = Expected, MatchError, cartesian_product, count, cycle, enumerate, flatten, fmap, filter, groupsof, ident, lift, map, mapreduce, multiset, range, reiterable, reversed, scan, starmap, tee, windowsof, zip, zip_longest, False, _coconut.functools.reduce, _coconut.itertools.takewhile, _coconut.itertools.dropwhile{COMMENT.anything_added_here_should_be_copied_to_stub_file} +TYPE_CHECKING, _coconut_Expected, _coconut_MatchError, _coconut_cartesian_product, _coconut_count, _coconut_cycle, _coconut_enumerate, _coconut_flatten, _coconut_fmap, _coconut_filter, _coconut_groupsof, _coconut_ident, _coconut_lift, _coconut_map, _coconut_mapreduce, _coconut_multiset, _coconut_range, _coconut_reiterable, _coconut_reversed, _coconut_scan, _coconut_starmap, _coconut_tee, _coconut_windowsof, _coconut_zip, _coconut_zip_longest = False, Expected, MatchError, cartesian_product, count, cycle, enumerate, flatten, fmap, filter, groupsof, ident, lift, map, mapreduce, multiset, range, reiterable, reversed, scan, starmap, tee, windowsof, zip, zip_longest{COMMENT.anything_added_here_should_be_copied_to_stub_file} diff --git a/coconut/root.py b/coconut/root.py index 53f26ea2..8e906330 100644 --- a/coconut/root.py +++ b/coconut/root.py @@ -59,11 +59,23 @@ def _get_target_info(target): # HEADER: # ----------------------------------------------------------------------------------------------------------------------- +_base_header = r''' +import functools as _coconut_functools +_coconut_getattr = getattr +def _coconut_wraps(base_func): + def wrap(new_func): + new_func_module = _coconut_getattr(new_func, "__module__") + _coconut_functools.update_wrapper(new_func, base_func) + if new_func_module is not None: + new_func.__module__ = new_func_module + return new_func + return wrap +''' + # if a new assignment is added below, a new builtins import should be added alongside it _base_py3_header = r'''from builtins import chr, dict, hex, input, int, map, object, oct, open, print, range, str, super, zip, filter, reversed, enumerate, repr py_bytes, py_chr, py_dict, py_hex, py_input, py_int, py_map, py_object, py_oct, py_open, py_print, py_range, py_str, py_super, py_zip, py_filter, py_reversed, py_enumerate, py_repr, py_min, py_max = bytes, chr, dict, hex, input, int, map, object, oct, open, print, range, str, super, zip, filter, reversed, enumerate, repr, min, max _coconut_py_str, _coconut_py_super, _coconut_py_dict, _coconut_py_min, _coconut_py_max = str, super, dict, min, max -from functools import wraps as _coconut_wraps exec("_coconut_exec = exec") ''' @@ -71,7 +83,6 @@ def _get_target_info(target): _base_py2_header = r'''from __builtin__ import chr, dict, hex, input, int, map, object, oct, open, print, range, str, super, zip, filter, reversed, enumerate, raw_input, xrange, repr, long py_bytes, py_chr, py_dict, py_hex, py_input, py_int, py_map, py_object, py_oct, py_open, py_print, py_range, py_str, py_super, py_zip, py_filter, py_reversed, py_enumerate, py_raw_input, py_xrange, py_repr, py_min, py_max = bytes, chr, dict, hex, input, int, map, object, oct, open, print, range, str, super, zip, filter, reversed, enumerate, raw_input, xrange, repr, min, max _coconut_py_raw_input, _coconut_py_xrange, _coconut_py_int, _coconut_py_long, _coconut_py_print, _coconut_py_str, _coconut_py_super, _coconut_py_unicode, _coconut_py_repr, _coconut_py_dict, _coconut_py_bytes, _coconut_py_min, _coconut_py_max = raw_input, xrange, int, long, print, str, super, unicode, repr, dict, bytes, min, max -from functools import wraps as _coconut_wraps from collections import Sequence as _coconut_Sequence from future_builtins import * chr, str = unichr, unicode @@ -353,7 +364,7 @@ def __repr__(self): __doc__ = getattr(_coconut_py_dict, "__doc__", "")''' + _finish_dict_def _py26_extras = '''if _coconut_sys.version_info < (2, 7): - import functools as _coconut_functools, copy_reg as _coconut_copy_reg + import copy_reg as _coconut_copy_reg def _coconut_new_partial(func, args, keywords): return _coconut_functools.partial(func, *(args if args is not None else ()), **(keywords if keywords is not None else {})) _coconut_copy_reg.constructor(_coconut_new_partial) @@ -392,7 +403,7 @@ def _get_root_header(version="universal"): ''' + _indent(_get_root_header("3")) version_info = _get_target_info(version) - header = "" + header = _base_header if version.startswith("3"): header += _base_py3_header diff --git a/coconut/tests/main_test.py b/coconut/tests/main_test.py index 32df6e82..b0eb72e6 100644 --- a/coconut/tests/main_test.py +++ b/coconut/tests/main_test.py @@ -168,6 +168,7 @@ def pexpect(p, out): "tutorial.py", "unused 'type: ignore' comment", "site-packages/numpy", + ".py: error:" ) ignore_atexit_errors_with = ( diff --git a/coconut/tests/src/cocotest/agnostic/primary_2.coco b/coconut/tests/src/cocotest/agnostic/primary_2.coco index 72ad4e95..7d164584 100644 --- a/coconut/tests/src/cocotest/agnostic/primary_2.coco +++ b/coconut/tests/src/cocotest/agnostic/primary_2.coco @@ -488,6 +488,9 @@ def primary_test_2() -> bool: assert False assert x == 12 assert (x=1).__match_args__ == ('x',) # type: ignore + assert reduce(function=(+), iterable=range(5), initial=-1) == 9 # type: ignore + assert takewhile(predicate=ident, iterable=[1, 2, 1, 0, 1]) |> list == [1, 2, 1] # type: ignore + assert dropwhile(predicate=(not), iterable=range(5)) |> list == [1, 2, 3, 4] # type: ignore with process_map.multiple_sequential_calls(): # type: ignore assert map((+), range(3), range(4)$[:-1], strict=True) |> list == [0, 2, 4] == process_map((+), range(3), range(4)$[:-1], strict=True) |> list # type: ignore