From e5f5122d018c8f5a63aa26931346c7c15caf474c Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Sat, 8 Jun 2024 22:31:58 -0700 Subject: [PATCH 01/26] Reenable develop --- coconut/root.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coconut/root.py b/coconut/root.py index 9c5e80b5..de086cbd 100644 --- a/coconut/root.py +++ b/coconut/root.py @@ -26,7 +26,7 @@ VERSION = "3.1.1" VERSION_NAME = None # False for release, int >= 1 for develop -DEVELOP = False +DEVELOP = 1 ALPHA = False # for pre releases rather than post releases assert DEVELOP is False or DEVELOP >= 1, "DEVELOP must be False or an int >= 1" From b516218a7f5b575b3928f1a37d6a96902ffe559f Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Mon, 10 Jun 2024 23:59:59 -0700 Subject: [PATCH 02/26] Change error message --- coconut/compiler/compiler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coconut/compiler/compiler.py b/coconut/compiler/compiler.py index 458d6c28..0fb12c37 100644 --- a/coconut/compiler/compiler.py +++ b/coconut/compiler/compiler.py @@ -4575,7 +4575,7 @@ def string_atom_handle(self, original, loc, tokens, allow_silent_concat=False): return tokens[0] else: if not allow_silent_concat: - self.strict_err_or_warn("found Python-style implicit string concatenation (use explicit '+' instead)", original, loc) + self.strict_err_or_warn("found implicit string concatenation (use explicit '+' instead)", original, loc) if any(s.endswith(")") for s in tokens): # has .format() calls # parens are necessary for string_atom_handle return "(" + " + ".join(tokens) + ")" From 916d4f5b4fc6d8620e0f81b4d4c98c10d37fb58e Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Mon, 24 Jun 2024 01:25:55 -0700 Subject: [PATCH 03/26] Add undefined name warning Resolves #843. --- coconut/compiler/compiler.py | 70 +++++++++++++------ coconut/constants.py | 7 +- coconut/root.py | 2 +- .../src/cocotest/agnostic/primary_1.coco | 4 +- .../tests/src/cocotest/agnostic/tutorial.coco | 6 +- .../tests/src/cocotest/target_2/py2_test.coco | 2 +- coconut/tests/src/extras.coco | 10 +++ 7 files changed, 72 insertions(+), 29 deletions(-) diff --git a/coconut/compiler/compiler.py b/coconut/compiler/compiler.py index 0fb12c37..8aaf2496 100644 --- a/coconut/compiler/compiler.py +++ b/coconut/compiler/compiler.py @@ -246,12 +246,17 @@ def import_stmt(imp_from, imp, imp_as, raw=False): ) -def imported_names(imports): - """Yields all the names imported by imports = [[imp1], [imp2, as], ...].""" +def get_imported_names(imports): + """Returns all the names imported by imports = [[imp1], [imp2, as], ...] and whether there is a star import.""" + saw_names = [] + saw_star = False for imp in imports: imp_name = imp[-1].split(".", 1)[0] - if imp_name != "*": - yield imp_name + if imp_name == "*": + saw_star = True + else: + saw_names.append(imp_name) + return saw_names, saw_star def special_starred_import_handle(imp_all=False): @@ -529,7 +534,8 @@ def reset(self, keep_state=False, filename=None): # but always overwrite temp_vars_by_key since they store locs that will be invalidated self.temp_vars_by_key = {} self.parsing_context = defaultdict(list) - self.unused_imports = defaultdict(list) + self.name_info = defaultdict(lambda: {"imported": [], "referenced": [], "assigned": []}) + self.star_import = False self.kept_lines = [] self.num_lines = 0 self.disable_name_check = False @@ -942,6 +948,11 @@ def strict_err(self, *args, **kwargs): if self.strict: raise self.make_err(CoconutStyleError, *args, **kwargs) + def strict_warn(self, *args, **kwargs): + internal_assert("extra" not in kwargs, "cannot pass extra=... to strict_warn") + if self.strict: + self.syntax_warning(*args, extra="remove --strict to dismiss", **kwargs) + def syntax_warning(self, message, original, loc, **kwargs): """Show a CoconutSyntaxWarning. Usage: self.syntax_warning(message, original, loc) @@ -1319,21 +1330,30 @@ def streamline(self, grammars, inputstring=None, force=False, inner=False): elif inputstring is not None and not inner: logger.log("No streamlining done for input of length {length}.".format(length=input_len)) + def qa_error(self, msg, original, loc): + """Strict error or warn an error that should be disabled by a NOQA comment.""" + ln = self.adjust(lineno(loc, original)) + comment = self.reformat(" ".join(self.comments[ln]), ignore_errors=True) + if not self.noqa_regex.search(comment): + self.strict_err_or_warn( + msg + " (add '# NOQA' to suppress)", + original, + loc, + endpoint=False, + ) + def run_final_checks(self, original, keep_state=False): """Run post-parsing checks to raise any necessary errors/warnings.""" - # only check for unused imports if we're not keeping state accross parses + # only check for unused imports/etc. if we're not keeping state accross parses if not keep_state: - for name, locs in self.unused_imports.items(): - for loc in locs: - ln = self.adjust(lineno(loc, original)) - comment = self.reformat(" ".join(self.comments[ln]), ignore_errors=True) - if not self.noqa_regex.search(comment): - self.strict_err_or_warn( - "found unused import " + repr(self.reformat(name, ignore_errors=True)) + " (add '# NOQA' to suppress)", - original, - loc, - endpoint=False, - ) + for name, info in self.name_info.items(): + if info["imported"] and not info["referenced"]: + for loc in info["imported"]: + self.qa_error("found unused import " + repr(self.reformat(name, ignore_errors=True)), original, loc) + if not self.star_import: # only check for undefined names when there are no * imports + if name not in all_builtins and info["referenced"] and not (info["assigned"] or info["imported"]): + for loc in info["referenced"]: + self.qa_error("found undefined name " + repr(self.reformat(name, ignore_errors=True)), original, loc) def parse_line_by_line(self, init_parser, line_parser, original): """Apply init_parser then line_parser repeatedly.""" @@ -3731,13 +3751,17 @@ def import_handle(self, original, loc, tokens): else: raise CoconutInternalException("invalid import tokens", tokens) imports = list(imports) - if imp_from == "*" or imp_from is None and "*" in imports: + imported_names, star_import = get_imported_names(imports) + self.star_import = self.star_import or star_import + if star_import: + self.strict_warn("found * import; these disable Coconut's undefined name detection", original, loc) + if imp_from == "*" or (imp_from is None and star_import): if not (len(imports) == 1 and imports[0] == "*"): raise self.make_err(CoconutSyntaxError, "only [from *] import * allowed, not from * import name", original, loc) self.syntax_warning("[from *] import * is a Coconut Easter egg and should not be used in production code", original, loc) return special_starred_import_handle(imp_all=bool(imp_from)) - for imp_name in imported_names(imports): - self.unused_imports[imp_name].append(loc) + for imp_name in imported_names: + self.name_info[imp_name]["imported"].append(loc) return self.universal_import(loc, imports, imp_from=imp_from) def complex_raise_stmt_handle(self, loc, tokens): @@ -4989,8 +5013,10 @@ def name_handle(self, original, loc, tokens, assign=False, classname=False, expr ) return typevars[name] - if not assign: - self.unused_imports.pop(name, None) + if assign: + self.name_info[name]["assigned"].append(loc) + else: + self.name_info[name]["referenced"].append(loc) if ( assign diff --git a/coconut/constants.py b/coconut/constants.py index 4b10de3b..02a91269 100644 --- a/coconut/constants.py +++ b/coconut/constants.py @@ -607,15 +607,20 @@ def get_path_env_var(env_var, default): "tuple", "type", "vars", "zip", + 'Ellipsis', "__import__", '__name__', '__file__', '__annotations__', '__debug__', + '__build_class__', + '__loader__', + '__package__', + '__spec__', ) python_exceptions = ( - "BaseException", "BaseExceptionGroup", "GeneratorExit", "KeyboardInterrupt", "SystemExit", "Exception", "ArithmeticError", "FloatingPointError", "OverflowError", "ZeroDivisionError", "AssertionError", "AttributeError", "BufferError", "EOFError", "ExceptionGroup", "BaseExceptionGroup", "ImportError", "ModuleNotFoundError", "LookupError", "IndexError", "KeyError", "MemoryError", "NameError", "UnboundLocalError", "OSError", "BlockingIOError", "ChildProcessError", "ConnectionError", "BrokenPipeError", "ConnectionAbortedError", "ConnectionRefusedError", "ConnectionResetError", "FileExistsError", "FileNotFoundError", "InterruptedError", "IsADirectoryError", "NotADirectoryError", "PermissionError", "ProcessLookupError", "TimeoutError", "ReferenceError", "RuntimeError", "NotImplementedError", "RecursionError", "StopAsyncIteration", "StopIteration", "SyntaxError", "IndentationError", "TabError", "SystemError", "TypeError", "ValueError", "UnicodeError", "UnicodeDecodeError", "UnicodeEncodeError", "UnicodeTranslateError", "Warning", "BytesWarning", "DeprecationWarning", "EncodingWarning", "FutureWarning", "ImportWarning", "PendingDeprecationWarning", "ResourceWarning", "RuntimeWarning", "SyntaxWarning", "UnicodeWarning", "UserWarning", + 'ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BaseExceptionGroup', 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning', 'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError', 'ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning', 'EOFError', 'EncodingWarning', 'EnvironmentError', 'Exception', 'ExceptionGroup', 'FileExistsError', 'FileNotFoundError', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'InterruptedError', 'IsADirectoryError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'ModuleNotFoundError', 'NameError', 'NotADirectoryError', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError', 'RecursionError', 'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning', 'StopAsyncIteration', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'TimeoutError', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'WindowsError', 'ZeroDivisionError' ) always_keep_parse_name_prefix = "HAS_" diff --git a/coconut/root.py b/coconut/root.py index de086cbd..0dfbd741 100644 --- a/coconut/root.py +++ b/coconut/root.py @@ -26,7 +26,7 @@ VERSION = "3.1.1" VERSION_NAME = None # False for release, int >= 1 for develop -DEVELOP = 1 +DEVELOP = 2 ALPHA = False # for pre releases rather than post releases assert DEVELOP is False or DEVELOP >= 1, "DEVELOP must be False or an int >= 1" diff --git a/coconut/tests/src/cocotest/agnostic/primary_1.coco b/coconut/tests/src/cocotest/agnostic/primary_1.coco index bfe7888c..299e3e7e 100644 --- a/coconut/tests/src/cocotest/agnostic/primary_1.coco +++ b/coconut/tests/src/cocotest/agnostic/primary_1.coco @@ -90,7 +90,7 @@ def primary_test_1() -> bool: \\assert data == 3 \\def backslash_test(): return (x) -> x - assert \(1) == 1 == backslash_test()(1) + assert \(1) == 1 == backslash_test()(1) # NOQA assert True is (\( "hello" ) == "hello" == \( @@ -100,7 +100,7 @@ def primary_test_1() -> bool: x, y): return x + y - assert multiline_backslash_test(1, 2) == 3 + assert multiline_backslash_test(1, 2) == 3 # noqa \\ assert True class one_line_class: pass assert isinstance(one_line_class(), one_line_class) diff --git a/coconut/tests/src/cocotest/agnostic/tutorial.coco b/coconut/tests/src/cocotest/agnostic/tutorial.coco index 3eeabae3..cd96b0a0 100644 --- a/coconut/tests/src/cocotest/agnostic/tutorial.coco +++ b/coconut/tests/src/cocotest/agnostic/tutorial.coco @@ -22,13 +22,15 @@ assert range(1, 5) |> product == 24 first_five_words = .split() ..> .$[:5] ..> " ".join assert first_five_words("ab cd ef gh ij kl") == "ab cd ef gh ij" -@recursive_iterator +# TODO: recursive_iterator -> recursive_generator +@recursive_iterator # noqa def fib() = (1, 1) :: map((+), fib(), fib()$[1:]) assert fib()$[:5] |> list == [1, 1, 2, 3, 5] +# TODO: parallel_map -> process_map # can't use parallel_map here otherwise each process would have to rerun all # the tutorial tests since we don't guard them behind __name__ == "__main__" -assert range(100) |> concurrent_map$(.**2) |> list |> .$[-1] == 9801 +assert range(100) |> thread_map$(.**2) |> list |> .$[-1] == 9801 def factorial(n, acc=1): match n: diff --git a/coconut/tests/src/cocotest/target_2/py2_test.coco b/coconut/tests/src/cocotest/target_2/py2_test.coco index cf8ef713..b9711f61 100644 --- a/coconut/tests/src/cocotest/target_2/py2_test.coco +++ b/coconut/tests/src/cocotest/target_2/py2_test.coco @@ -4,5 +4,5 @@ def py2_test() -> bool: assert py_map((+)$(2), range(5)) == [2, 3, 4, 5, 6] assert py_range(5) == [0, 1, 2, 3, 4] assert not isinstance(long(1), py_int) # type: ignore - assert py_str(3) == b"3" == unicode(b"3") # type: ignore + assert py_str(3) == b"3" == unicode(b"3") # noqa # type: ignore return True diff --git a/coconut/tests/src/extras.coco b/coconut/tests/src/extras.coco index 13c69496..12a1560d 100644 --- a/coconut/tests/src/extras.coco +++ b/coconut/tests/src/extras.coco @@ -414,6 +414,16 @@ import abc except CoconutStyleError as err: assert str(err) == """found unused import 'abc' (add '# NOQA' to suppress) (remove --strict to downgrade to a warning) (line 1) import abc""" + try: + parse(""" +1 +2 + x +3 + """.strip()) + except CoconutStyleError as err: + assert str(err) == """found undefined name 'x' (add '# NOQA' to suppress) (remove --strict to downgrade to a warning) (line 2) + 2 + x + ^""" assert_raises(-> parse(""" class A(object): 1 From 7ea01637cc12ec1aa07838cf7f3d8cee3fdff59c Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Mon, 22 Jul 2024 14:09:30 -0700 Subject: [PATCH 04/26] Allow disabling use of __coconut_cache__ --- coconut/_pyparsing.py | 4 ++-- coconut/constants.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/coconut/_pyparsing.py b/coconut/_pyparsing.py index f3101d42..c0cb6932 100644 --- a/coconut/_pyparsing.py +++ b/coconut/_pyparsing.py @@ -46,7 +46,7 @@ never_clear_incremental_cache, warn_on_multiline_regex, num_displayed_timing_items, - use_cache_file, + use_pyparsing_cache_file, use_line_by_line_parser, incremental_use_hybrid, ) @@ -254,7 +254,7 @@ def enableIncremental(*args, **kwargs): and hasattr(MatchFirst, "setAdaptiveMode") ) -USE_CACHE = SUPPORTS_INCREMENTAL and use_cache_file +USE_CACHE = SUPPORTS_INCREMENTAL and use_pyparsing_cache_file USE_LINE_BY_LINE = USE_COMPUTATION_GRAPH and use_line_by_line_parser if MODERN_PYPARSING: diff --git a/coconut/constants.py b/coconut/constants.py index 02a91269..3b154568 100644 --- a/coconut/constants.py +++ b/coconut/constants.py @@ -135,7 +135,7 @@ def get_path_env_var(env_var, default): streamline_grammar_for_len = 1536 -use_cache_file = True +use_pyparsing_cache_file = True adaptive_any_of_env_var = "COCONUT_ADAPTIVE_ANY_OF" use_adaptive_any_of = get_bool_env_var(adaptive_any_of_env_var, True) @@ -638,7 +638,7 @@ def get_path_env_var(env_var, default): main_prompt = ">>> " more_prompt = " " -default_use_cache_dir = PY34 +default_use_cache_dir = get_bool_env_var("COCONUT_USE_COCONUT_CACHE", PY34) coconut_cache_dir = "__coconut_cache__" mypy_path_env_var = "MYPYPATH" From 3d7577e99f0a022e85ecfa272ee937e6a3cefb48 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Thu, 25 Jul 2024 19:41:33 -0700 Subject: [PATCH 05/26] Improve pattern-matching Resolves #847, #848. --- DOCS.md | 6 ++- coconut/compiler/grammar.py | 15 ++++-- coconut/compiler/matching.py | 50 +++++++++++++------ coconut/root.py | 2 +- .../src/cocotest/agnostic/primary_2.coco | 21 ++++++++ 5 files changed, 72 insertions(+), 22 deletions(-) diff --git a/DOCS.md b/DOCS.md index a341d3a4..a3472835 100644 --- a/DOCS.md +++ b/DOCS.md @@ -1204,6 +1204,7 @@ base_pattern ::= ( | NAME "(" patterns ")" # classes or data types | "data" NAME "(" patterns ")" # data types | "class" NAME "(" patterns ")" # classes + | "(" name "=" pattern ... ")" # anonymous named tuples | "{" pattern_pairs # dictionaries ["," "**" (NAME | "{}")] "}" # (keys must be constants or equality checks) | ["s" | "f" | "m"] "{" @@ -1269,7 +1270,8 @@ base_pattern ::= ( - Classes or Data Types (`()`): will match as a data type if given [a Coconut `data` type](#data) (or a tuple of Coconut data types) and a class otherwise. - Data Types (`data ()`): will check that whatever is in that position is of data type `` and will match the attributes to ``. Generally, `data ()` will match any data type that could have been constructed with `makedata(, )`. Includes support for positional arguments, named arguments, default arguments, and starred arguments. Also supports strict attributes by prepending a dot to the attribute name that raises `AttributError` if the attribute is not present rather than failing the match (e.g. `data MyData(.my_attr=)`). - Classes (`class ()`): does [PEP-634-style class matching](https://www.python.org/dev/peps/pep-0634/#class-patterns). Also supports strict attribute matching as above. -- Mapping Destructuring: + - Anonymous Named Tuples (`(=, ...)`): checks that the object is a `tuple` of the given length with the given attributes. For matching [anonymous `namedtuple`s](#anonymous-namedtuples). +- Dict Destructuring: - Dicts (`{: , ...}`): will match any mapping (`collections.abc.Mapping`) with the given keys and values that match the value patterns. Keys must be constants or equality checks. - Dicts With Rest (`{, **}`): will match a mapping (`collections.abc.Mapping`) containing all the ``, and will put a `dict` of everything else into ``. If `` is `{}`, will enforce that the mapping is exactly the same length as ``. - Set Destructuring: @@ -2233,7 +2235,7 @@ as a shorthand for f(long_variable_name=long_variable_name) ``` -Such syntax is also supported in [partial application](#partial-application) and [anonymous `namedtuple`s](#anonymous-namedtuples). +Such syntax is also supported in [partial application](#partial-application), [anonymous `namedtuple`s](#anonymous-namedtuples), and [`class`/`data`/anonymous `namedtuple` patterns](#match). _Deprecated: Coconut also supports `f(...=long_variable_name)` as an alternative shorthand syntax._ diff --git a/coconut/compiler/grammar.py b/coconut/compiler/grammar.py index 7e5bd8e6..f7947e9e 100644 --- a/coconut/compiler/grammar.py +++ b/coconut/compiler/grammar.py @@ -1995,8 +1995,17 @@ class Grammar(object): del_stmt = addspace(keyword("del") - simple_assignlist) - matchlist_data_item = Group(Optional(star | Optional(dot) + unsafe_name + equals) + match) - matchlist_data = Group(Optional(tokenlist(matchlist_data_item, comma))) + interior_name_match = labeled_group(setname, "var") + matchlist_anon_named_tuple_item = ( + Group(Optional(dot) + unsafe_name) + equals + match + | Group(Optional(dot) + interior_name_match) + equals + ) + matchlist_data_item = ( + matchlist_anon_named_tuple_item + | Optional(star) + match + ) + matchlist_data = Group(Optional(tokenlist(Group(matchlist_data_item), comma))) + matchlist_anon_named_tuple = Optional(tokenlist(Group(matchlist_anon_named_tuple_item), comma)) match_check_equals = Forward() match_check_equals_ref = equals @@ -2031,7 +2040,6 @@ class Grammar(object): match_tuple = Group(lparen + matchlist_tuple + rparen.suppress()) match_lazy = Group(lbanana + matchlist_list + rbanana.suppress()) - interior_name_match = labeled_group(setname, "var") match_string = interleaved_tokenlist( # f_string_atom must come first f_string_atom("f_string") | fixed_len_string_tokens("string"), @@ -2085,6 +2093,7 @@ class Grammar(object): | (keyword("data").suppress() + dotted_refname + lparen.suppress() + matchlist_data + rparen.suppress())("data") | (keyword("class").suppress() + dotted_refname + lparen.suppress() + matchlist_data + rparen.suppress())("class") | (dotted_refname + lparen.suppress() + matchlist_data + rparen.suppress())("data_or_class") + | (lparen.suppress() + matchlist_anon_named_tuple + rparen.suppress())("anon_named_tuple") | Optional(keyword("as").suppress()) + setname("var"), ) diff --git a/coconut/compiler/matching.py b/coconut/compiler/matching.py index 9690dc9d..7f6c4d55 100644 --- a/coconut/compiler/matching.py +++ b/coconut/compiler/matching.py @@ -150,6 +150,7 @@ class Matcher(object): "data": lambda self: self.match_data, "class": lambda self: self.match_class, "data_or_class": lambda self: self.match_data_or_class, + "anon_named_tuple": lambda self: self.match_anon_named_tuple, "paren": lambda self: self.match_paren, "as": lambda self: self.match_as, "and": lambda self: self.match_and, @@ -1056,10 +1057,8 @@ def match_set(self, tokens, item): for const in match: self.add_check(const + " in " + item) - def split_data_or_class_match(self, tokens): - """Split data/class match tokens into cls_name, pos_matches, name_matches, star_match.""" - cls_name, matches = tokens - + def split_data_or_class_matches(self, matches): + """Split data/class match tokens into pos_matches, name_matches, star_match.""" pos_matches = [] name_matches = {} star_match = None @@ -1073,8 +1072,7 @@ def split_data_or_class_match(self, tokens): raise CoconutDeferredSyntaxError("positional arg after keyword arg in data/class match", self.loc) pos_matches.append(match) # starred arg - elif len(match_arg) == 2: - internal_assert(match_arg[0] == "*", "invalid starred data/class match arg tokens", match_arg) + elif len(match_arg) == 2 and match_arg[0] == "*": _, match = match_arg if star_match is not None: raise CoconutDeferredSyntaxError("duplicate starred arg in data/class match", self.loc) @@ -1083,23 +1081,30 @@ def split_data_or_class_match(self, tokens): star_match = match # keyword arg else: + internal_assert(match_arg[1] == "=", "invalid keyword data/class match arg tokens", match_arg) if len(match_arg) == 3: - internal_assert(match_arg[1] == "=", "invalid keyword data/class match arg tokens", match_arg) - name, _, match = match_arg - strict = False - elif len(match_arg) == 4: - internal_assert(match_arg[0] == "." and match_arg[2] == "=", "invalid strict keyword data/class match arg tokens", match_arg) - _, name, _, match = match_arg - strict = True + name_grp, _, match = match_arg + elif len(match_arg) == 2: + match_grp, _ = match_arg + match = match_grp[-1] + name, = match + name_grp = match_grp[:-1] + [name] else: raise CoconutInternalException("invalid data/class match arg", match_arg) + if len(name_grp) == 1: + name, = name_grp + strict = False + else: + internal_assert(name_grp[0] == ".", "invalid keyword data/class match arg tokens", name_grp) + _, name = name_grp + strict = True if star_match is not None: raise CoconutDeferredSyntaxError("both keyword arg and starred arg in data/class match", self.loc) if name in name_matches: raise CoconutDeferredSyntaxError("duplicate keyword arg {name!r} in data/class match".format(name=name), self.loc) name_matches[name] = (match, strict) - return cls_name, pos_matches, name_matches, star_match + return pos_matches, name_matches, star_match def match_class_attr(self, match, attr, item): """Match an attribute for a class match where attr is an expression that evaluates to the attribute name.""" @@ -1119,7 +1124,8 @@ def match_class_names(self, name_matches, item): def match_class(self, tokens, item): """Matches a class PEP-622-style.""" - cls_name, pos_matches, name_matches, star_match = self.split_data_or_class_match(tokens) + cls_name, matches = tokens + pos_matches, name_matches, star_match = self.split_data_or_class_matches(matches) self.add_check("_coconut.isinstance(" + item + ", " + cls_name + ")") @@ -1191,7 +1197,8 @@ def match_class(self, tokens, item): def match_data(self, tokens, item): """Matches a data type.""" - cls_name, pos_matches, name_matches, star_match = self.split_data_or_class_match(tokens) + cls_name, matches = tokens + pos_matches, name_matches, star_match = self.split_data_or_class_matches(matches) self.add_check("_coconut.isinstance(" + item + ", " + cls_name + ")") @@ -1240,6 +1247,17 @@ def match_data(self, tokens, item): with self.down_a_level(): self.add_check(temp_var) + def match_anon_named_tuple(self, tokens, item): + """Matches an anonymous named tuple pattern.""" + pos_matches, name_matches, star_match = self.split_data_or_class_matches(tokens) + internal_assert(not pos_matches and not star_match, "got invalid pos/star matches in anon named tuple pattern", (pos_matches, star_match)) + self.add_check("_coconut.isinstance(" + item + ", tuple)") + self.add_check("_coconut.len({item}) == {expected_len}".format( + item=item, + expected_len=len(name_matches), + )) + self.match_class_names(name_matches, item) + def match_data_or_class(self, tokens, item): """Matches an ambiguous data or class match.""" cls_name, matches = tokens diff --git a/coconut/root.py b/coconut/root.py index 0dfbd741..5a4e12e8 100644 --- a/coconut/root.py +++ b/coconut/root.py @@ -26,7 +26,7 @@ VERSION = "3.1.1" VERSION_NAME = None # False for release, int >= 1 for develop -DEVELOP = 2 +DEVELOP = 3 ALPHA = False # for pre releases rather than post releases assert DEVELOP is False or DEVELOP >= 1, "DEVELOP must be False or an int >= 1" diff --git a/coconut/tests/src/cocotest/agnostic/primary_2.coco b/coconut/tests/src/cocotest/agnostic/primary_2.coco index e95fa4c6..f4238643 100644 --- a/coconut/tests/src/cocotest/agnostic/primary_2.coco +++ b/coconut/tests/src/cocotest/agnostic/primary_2.coco @@ -466,6 +466,27 @@ def primary_test_2() -> bool: """ == '\n"2"\n' assert f"\{1}" == "\\1" assert f''' '{1}' ''' == " '1' " + tuple(x=) = (x=4) + assert x == 4 + tuple(x=, y=) = (x=5, y=5) + assert x == 5 == y + data tuple(x=) = (x=6) + assert x == 6 + class tuple(x=) = (x=7) + assert x == 7 + data tuple(x, y=) = (x=8, y=8) + assert x == 8 == y + (x=, y=) = (x=9, y=9) + assert x == 9 == y + (x=x) = (x=10) + assert x == 10 + (x=, y=y) = (x=11, y=11) + assert x == 11 == y + tuple(x=) = (x=12, y=12) + assert x == 12 + match (x=) in (x=13, y=13): + assert False + assert x == 12 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 From 5f8bc6c21ac5c6122c60567f57529e4aec8172da Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Fri, 26 Jul 2024 13:55:15 -0700 Subject: [PATCH 06/26] Fix mypy test error --- coconut/tests/src/cocotest/agnostic/suite.coco | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coconut/tests/src/cocotest/agnostic/suite.coco b/coconut/tests/src/cocotest/agnostic/suite.coco index ae651af8..3589aedf 100644 --- a/coconut/tests/src/cocotest/agnostic/suite.coco +++ b/coconut/tests/src/cocotest/agnostic/suite.coco @@ -178,7 +178,7 @@ def suite_test() -> bool: assert init_last([1,2,3]) == ([1,2], 3) assert last_two([1,2,3]) == (2, 3) == last_two_([1,2,3]) assert expl_ident(5) == 5 == ident(5) - assert mod$ <| 5 <| 3 == 2 == (%)$ <| 5 <| 3 + assert mod$ <| 5 <| 3 == 2 == (%)$ <| 5 <| 3 # type: ignore assert 5 |> dectest == 5 try: raise ValueError() From b10f86dbd0fb35379200567a2efc0dbbc83f8997 Mon Sep 17 00:00:00 2001 From: pardouin <116360248+pardouin@users.noreply.github.com> Date: Mon, 29 Jul 2024 00:20:15 +0200 Subject: [PATCH 07/26] Update DOCS.md Fixed a typo. --- DOCS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DOCS.md b/DOCS.md index a3472835..2270efb4 100644 --- a/DOCS.md +++ b/DOCS.md @@ -1006,7 +1006,7 @@ Coconut also allows a single `?` before attribute access, function calling, part When using a `None`-aware operator for member access, either for a method or an attribute, the syntax is `obj?.method()` or `obj?.attr` respectively. `obj?.attr` is equivalent to `obj.attr if obj is not None else obj`. This does not prevent an `AttributeError` if `attr` is not an attribute or method of `obj`. -The `None`-aware indexing operator is used identically to normal indexing, using `?[]` instead of `[]`. `seq?[index]` is equivalent to the expression `seq[index] is seq is not None else seq`. Using this operator will not prevent an `IndexError` if `index` is outside the bounds of `seq`. +The `None`-aware indexing operator is used identically to normal indexing, using `?[]` instead of `[]`. `seq?[index]` is equivalent to the expression `seq[index] if seq is not None else seq`. Using this operator will not prevent an `IndexError` if `index` is outside the bounds of `seq`. Coconut also supports None-aware [pipe operators](#pipes) and [function composition pipes](#function-composition). From 414806bfa0f7ab320f324c10af663c31d5794365 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Sun, 18 Aug 2024 01:06:06 -0700 Subject: [PATCH 08/26] Improve test --- coconut/tests/src/cocotest/agnostic/primary_2.coco | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coconut/tests/src/cocotest/agnostic/primary_2.coco b/coconut/tests/src/cocotest/agnostic/primary_2.coco index f4238643..0150a89c 100644 --- a/coconut/tests/src/cocotest/agnostic/primary_2.coco +++ b/coconut/tests/src/cocotest/agnostic/primary_2.coco @@ -480,7 +480,7 @@ def primary_test_2() -> bool: assert x == 9 == y (x=x) = (x=10) assert x == 10 - (x=, y=y) = (x=11, y=11) + (y=y, x=) = (x=11, y=11) assert x == 11 == y tuple(x=) = (x=12, y=12) assert x == 12 From 13b31f62274c81d9d67c20fbf83a0a20f975340a Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Sun, 18 Aug 2024 01:52:24 -0700 Subject: [PATCH 09/26] Change env var --- coconut/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coconut/constants.py b/coconut/constants.py index 3b154568..14960727 100644 --- a/coconut/constants.py +++ b/coconut/constants.py @@ -638,7 +638,7 @@ def get_path_env_var(env_var, default): main_prompt = ">>> " more_prompt = " " -default_use_cache_dir = get_bool_env_var("COCONUT_USE_COCONUT_CACHE", PY34) +default_use_cache_dir = get_bool_env_var("COCONUT_USE_CACHE_DIR", PY34) coconut_cache_dir = "__coconut_cache__" mypy_path_env_var = "MYPYPATH" From dd52a7e4a90241a0e458ef26d43801c49099f983 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Tue, 20 Aug 2024 19:56:35 -0700 Subject: [PATCH 10/26] Fix comment handling in kernel Resolves #851. --- coconut/compiler/util.py | 6 ++++++ coconut/icoconut/root.py | 7 +++++-- coconut/root.py | 2 +- coconut/tests/src/extras.coco | 9 +++++++++ 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/coconut/compiler/util.py b/coconut/compiler/util.py index bd434b36..d733a76b 100644 --- a/coconut/compiler/util.py +++ b/coconut/compiler/util.py @@ -1925,6 +1925,12 @@ def rem_comment(line): return base +def get_comment(line): + """Extract a comment from a line if it has one.""" + base, comment = split_comment(line) + return comment + + def should_indent(code): """Determines whether the next line should be indented.""" last_line = rem_comment(code.splitlines()[-1]) diff --git a/coconut/icoconut/root.py b/coconut/icoconut/root.py index 7fd1d968..8afe190a 100644 --- a/coconut/icoconut/root.py +++ b/coconut/icoconut/root.py @@ -48,7 +48,7 @@ from coconut.terminal import logger from coconut.util import override, memoize_with_exceptions, replace_all from coconut.compiler import Compiler -from coconut.compiler.util import should_indent, paren_change +from coconut.compiler.util import should_indent, paren_change, get_comment from coconut.command.util import Runner try: @@ -214,7 +214,10 @@ def _coconut_assemble_logical_lines(): level += paren_change(no_strs_line) # put line in parts and break if done - if level < 0: + if get_comment(line): + parts.append(line) + break + elif level < 0: parts.append(line) elif no_strs_line.endswith("\\"): parts.append(line[:-1]) diff --git a/coconut/root.py b/coconut/root.py index 5a4e12e8..a8592e95 100644 --- a/coconut/root.py +++ b/coconut/root.py @@ -26,7 +26,7 @@ VERSION = "3.1.1" VERSION_NAME = None # False for release, int >= 1 for develop -DEVELOP = 3 +DEVELOP = 4 ALPHA = False # for pre releases rather than post releases assert DEVELOP is False or DEVELOP >= 1, "DEVELOP must be False or an int >= 1" diff --git a/coconut/tests/src/extras.coco b/coconut/tests/src/extras.coco index 12a1560d..78a3b505 100644 --- a/coconut/tests/src/extras.coco +++ b/coconut/tests/src/extras.coco @@ -612,6 +612,15 @@ def test_kernel() -> bool: assert captured_msg_content is None assert captured_msg_type["content"]["data"]["text/plain"] == "'()'" + assert k.do_execute("""[ + "hey", + # "there", # dont want this value now + "is comment" +]""", False, True, {}, True) |> unwrap_future$(loop) |> .["status"] == "ok" + captured_msg_type, captured_msg_content = fake_session.captured_messages[-1] + assert captured_msg_content is None + assert captured_msg_type["content"]["data"]["text/plain"] == "['hey', 'is comment']" + return True From b17742daa5eb755d59063e530d8e3dcd3f5d865c Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Tue, 20 Aug 2024 21:55:17 -0700 Subject: [PATCH 11/26] Fix py2 errors --- coconut/command/util.py | 2 +- coconut/compiler/header.py | 8 ++++++++ coconut/compiler/templates/header.py_template | 6 ++++-- coconut/constants.py | 2 +- 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/coconut/command/util.py b/coconut/command/util.py index fe26947d..0475616e 100644 --- a/coconut/command/util.py +++ b/coconut/command/util.py @@ -760,7 +760,7 @@ def prompt(self, msg): pygments.styles.get_style_by_name(self.style), ), completer=self.get_completer(), - auto_suggest=self.suggester, + auto_suggest=self.suggester or None, ) diff --git a/coconut/compiler/header.py b/coconut/compiler/header.py index 989e0c6a..4c93eab2 100644 --- a/coconut/compiler/header.py +++ b/coconut/compiler/header.py @@ -458,6 +458,14 @@ def recursive_iterator(*args, **kwargs): ''', indent=1, ), + set_nt_match_args=pycondition( + (3, 10), + if_lt=r''' +nt.__match_args__ = nt._fields + ''', + indent=1, + newline=True, + ), import_copyreg=pycondition( (3,), if_lt="import copy_reg as copyreg", diff --git a/coconut/compiler/templates/header.py_template b/coconut/compiler/templates/header.py_template index c5cfb8f2..7aa6a0a9 100644 --- a/coconut/compiler/templates/header.py_template +++ b/coconut/compiler/templates/header.py_template @@ -2024,8 +2024,10 @@ def _coconut_mk_anon_namedtuple(fields, types=None, of_kwargs=None): NT = _coconut.typing.NamedTuple("_namedtuple_of", [(f, t) for f, t in _coconut.zip(fields, types)]) _coconut.copyreg.pickle(NT, lambda nt: (_coconut_mk_anon_namedtuple, (nt._fields, types, nt._asdict()))) if of_kwargs is None: - return NT - return NT(**of_kwargs) + nt = NT + else: + nt = NT(**of_kwargs) +{set_nt_match_args} return nt def _coconut_ndim(arr): arr_mod = _coconut_get_base_module(arr) if (arr_mod in _coconut.numpy_modules or _coconut.hasattr(arr.__class__, "__matconcat__")) and _coconut.hasattr(arr, "ndim"): diff --git a/coconut/constants.py b/coconut/constants.py index 14960727..995f53d6 100644 --- a/coconut/constants.py +++ b/coconut/constants.py @@ -666,7 +666,7 @@ def get_path_env_var(env_var, default): prompt_vi_mode = get_bool_env_var(vi_mode_env_var, False) prompt_wrap_lines = True prompt_history_search = True -prompt_use_suggester = False +prompt_use_suggester = not PY2 base_dir = os.path.dirname(os.path.abspath(fixpath(__file__))) From c4b8016ca103987ca3bf9ed44a0862ce93c45a87 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Tue, 20 Aug 2024 22:03:20 -0700 Subject: [PATCH 12/26] Fix more tests --- DOCS.md | 2 +- coconut/command/util.py | 6 +++--- coconut/root.py | 2 +- coconut/tests/src/cocotest/agnostic/main.coco | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/DOCS.md b/DOCS.md index 2270efb4..664a1b0a 100644 --- a/DOCS.md +++ b/DOCS.md @@ -2264,7 +2264,7 @@ main_func( ### Anonymous Namedtuples -Coconut supports anonymous [`namedtuple`](https://docs.python.org/3/library/collections.html#collections.namedtuple) literals, such that `(a=1, b=2)` can be used just as `(1, 2)`, but with added names. Anonymous `namedtuple`s are always pickleable. +Coconut supports anonymous [`namedtuple`](https://docs.python.org/3/library/collections.html#collections.namedtuple) literals, such that `(a=1, b=2)` can be used just as `(1, 2)`, but with added names. Anonymous `namedtuple`s are always pickleable and support [`__match_args__`](https://peps.python.org/pep-0622/) on all Python versions. The syntax for anonymous namedtuple literals is: ```coconut diff --git a/coconut/command/util.py b/coconut/command/util.py index 0475616e..923b0b59 100644 --- a/coconut/command/util.py +++ b/coconut/command/util.py @@ -671,7 +671,7 @@ class Prompt(object): style = None runner = None lexer = None - suggester = None if prompt_use_suggester else False + suggester = True if prompt_use_suggester else None def __init__(self, setup_now=False): """Set up the prompt.""" @@ -686,7 +686,7 @@ def setup(self): We do this lazily since it's expensive.""" if self.lexer is None: self.lexer = PygmentsLexer(CoconutLexer) - if self.suggester is None: + if self.suggester is True: self.suggester = AutoSuggestFromHistory() def set_style(self, style): @@ -760,7 +760,7 @@ def prompt(self, msg): pygments.styles.get_style_by_name(self.style), ), completer=self.get_completer(), - auto_suggest=self.suggester or None, + auto_suggest=self.suggester, ) diff --git a/coconut/root.py b/coconut/root.py index a8592e95..a067cbd5 100644 --- a/coconut/root.py +++ b/coconut/root.py @@ -26,7 +26,7 @@ VERSION = "3.1.1" VERSION_NAME = None # False for release, int >= 1 for develop -DEVELOP = 4 +DEVELOP = 5 ALPHA = False # for pre releases rather than post releases assert DEVELOP is False or DEVELOP >= 1, "DEVELOP must be False or an int >= 1" diff --git a/coconut/tests/src/cocotest/agnostic/main.coco b/coconut/tests/src/cocotest/agnostic/main.coco index 56bfad40..7e92d3a8 100644 --- a/coconut/tests/src/cocotest/agnostic/main.coco +++ b/coconut/tests/src/cocotest/agnostic/main.coco @@ -35,7 +35,7 @@ def package_test(outer_MatchError) -> bool: assert MatchError() `isinstance` outer_MatchError, (MatchError, outer_MatchError) assert outer_MatchError() `isinstance` MatchError, (outer_MatchError, MatchError) assert_raises((raise)$(outer_MatchError), MatchError) - assert_raises((raise)$(MatchError), outer_MatchError) + assert_raises((raise)$(MatchError), outer_MatchError) # type: ignore def raises_outer_MatchError(obj=None): raise outer_MatchError("raises_outer_MatchError") match raises_outer_MatchError -> None in 10: From bfff1eb734dcb2431bc352d63b809f509ae7be38 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Wed, 21 Aug 2024 23:23:28 -0700 Subject: [PATCH 13/26] Reduce tests --- coconut/tests/main_test.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/coconut/tests/main_test.py b/coconut/tests/main_test.py index 07b3a04c..163cef66 100644 --- a/coconut/tests/main_test.py +++ b/coconut/tests/main_test.py @@ -1054,11 +1054,8 @@ def test_any_of(self): }): run() - def test_keep_lines(self): - run(["--keep-lines"]) - - def test_strict(self): - run(["--strict"]) + def test_strict_keep_lines(self): + run(["--strict", "--keep-lines"]) def test_and(self): run(["--and"]) # src and dest built by comp From 1fba2992256acad146ed03e1088f1d9de558fb47 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Sat, 24 Aug 2024 18:22:25 -0700 Subject: [PATCH 14/26] Further reduce tests --- coconut/tests/main_test.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/coconut/tests/main_test.py b/coconut/tests/main_test.py index 163cef66..32df6e82 100644 --- a/coconut/tests/main_test.py +++ b/coconut/tests/main_test.py @@ -1108,11 +1108,12 @@ def test_bbopt(self): # if PY38: # run_pyprover() - def test_pyston(self): - with using_paths(pyston): - comp_pyston(["--no-tco"]) - if PYPY and PY2: - run_pyston() + if PY312: # reduce test load + def test_pyston(self): + with using_paths(pyston): + comp_pyston(["--no-tco"]) + if PYPY and PY2: + run_pyston() # ----------------------------------------------------------------------------------------------------------------------- From d46bbc78d0855c2b5228447da6387bdaebbece6f Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Sun, 25 Aug 2024 00:05:21 -0700 Subject: [PATCH 15/26] Disable suggester --- coconut/compiler/compiler.py | 32 +++++++++++-------- coconut/compiler/templates/header.py_template | 12 +++---- coconut/constants.py | 2 +- 3 files changed, 25 insertions(+), 21 deletions(-) diff --git a/coconut/compiler/compiler.py b/coconut/compiler/compiler.py index 8aaf2496..cae7f6d8 100644 --- a/coconut/compiler/compiler.py +++ b/coconut/compiler/compiler.py @@ -3493,25 +3493,30 @@ def __new__(_coconut_cls, {all_args}): return self.assemble_data(decorators, name, namedtuple_call, inherit, extra_stmts, stmts, base_args, paramdefs) - def make_namedtuple_call(self, name, namedtuple_args, types=None): + def make_namedtuple_call(self, name, namedtuple_args, types=None, of_args=None): """Construct a namedtuple call.""" if types: wrapped_types = [ self.wrap_typedef(types.get(i, "_coconut.typing.Any"), for_py_typedef=False) for i in range(len(namedtuple_args)) ] - if name is None: - return "_coconut_mk_anon_namedtuple(" + tuple_str_of(namedtuple_args, add_quotes=True) + ", " + tuple_str_of(wrapped_types) + ")" - else: - return '_coconut.typing.NamedTuple("' + name + '", [' + ", ".join( - '("' + argname + '", ' + wrapped_type + ")" - for argname, wrapped_type in zip(namedtuple_args, wrapped_types) - ) + "])" else: - if name is None: - return "_coconut_mk_anon_namedtuple(" + tuple_str_of(namedtuple_args, add_quotes=True) + ")" - else: - return '_coconut.collections.namedtuple("' + name + '", ' + tuple_str_of(namedtuple_args, add_quotes=True) + ')' + wrapped_types = None + if name is None: + return ( + "_coconut_mk_anon_namedtuple(" + + tuple_str_of(namedtuple_args, add_quotes=True) + + ("" if wrapped_types is None else ", " + tuple_str_of(wrapped_types)) + + ("" if of_args is None else ", of_args=" + tuple_str_of(of_args) + "") + + ")" + ) + elif wrapped_types is None: + return '_coconut.collections.namedtuple("' + name + '", ' + tuple_str_of(namedtuple_args, add_quotes=True) + ')' + ("" if of_args is None else tuple_str_of(of_args)) + else: + return '_coconut.typing.NamedTuple("' + name + '", [' + ", ".join( + '("' + argname + '", ' + wrapped_type + ")" + for argname, wrapped_type in zip(namedtuple_args, wrapped_types) + ) + "])" + ("" if of_args is None else tuple_str_of(of_args)) def assemble_data(self, decorators, name, namedtuple_call, inherit, extra_stmts, stmts, match_args, paramdefs=()): """Create a data class definition from the given components. @@ -3617,8 +3622,7 @@ def anon_namedtuple_handle(self, original, loc, tokens): names.append(name) items.append(item) - namedtuple_call = self.make_namedtuple_call(None, names, types) - return namedtuple_call + "(" + ", ".join(items) + ")" + return self.make_namedtuple_call(None, names, types, of_args=items) def single_import(self, loc, path, imp_as, type_ignore=False): """Generate import statements from a fully qualified import and the name to bind it to.""" diff --git a/coconut/compiler/templates/header.py_template b/coconut/compiler/templates/header.py_template index 7aa6a0a9..d0120655 100644 --- a/coconut/compiler/templates/header.py_template +++ b/coconut/compiler/templates/header.py_template @@ -2017,17 +2017,17 @@ collectby.using_threads = _coconut_partial(_coconut_parallel_mapreduce, collectb def _namedtuple_of(**kwargs): """Construct an anonymous namedtuple of the given keyword arguments.""" {namedtuple_of_implementation} -def _coconut_mk_anon_namedtuple(fields, types=None, of_kwargs=None): +def _coconut_mk_anon_namedtuple(fields, types=None, of_kwargs={empty_dict}, of_args=()): if types is None: NT = _coconut.collections.namedtuple("_namedtuple_of", fields) else: NT = _coconut.typing.NamedTuple("_namedtuple_of", [(f, t) for f, t in _coconut.zip(fields, types)]) _coconut.copyreg.pickle(NT, lambda nt: (_coconut_mk_anon_namedtuple, (nt._fields, types, nt._asdict()))) - if of_kwargs is None: - nt = NT - else: - nt = NT(**of_kwargs) -{set_nt_match_args} return nt + if not (of_kwargs or of_args): + return NT + nt = NT(*of_args, **of_kwargs) +{set_nt_match_args} + return nt def _coconut_ndim(arr): arr_mod = _coconut_get_base_module(arr) if (arr_mod in _coconut.numpy_modules or _coconut.hasattr(arr.__class__, "__matconcat__")) and _coconut.hasattr(arr, "ndim"): diff --git a/coconut/constants.py b/coconut/constants.py index 995f53d6..14960727 100644 --- a/coconut/constants.py +++ b/coconut/constants.py @@ -666,7 +666,7 @@ def get_path_env_var(env_var, default): prompt_vi_mode = get_bool_env_var(vi_mode_env_var, False) prompt_wrap_lines = True prompt_history_search = True -prompt_use_suggester = not PY2 +prompt_use_suggester = False base_dir = os.path.dirname(os.path.abspath(fixpath(__file__))) From 4a37f046071409c5e8aa38444bd1ca42dbf79f29 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Sun, 25 Aug 2024 00:19:00 -0700 Subject: [PATCH 16/26] Fix mypy --- __coconut__/__init__.pyi | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/__coconut__/__init__.pyi b/__coconut__/__init__.pyi index fedb0bb9..f2bc2bdf 100644 --- a/__coconut__/__init__.pyi +++ b/__coconut__/__init__.pyi @@ -1865,6 +1865,18 @@ def _coconut_mk_anon_namedtuple( fields: _t.Tuple[_t.Text, ...], types: _t.Optional[_t.Tuple[_t.Any, ...]] = None, ) -> _t.Callable[..., _t.Tuple[_t.Any, ...]]: ... +@_t.overload +def _coconut_mk_anon_namedtuple( + fields: _t.Tuple[_t.Text, ...], + types: _t.Optional[_t.Tuple[_t.Any, ...]], + of_args: _T, +) -> _T: ... +@_t.overload +def _coconut_mk_anon_namedtuple( + fields: _t.Tuple[_t.Text, ...], + *, + of_args: _T, +) -> _T: ... # @_t.overload From 16f7e58b78c83ed1666b76099a06d22a795b5eb7 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Sun, 25 Aug 2024 11:24:57 -0700 Subject: [PATCH 17/26] Fix anon namedtuples --- coconut/compiler/header.py | 4 ++-- coconut/compiler/templates/header.py_template | 7 +++---- coconut/root.py | 2 +- coconut/tests/src/cocotest/agnostic/primary_2.coco | 1 + 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/coconut/compiler/header.py b/coconut/compiler/header.py index 4c93eab2..e96b00c7 100644 --- a/coconut/compiler/header.py +++ b/coconut/compiler/header.py @@ -458,10 +458,10 @@ def recursive_iterator(*args, **kwargs): ''', indent=1, ), - set_nt_match_args=pycondition( + set_NT_match_args=pycondition( (3, 10), if_lt=r''' -nt.__match_args__ = nt._fields +NT.__match_args__ = _coconut.property(lambda self: self._fields) ''', indent=1, newline=True, diff --git a/coconut/compiler/templates/header.py_template b/coconut/compiler/templates/header.py_template index d0120655..0c4d503e 100644 --- a/coconut/compiler/templates/header.py_template +++ b/coconut/compiler/templates/header.py_template @@ -2023,11 +2023,10 @@ def _coconut_mk_anon_namedtuple(fields, types=None, of_kwargs={empty_dict}, of_a else: NT = _coconut.typing.NamedTuple("_namedtuple_of", [(f, t) for f, t in _coconut.zip(fields, types)]) _coconut.copyreg.pickle(NT, lambda nt: (_coconut_mk_anon_namedtuple, (nt._fields, types, nt._asdict()))) - if not (of_kwargs or of_args): +{set_NT_match_args} if of_kwargs or of_args: + return NT(*of_args, **of_kwargs) + else: return NT - nt = NT(*of_args, **of_kwargs) -{set_nt_match_args} - return nt def _coconut_ndim(arr): arr_mod = _coconut_get_base_module(arr) if (arr_mod in _coconut.numpy_modules or _coconut.hasattr(arr.__class__, "__matconcat__")) and _coconut.hasattr(arr, "ndim"): diff --git a/coconut/root.py b/coconut/root.py index a067cbd5..53f26ea2 100644 --- a/coconut/root.py +++ b/coconut/root.py @@ -26,7 +26,7 @@ VERSION = "3.1.1" VERSION_NAME = None # False for release, int >= 1 for develop -DEVELOP = 5 +DEVELOP = 6 ALPHA = False # for pre releases rather than post releases assert DEVELOP is False or DEVELOP >= 1, "DEVELOP must be False or an int >= 1" diff --git a/coconut/tests/src/cocotest/agnostic/primary_2.coco b/coconut/tests/src/cocotest/agnostic/primary_2.coco index 0150a89c..72ad4e95 100644 --- a/coconut/tests/src/cocotest/agnostic/primary_2.coco +++ b/coconut/tests/src/cocotest/agnostic/primary_2.coco @@ -487,6 +487,7 @@ def primary_test_2() -> bool: match (x=) in (x=13, y=13): assert False assert x == 12 + assert (x=1).__match_args__ == ('x',) # 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 From 373e38b1059e5a35d76b8b16b395dcd196acaa9a Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Fri, 30 Aug 2024 21:22:00 -0700 Subject: [PATCH 18/26] Add keyword support to more builtins Resolves #846, #845. --- DOCS.md | 14 +++++------ coconut/compiler/templates/header.py_template | 23 +++++++++++++++---- coconut/root.py | 19 +++++++++++---- coconut/tests/main_test.py | 1 + .../src/cocotest/agnostic/primary_2.coco | 3 +++ 5 files changed, 45 insertions(+), 15 deletions(-) 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 From 6048e7620232a195490d6ca97bbf2c85744d6de2 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Fri, 30 Aug 2024 22:27:09 -0700 Subject: [PATCH 19/26] Update dependencies --- .pre-commit-config.yaml | 2 +- Makefile | 10 +++++----- coconut/constants.py | 25 ++++++++++++++++--------- coconut/root.py | 2 +- 4 files changed, 23 insertions(+), 16 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e1ea9b6a..2b253a6d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -34,7 +34,7 @@ repos: args: - --autofix - repo: https://github.com/pycqa/flake8 - rev: 7.0.0 + rev: 7.1.1 hooks: - id: flake8 args: diff --git a/Makefile b/Makefile index e96ed3ee..f60e01a9 100644 --- a/Makefile +++ b/Makefile @@ -26,27 +26,27 @@ dev-py3: clean setup-py3 .PHONY: setup setup: -python -m ensurepip - python -m pip install --upgrade setuptools wheel pip pytest_remotedata cython + python -m pip install --upgrade setuptools wheel pip cython .PHONY: setup-py2 setup-py2: -python2 -m ensurepip - python2 -m pip install --upgrade "setuptools<58" wheel pip pytest_remotedata cython + python2 -m pip install --upgrade "setuptools<58" wheel pip cython .PHONY: setup-py3 setup-py3: -python3 -m ensurepip - python3 -m pip install --upgrade setuptools wheel pip pytest_remotedata cython + python3 -m pip install --upgrade setuptools wheel pip cython .PHONY: setup-pypy setup-pypy: -pypy -m ensurepip - pypy -m pip install --upgrade "setuptools<58" wheel pip pytest_remotedata + pypy -m pip install --upgrade "setuptools<58" wheel pip .PHONY: setup-pypy3 setup-pypy3: -pypy3 -m ensurepip - pypy3 -m pip install --upgrade setuptools wheel pip pytest_remotedata + pypy3 -m pip install --upgrade setuptools wheel pip .PHONY: install install: setup diff --git a/coconut/constants.py b/coconut/constants.py index 14960727..a2cabc76 100644 --- a/coconut/constants.py +++ b/coconut/constants.py @@ -1024,6 +1024,9 @@ def get_path_env_var(env_var, default): ("pygments", "py>=39"), "myst-parser", "pydata-sphinx-theme", + # these are necessary to fix a sphinx error + "sphinxcontrib_applehelp", + "sphinxcontrib_htmlhelp", ), "numpy": ( ("numpy", "py<3;cpy"), @@ -1037,6 +1040,7 @@ def get_path_env_var(env_var, default): ("pytest", "py>=36;py<38"), ("pytest", "py38"), "pexpect", + "pytest_remotedata", # fixes a pytest error ), } @@ -1044,22 +1048,24 @@ def get_path_env_var(env_var, default): unpinned_min_versions = { "cPyparsing": (2, 4, 7, 2, 4, 0), ("pre-commit", "py3"): (3,), - ("psutil", "py>=27"): (5,), - "jupyter": (1, 0), + ("psutil", "py>=27"): (6,), + "jupyter": (1, 1), "types-backports": (0, 1), ("futures", "py<3"): (3, 4), ("argparse", "py<27"): (1, 4), "pexpect": (4,), ("trollius", "py<3;cpy"): (2, 2), "requests": (2, 32), - ("numpy", "py39"): (1, 26), + ("numpy", "py39"): (2,), ("xarray", "py39"): (2024,), ("dataclasses", "py==36"): (0, 8), ("aenum", "py<34"): (3, 1, 15), "pydata-sphinx-theme": (0, 15), - "myst-parser": (3,), - "sphinx": (7,), - "mypy[python2]": (1, 10), + "myst-parser": (4,), + "sphinx": (8,), + "sphinxcontrib_applehelp": (2,), + "sphinxcontrib_htmlhelp": (2,), + "mypy[python2]": (1, 11), "pyright": (1, 1), ("jupyter-console", "py37"): (6, 6), ("typing", "py<35"): (3, 10), @@ -1067,11 +1073,12 @@ def get_path_env_var(env_var, default): ("ipykernel", "py38"): (6,), ("jedi", "py39"): (0, 19), ("pygments", "py>=39"): (2, 18), - ("xonsh", "py39"): (0, 16), + ("xonsh", "py39"): (0, 18), ("async_generator", "py35"): (1, 10), ("exceptiongroup", "py37;py<311"): (1,), - ("ipython", "py>=310"): (8, 25), + ("ipython", "py>=310"): (8, 27), "py-spy": (0, 3), + "pytest_remotedata": (0, 4), } pinned_min_versions = { @@ -1088,7 +1095,7 @@ def get_path_env_var(env_var, default): # don't upgrade these; they break on Python 3.6 ("anyio", "py36"): (3,), ("xonsh", "py>=36;py<39"): (0, 11), - ("pandas", "py36"): (1,), + ("pandas", "py36"): (1, 16), ("jupyter-client", "py36"): (7, 1, 2), ("typing_extensions", "py==36"): (4, 1), ("pytest", "py>=36;py<38"): (7,), diff --git a/coconut/root.py b/coconut/root.py index 8e906330..3fa2df30 100644 --- a/coconut/root.py +++ b/coconut/root.py @@ -26,7 +26,7 @@ VERSION = "3.1.1" VERSION_NAME = None # False for release, int >= 1 for develop -DEVELOP = 6 +DEVELOP = 7 ALPHA = False # for pre releases rather than post releases assert DEVELOP is False or DEVELOP >= 1, "DEVELOP must be False or an int >= 1" From 3a0befc63a8731b01b414278927dc416a007ad5a Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Fri, 30 Aug 2024 22:45:00 -0700 Subject: [PATCH 20/26] Fix numpy version --- coconut/constants.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/coconut/constants.py b/coconut/constants.py index a2cabc76..6a01997a 100644 --- a/coconut/constants.py +++ b/coconut/constants.py @@ -1056,7 +1056,6 @@ def get_path_env_var(env_var, default): "pexpect": (4,), ("trollius", "py<3;cpy"): (2, 2), "requests": (2, 32), - ("numpy", "py39"): (2,), ("xarray", "py39"): (2024,), ("dataclasses", "py==36"): (0, 8), ("aenum", "py<34"): (3, 1, 15), @@ -1082,6 +1081,8 @@ def get_path_env_var(env_var, default): } pinned_min_versions = { + # don't upgrade this; some extensions implicitly require numpy<2 + ("numpy", "py39"): (1, 26), # don't upgrade this; it breaks xonsh ("pytest", "py38"): (8, 0), # don't upgrade these; they break on Python 3.9 From 149110f6a4df75cad7d8e2f924f1e75b75890aca Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Sat, 31 Aug 2024 00:00:02 -0700 Subject: [PATCH 21/26] Fix dependencies --- coconut/constants.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/coconut/constants.py b/coconut/constants.py index 6a01997a..f26f652e 100644 --- a/coconut/constants.py +++ b/coconut/constants.py @@ -1077,7 +1077,6 @@ def get_path_env_var(env_var, default): ("exceptiongroup", "py37;py<311"): (1,), ("ipython", "py>=310"): (8, 27), "py-spy": (0, 3), - "pytest_remotedata": (0, 4), } pinned_min_versions = { @@ -1096,7 +1095,7 @@ def get_path_env_var(env_var, default): # don't upgrade these; they break on Python 3.6 ("anyio", "py36"): (3,), ("xonsh", "py>=36;py<39"): (0, 11), - ("pandas", "py36"): (1, 16), + ("pandas", "py36"): (1, 1), ("jupyter-client", "py36"): (7, 1, 2), ("typing_extensions", "py==36"): (4, 1), ("pytest", "py>=36;py<38"): (7,), @@ -1129,6 +1128,7 @@ def get_path_env_var(env_var, default): "papermill": (1, 2), ("numpy", "py<3;cpy"): (1, 16), ("backports.functools-lru-cache", "py<3"): (1, 6), + "pytest_remotedata": (0, 3), # don't upgrade this; it breaks with old IPython versions ("jedi", "py<39"): (0, 17), # Coconut requires pyparsing 2 From b16d9f73c93f7a64eb0d9054a0a4aaf2b47531bd Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Sat, 31 Aug 2024 01:51:49 -0700 Subject: [PATCH 22/26] Fix pypy 3.8 --- coconut/constants.py | 4 ++++ coconut/requirements.py | 3 ++- coconut/tests/src/extras.coco | 9 ++++----- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/coconut/constants.py b/coconut/constants.py index f26f652e..243bca2a 100644 --- a/coconut/constants.py +++ b/coconut/constants.py @@ -101,6 +101,10 @@ def get_path_env_var(env_var, default): and not (PYPY and PY39) and (PY38 or not PY36) ) +NUMPY = ( + not PYPY + and (PY2 or PY34) +) py_version_str = sys.version.split()[0] diff --git a/coconut/requirements.py b/coconut/requirements.py index c6db8459..b7f1ce2e 100644 --- a/coconut/requirements.py +++ b/coconut/requirements.py @@ -25,6 +25,7 @@ from coconut.constants import ( CPYTHON, PY34, + NUMPY, IPY, MYPY, XONSH, @@ -249,7 +250,7 @@ def everything_in(req_dict): "docs": unique_wrt(get_reqs("docs"), requirements), "tests": uniqueify_all( get_reqs("tests"), - extras["numpy"], + extras["numpy"] if NUMPY else [], extras["jupyter"] if IPY else [], extras["mypy"] if MYPY else [], extras["xonsh"] if XONSH else [], diff --git a/coconut/tests/src/extras.coco b/coconut/tests/src/extras.coco index 78a3b505..d8c359f5 100644 --- a/coconut/tests/src/extras.coco +++ b/coconut/tests/src/extras.coco @@ -7,8 +7,7 @@ os.environ["COCONUT_USE_COLOR"] = "False" from coconut.__coconut__ import consume as coc_consume from coconut.constants import ( IPY, - PY2, - PY34, + NUMPY, PY35, PY36, PY39, @@ -762,13 +761,13 @@ def test_xarray() -> bool: def test_extras() -> bool: - if not PYPY and (PY2 or PY34): + if NUMPY: assert test_numpy() is True print(".", end="") - if not PYPY and PY36: + if NUMPY and PY36: assert test_pandas() is True # . print(".", end="") - if not PYPY and PY39: + if NUMPY and PY39: assert test_xarray() is True # .. print(".") # newline bc we print stuff after this assert test_setup_none() is True # ... From 2bfc452900f3eb7c43247007d73f3cad97540c1d Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Sat, 31 Aug 2024 13:07:05 -0700 Subject: [PATCH 23/26] Fix prelude test --- coconut/tests/main_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coconut/tests/main_test.py b/coconut/tests/main_test.py index b0eb72e6..e911da59 100644 --- a/coconut/tests/main_test.py +++ b/coconut/tests/main_test.py @@ -1093,7 +1093,7 @@ class TestExternal(unittest.TestCase): if not PYPY or PY2: def test_prelude(self): with using_paths(prelude): - comp_prelude() + comp_prelude(expect_retcode=None) if MYPY and PY38: run_prelude() From 58b1de320f71f871bc117646b64830c241adb992 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Sat, 31 Aug 2024 20:42:32 -0700 Subject: [PATCH 24/26] Fix test --- coconut/tests/src/cocotest/agnostic/suite.coco | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coconut/tests/src/cocotest/agnostic/suite.coco b/coconut/tests/src/cocotest/agnostic/suite.coco index 3589aedf..54bf46d5 100644 --- a/coconut/tests/src/cocotest/agnostic/suite.coco +++ b/coconut/tests/src/cocotest/agnostic/suite.coco @@ -637,7 +637,7 @@ def suite_test() -> bool: assert map(HasDefs().a_def, range(5)) |> list == range(1, 6) |> list assert HasDefs().a_def 1 == 2 assert HasDefs().case_def 1 == 0 == HasDefs().case_def_ 1 - assert HasDefs.__annotations__.keys() |> set == {"a_def"}, HasDefs.__annotations__ + assert "a_def" in HasDefs.__annotations__.keys() |> set, HasDefs.__annotations__ assert store.plus1 store.one == store.two assert ret_locals()["my_loc"] == 1 assert ret_globals()["my_glob"] == 1 From ecfa2c4c023ff3db3ce3473c7783415c41b678f9 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Sat, 31 Aug 2024 22:51:23 -0700 Subject: [PATCH 25/26] Revert "Fix test" This reverts commit 58b1de320f71f871bc117646b64830c241adb992. --- coconut/tests/src/cocotest/agnostic/suite.coco | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coconut/tests/src/cocotest/agnostic/suite.coco b/coconut/tests/src/cocotest/agnostic/suite.coco index 54bf46d5..3589aedf 100644 --- a/coconut/tests/src/cocotest/agnostic/suite.coco +++ b/coconut/tests/src/cocotest/agnostic/suite.coco @@ -637,7 +637,7 @@ def suite_test() -> bool: assert map(HasDefs().a_def, range(5)) |> list == range(1, 6) |> list assert HasDefs().a_def 1 == 2 assert HasDefs().case_def 1 == 0 == HasDefs().case_def_ 1 - assert "a_def" in HasDefs.__annotations__.keys() |> set, HasDefs.__annotations__ + assert HasDefs.__annotations__.keys() |> set == {"a_def"}, HasDefs.__annotations__ assert store.plus1 store.one == store.two assert ret_locals()["my_loc"] == 1 assert ret_globals()["my_glob"] == 1 From 120c2737252fe195f85d4eee9c9cddc440beb41e Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Sat, 31 Aug 2024 23:04:39 -0700 Subject: [PATCH 26/26] Prepare for v3.1.2 --- coconut/root.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/coconut/root.py b/coconut/root.py index 3fa2df30..a80341ad 100644 --- a/coconut/root.py +++ b/coconut/root.py @@ -23,10 +23,10 @@ # VERSION: # ----------------------------------------------------------------------------------------------------------------------- -VERSION = "3.1.1" +VERSION = "3.1.2" VERSION_NAME = None # False for release, int >= 1 for develop -DEVELOP = 7 +DEVELOP = False ALPHA = False # for pre releases rather than post releases assert DEVELOP is False or DEVELOP >= 1, "DEVELOP must be False or an int >= 1"