From 59a8b7979a366fce752fb4b5bc4f57c715fb4ac7 Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Fri, 6 May 2022 12:36:22 -0400 Subject: [PATCH 01/12] =?UTF-8?q?`with=5Fdecorator.hy`=20=E2=86=92=20`deco?= =?UTF-8?q?rators.hy`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/native_tests/{with_decorator.hy => decorators.hy} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/native_tests/{with_decorator.hy => decorators.hy} (100%) diff --git a/tests/native_tests/with_decorator.hy b/tests/native_tests/decorators.hy similarity index 100% rename from tests/native_tests/with_decorator.hy rename to tests/native_tests/decorators.hy From e31a80da9e9a219515eaf94164fc0e6cf04b5c11 Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Fri, 6 May 2022 13:24:21 -0400 Subject: [PATCH 02/12] Replace `with-decorator` with decorator lists --- hy/core/result_macros.py | 52 +++++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/hy/core/result_macros.py b/hy/core/result_macros.py index cf39302d5..9e5bd1342 100644 --- a/hy/core/result_macros.py +++ b/hy/core/result_macros.py @@ -1398,25 +1398,33 @@ def compile_function_lambda(compiler, expr, root, returns, params, body): # Otherwise create a standard function node = asty.AsyncFunctionDef if root == "fn/a" else asty.FunctionDef name = compiler.get_anon_var() - ret += compile_function_node(compiler, expr, node, name, args, returns, body) + ret += compile_function_node(compiler, expr, node, [], name, args, returns, body) # return its name as the final expr return ret + Result(expr=ret.temp_variables[0]) -@pattern_macro(["defn", "defn/a"], [OPTIONAL_ANNOTATION, SYM, lambda_list, many(FORM)]) -def compile_function_def(compiler, expr, root, returns, name, params, body): +@pattern_macro(["defn", "defn/a"], [ + maybe(brackets(many(FORM))), + OPTIONAL_ANNOTATION, + SYM, lambda_list, + many(FORM)]) +def compile_function_def(compiler, expr, root, decorators, returns, name, params, body): node = asty.FunctionDef if root == "defn" else asty.AsyncFunctionDef - args, ret = compile_lambda_list(compiler, params) + decorators, ret, _ = compiler._compile_collect( + decorators[0] if decorators else []) + args, ret2 = compile_lambda_list(compiler, params) + ret += ret2 name = mangle(compiler._nonconst(name)) compiler.scope.define(name) with compiler.scope.create(ScopeFn, args): body = compiler._compile_branch(body) - return ret + compile_function_node(compiler, expr, node, name, args, returns, body) + return ret + compile_function_node( + compiler, expr, node, decorators, name, args, returns, body) -def compile_function_node(compiler, expr, node, name, args, returns, body): +def compile_function_node(compiler, expr, node, decorators, name, args, returns, body): ret = Result() if body.expr: @@ -1427,7 +1435,7 @@ def compile_function_node(compiler, expr, node, name, args, returns, body): name=name, args=args, body=body.stmts or [asty.Pass(expr)], - decorator_list=[], + decorator_list=decorators, returns=compiler.compile(returns).force_expr if returns is not None else None, ) @@ -1573,19 +1581,6 @@ def compile_arguments_set(compiler, decls, ret, is_kwonly=False): return args_ast, args_defaults, ret -_decoratables = (ast.FunctionDef, ast.ClassDef, ast.AsyncFunctionDef) - - -@pattern_macro("with-decorator", [oneplus(FORM)]) -def compile_decorate_expression(compiler, expr, name, args): - decs, fn = args[:-1], compiler.compile(args[-1]) - if not fn.stmts or not isinstance(fn.stmts[-1], _decoratables): - raise compiler._syntax_error(args[-1], "Decorated a non-function") - decs, ret, _ = compiler._compile_collect(decs) - fn.stmts[-1].decorator_list = decs + fn.stmts[-1].decorator_list - return ret + fn - - @pattern_macro("return", [maybe(FORM)]) def compile_return(compiler, expr, root, arg): ret = Result() @@ -1615,13 +1610,20 @@ def compile_yield_from_or_await_expression(compiler, expr, root, arg): # ------------------------------------------------ -@pattern_macro("defclass", [SYM, maybe(brackets(many(FORM)) + maybe(STR) + many(FORM))]) -def compile_class_expression(compiler, expr, root, name, rest): +@pattern_macro("defclass", [ + maybe(brackets(many(FORM))), SYM, maybe(brackets(many(FORM)) + + maybe(STR) + + many(FORM))]) +def compile_class_expression(compiler, expr, root, decorators, name, rest): base_list, docstring, body = rest or ([[]], None, []) - bases_expr, bases, keywords = compiler._compile_collect( + decorators, ret, _ = compiler._compile_collect( + decorators[0] if decorators else [] + ) + bases_expr, ret2, keywords = compiler._compile_collect( base_list[0], with_kwargs=True ) + ret += ret2 bodyr = Result() @@ -1635,9 +1637,9 @@ def compile_class_expression(compiler, expr, root, name, rest): e = compiler._compile_branch(body) bodyr += e + e.expr_as_stmt() - return bases + asty.ClassDef( + return ret + asty.ClassDef( expr, - decorator_list=[], + decorator_list=decorators, name=name, keywords=keywords, starargs=None, From 8c9e0e228560c6c3984313b45425a6262d7ee3e0 Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Fri, 6 May 2022 12:37:38 -0400 Subject: [PATCH 03/12] Remove an obsolete decoration test --- tests/compilers/test_ast.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/compilers/test_ast.py b/tests/compilers/test_ast.py index 0aed4f16c..d73d58944 100644 --- a/tests/compilers/test_ast.py +++ b/tests/compilers/test_ast.py @@ -381,11 +381,6 @@ def test_ast_anon_fns_basics(): cant_compile("(fn)") -def test_ast_non_decoratable(): - """Ensure decorating garbage breaks""" - cant_compile("(with-decorator (foo) (* x x))") - - def test_ast_lambda_lists(): """Ensure the compiler chokes on invalid lambda-lists""" cant_compile("(fn [[a b c]] a)") From 06637cb87747d31e9549ffec6d275e2fdb7020a8 Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Fri, 6 May 2022 12:54:42 -0400 Subject: [PATCH 04/12] Update decoration syntax in tests --- tests/native_tests/comprehensions.hy | 9 +- tests/native_tests/decorators.hy | 23 ++-- tests/native_tests/language.hy | 8 +- tests/native_tests/native_macros.hy | 136 ++++++++++++------------ tests/native_tests/py3_10_only_tests.hy | 16 ++- tests/native_tests/reader_macros.hy | 22 ++-- tests/resources/pydemo.hy | 3 +- 7 files changed, 101 insertions(+), 116 deletions(-) diff --git a/tests/native_tests/comprehensions.hy b/tests/native_tests/comprehensions.hy index 956ce6a34..742706250 100644 --- a/tests/native_tests/comprehensions.hy +++ b/tests/native_tests/comprehensions.hy @@ -18,9 +18,10 @@ (assert (is (type (gfor x "abc" :do (setv y 1) x)) types.GeneratorType))) -(with-decorator (pytest.mark.parametrize "specialop" - ["for" "lfor" "sfor" "gfor" "dfor"]) -(defn test-fors [specialop] +(defn + [(pytest.mark.parametrize "specialop" + ["for" "lfor" "sfor" "gfor" "dfor"])] + test-fors [specialop] (setv cases [ ['(f x [] x) @@ -97,7 +98,7 @@ (setv result (hy.eval expr)) (when (= specialop "dfor") (setv result (.keys result))) - (assert (= (sorted result) answer) (str expr))))) + (assert (= (sorted result) answer) (str expr)))) (defn test-fors-no-loopers [] diff --git a/tests/native_tests/decorators.hy b/tests/native_tests/decorators.hy index 7846277f6..81851fe09 100644 --- a/tests/native_tests/decorators.hy +++ b/tests/native_tests/decorators.hy @@ -1,19 +1,17 @@ (defn test-decorated-1line-function [] (defn foodec [func] (fn [] (+ (func) 1))) - (with-decorator foodec - (defn tfunction [] - (* 2 2))) + (defn [foodec] tfunction [] + (* 2 2)) (assert (= (tfunction) 5))) (defn test-decorated-multiline-function [] (defn bazdec [func] (fn [] (+ (func) "x"))) - (with-decorator bazdec - (defn f [] - (setv intermediate "i") - (+ intermediate "b"))) + (defn [bazdec] f [] + (setv intermediate "i") + (+ intermediate "b")) (assert (= (f) "ibx"))) @@ -21,18 +19,15 @@ (defn bardec [cls] (setv cls.attr2 456) cls) - (with-decorator bardec - (defclass cls [] - (setv attr1 123))) + (defclass [bardec] cls [] + (setv attr1 123)) (assert (= cls.attr1 123)) (assert (= cls.attr2 456))) -(defn test-nested-decorators [] +(defn test-stacked-decorators [] (do (defn dec1 [f] (fn [] (+ (f) 1))) (defn dec2 [f] (fn [] (+ (f) 2))) - (with-decorator dec1 - (with-decorator dec2 - (defn f [] 1))) + (defn [dec1 dec2] f [] 1) (assert (= (f) 4)))) diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index 1c4b0bc70..c01e8bf08 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -1704,11 +1704,9 @@ cee"} dee" "ey bee\ncee dee")) (defn test-decorated-defn/a [] (defn decorator [func] (fn/a [] (/ (await (func)) 2))) - (with-decorator - decorator - (defn/a coro-test [] - (await (asyncio.sleep 0)) - 42)) + (defn/a [decorator] coro-test [] + (await (asyncio.sleep 0)) + 42) (assert (= (asyncio.run (coro-test)) 21))) diff --git a/tests/native_tests/native_macros.hy b/tests/native_tests/native_macros.hy index b43e4babe..b0ba5bc4a 100644 --- a/tests/native_tests/native_macros.hy +++ b/tests/native_tests/native_macros.hy @@ -244,77 +244,75 @@ in expansions." (require-macros)) -(with-decorator - pytest.mark.xfail - (defn test-macro-from-module [] - " - Macros loaded from an external module, which itself `require`s macros, should - work without having to `require` the module's macro dependencies (due to - [minimal] macro namespace resolution). - - In doing so we also confirm that a module's `__macros__` attribute is correctly - loaded and used. - - Additionally, we confirm that `require` statements are executed via loaded bytecode. - " - - (setv pyc-file (importlib.util.cache-from-source - (os.path.realpath - (os.path.join - "tests" "resources" "macro_with_require.hy")))) - - ;; Remove any cached byte-code, so that this runs from source and - ;; gets evaluated in this module. - (when (os.path.isfile pyc-file) - (os.unlink pyc-file) - (.clear sys.path_importer_cache) - (when (in "tests.resources.macro_with_require" sys.modules) - (del (get sys.modules "tests.resources.macro_with_require")) - (__macros__.clear))) - - ;; Ensure that bytecode isn't present when we require this module. - (assert (not (os.path.isfile pyc-file))) - - (defn test-requires-and-macros [] - (require tests.resources.macro-with-require - [test-module-macro]) - - ;; Make sure that `require` didn't add any of its `require`s - (assert (not (in (hy.mangle "nonlocal-test-macro") __macros__))) - ;; and that it didn't add its tags. - (assert (not (in (hy.mangle "#test-module-tag") __macros__))) - - ;; Now, require everything. - (require tests.resources.macro-with-require *) - - ;; Again, make sure it didn't add its required macros and/or tags. - (assert (not (in (hy.mangle "nonlocal-test-macro") __macros__))) - - ;; Its tag(s) should be here now. - (assert (in (hy.mangle "#test-module-tag") __macros__)) - - ;; The test macro expands to include this symbol. - (setv module-name-var "tests.native_tests.native_macros") - (assert (= (+ "This macro was created in tests.resources.macros, " - "expanded in tests.native_tests.native_macros " - "and passed the value 1.") - (test-module-macro 1)))) - - (test-requires-and-macros) - - ;; Now that bytecode is present, reload the module, clear the `require`d - ;; macros and tags, and rerun the tests. - (assert (os.path.isfile pyc-file)) - - ;; Reload the module and clear the local macro context. +(defn [(pytest.mark.xfail)] test-macro-from-module [] + " + Macros loaded from an external module, which itself `require`s macros, should + work without having to `require` the module's macro dependencies (due to + [minimal] macro namespace resolution). + + In doing so we also confirm that a module's `__macros__` attribute is correctly + loaded and used. + + Additionally, we confirm that `require` statements are executed via loaded bytecode. + " + + (setv pyc-file (importlib.util.cache-from-source + (os.path.realpath + (os.path.join + "tests" "resources" "macro_with_require.hy")))) + + ;; Remove any cached byte-code, so that this runs from source and + ;; gets evaluated in this module. + (when (os.path.isfile pyc-file) + (os.unlink pyc-file) (.clear sys.path_importer_cache) - (del (get sys.modules "tests.resources.macro_with_require")) - (.clear __macros__) + (when (in "tests.resources.macro_with_require" sys.modules) + (del (get sys.modules "tests.resources.macro_with_require")) + (__macros__.clear))) + + ;; Ensure that bytecode isn't present when we require this module. + (assert (not (os.path.isfile pyc-file))) + + (defn test-requires-and-macros [] + (require tests.resources.macro-with-require + [test-module-macro]) + + ;; Make sure that `require` didn't add any of its `require`s + (assert (not (in (hy.mangle "nonlocal-test-macro") __macros__))) + ;; and that it didn't add its tags. + (assert (not (in (hy.mangle "#test-module-tag") __macros__))) + + ;; Now, require everything. + (require tests.resources.macro-with-require *) + + ;; Again, make sure it didn't add its required macros and/or tags. + (assert (not (in (hy.mangle "nonlocal-test-macro") __macros__))) + + ;; Its tag(s) should be here now. + (assert (in (hy.mangle "#test-module-tag") __macros__)) + + ;; The test macro expands to include this symbol. + (setv module-name-var "tests.native_tests.native_macros") + (assert (= (+ "This macro was created in tests.resources.macros, " + "expanded in tests.native_tests.native_macros " + "and passed the value 1.") + (test-module-macro 1)))) + + (test-requires-and-macros) + + ;; Now that bytecode is present, reload the module, clear the `require`d + ;; macros and tags, and rerun the tests. + (assert (os.path.isfile pyc-file)) + + ;; Reload the module and clear the local macro context. + (.clear sys.path_importer_cache) + (del (get sys.modules "tests.resources.macro_with_require")) + (.clear __macros__) - ;; There doesn't seem to be a way--via standard import mechanisms--to - ;; ensure that an imported module used the cached bytecode. We'll simply have - ;; to trust that the .pyc loading convention was followed. - (test-requires-and-macros))) + ;; There doesn't seem to be a way--via standard import mechanisms--to + ;; ensure that an imported module used the cached bytecode. We'll simply have + ;; to trust that the .pyc loading convention was followed. + (test-requires-and-macros)) (defn test-recursive-require-star [] diff --git a/tests/native_tests/py3_10_only_tests.hy b/tests/native_tests/py3_10_only_tests.hy index 0f57a1aad..3bd5709d3 100644 --- a/tests/native_tests/py3_10_only_tests.hy +++ b/tests/native_tests/py3_10_only_tests.hy @@ -75,11 +75,9 @@ 0) 0)) - (with-decorator - dataclass - (defclass Point [] - (^int x) - (^int y))) + (defclass [dataclass] Point [] + (^int x) + (^int y)) (assert (= 0 (match (Point 1 0) (Point 1 :y var) var))) (assert (is None (match (Point 0 0) (Point 1 :y var) var))) @@ -164,11 +162,9 @@ (assert (= [x y] [5 6]))) (assert (= [x y] [3 4]))) -(with-decorator - dataclass - (defclass Point [] - (^int x) - (^int y))) +(defclass [dataclass] Point [] + (^int x) + (^int y)) (defn test-let-match-pattern [] (setv [x y] [1 2] diff --git a/tests/native_tests/reader_macros.hy b/tests/native_tests/reader_macros.hy index 29ade650a..0cf499e8d 100644 --- a/tests/native_tests/reader_macros.hy +++ b/tests/native_tests/reader_macros.hy @@ -6,18 +6,16 @@ pytest) -(with-decorator - contextmanager - (defn temp-module [module-name] - (let [module (types.ModuleType module-name) - old-module (sys.modules.get module-name)] - (setv (get sys.modules module-name) module) - (try - (yield module) - (finally - (if old-module - (setv (get sys.modules module-name) module) - (sys.modules.pop module-name))))))) +(defn [contextmanager] temp-module [module-name] + (let [module (types.ModuleType module-name) + old-module (sys.modules.get module-name)] + (setv (get sys.modules module-name) module) + (try + (yield module) + (finally + (if old-module + (setv (get sys.modules module-name) module) + (sys.modules.pop module-name)))))) (defn eval-isolated [tree [module None]] diff --git a/tests/resources/pydemo.hy b/tests/resources/pydemo.hy index c610bb2b0..d71c5c76c 100644 --- a/tests/resources/pydemo.hy +++ b/tests/resources/pydemo.hy @@ -139,8 +139,7 @@ Call me Ishmael. Some years ago—never mind how long precisely—having little (yield x))) (setv myyield (list (generator))) -(with-decorator (fn [f] (setv f.newattr "hello") f) - (defn mydecorated [])) +(defn [(fn [f] (setv f.newattr "hello") f)] mydecorated []) (setv myglobal 102) (defn set-global [] From 1675751e09bdb9ffc1eed09818bbb99a19191591 Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Fri, 6 May 2022 13:09:13 -0400 Subject: [PATCH 05/12] Remove an unnecessary `do` in a test --- tests/native_tests/decorators.hy | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/native_tests/decorators.hy b/tests/native_tests/decorators.hy index 81851fe09..729426aea 100644 --- a/tests/native_tests/decorators.hy +++ b/tests/native_tests/decorators.hy @@ -26,8 +26,7 @@ (defn test-stacked-decorators [] - (do - (defn dec1 [f] (fn [] (+ (f) 1))) - (defn dec2 [f] (fn [] (+ (f) 2))) - (defn [dec1 dec2] f [] 1) - (assert (= (f) 4)))) + (defn dec1 [f] (fn [] (+ (f) 1))) + (defn dec2 [f] (fn [] (+ (f) 2))) + (defn [dec1 dec2] f [] 1) + (assert (= (f) 4))) From 68865a1e545e76c53aacc04f9459a7f5e09e0041 Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Fri, 6 May 2022 14:01:06 -0400 Subject: [PATCH 06/12] Make `test-stacked-decorators` order-sensitive --- tests/native_tests/decorators.hy | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/native_tests/decorators.hy b/tests/native_tests/decorators.hy index 729426aea..3b7d037d3 100644 --- a/tests/native_tests/decorators.hy +++ b/tests/native_tests/decorators.hy @@ -26,7 +26,7 @@ (defn test-stacked-decorators [] - (defn dec1 [f] (fn [] (+ (f) 1))) - (defn dec2 [f] (fn [] (+ (f) 2))) - (defn [dec1 dec2] f [] 1) - (assert (= (f) 4))) + (defn dec1 [f] (fn [] (+ (f) "a"))) + (defn dec2 [f] (fn [] (+ (f) "b"))) + (defn [dec1 dec2] f [] "c") + (assert (= (f) "cba"))) From 7132cd5495c3aaa755cc2a8021cc9851f38aa3df Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Fri, 6 May 2022 13:23:40 -0400 Subject: [PATCH 07/12] Test evaluation order with decorators --- tests/native_tests/decorators.hy | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/native_tests/decorators.hy b/tests/native_tests/decorators.hy index 3b7d037d3..4b79d1f4d 100644 --- a/tests/native_tests/decorators.hy +++ b/tests/native_tests/decorators.hy @@ -30,3 +30,19 @@ (defn dec2 [f] (fn [] (+ (f) "b"))) (defn [dec1 dec2] f [] "c") (assert (= (f) "cba"))) + + +(defn test-evaluation-order [] + (setv l []) + (defn foo [f] + (.append l "foo") + (fn [] + (.append l "foo fn") + (f))) + (defn + [(do (.append l "dec") foo)] ; Decorator list + bar ; Function name + [[arg (do (.append l "arg") 1)]] ; Lambda list + (.append l "bar body") arg) ; Body + (.append l (bar)) + (assert (= l ["dec" "arg" "foo" "foo fn" "bar body" 1]))) From e728648f00db46e3f174619f784059f98ea98ddc Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Sun, 8 May 2022 12:44:10 -0400 Subject: [PATCH 08/12] Remove documentation for `with-decorator` --- docs/api.rst | 44 -------------------------------------------- docs/cheatsheet.json | 1 - 2 files changed, 45 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index 754b6ac35..40cb6460e 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -1504,50 +1504,6 @@ base names, such that ``hy.core.macros.foo`` can be called as just ``foo``. (because the context manager's ``__aexit__`` method returned true), in which case it returns ``None``. -.. hy:function:: (with-decorator [#* args]) - - ``with-decorator`` is used to wrap a function with another. The function - performing the decoration should accept a single value: the function being - decorated, and return a new function. ``with-decorator`` takes a minimum - of two parameters: the function performing decoration and the function - being decorated. More than one decorator function can be applied; they - will be applied in order from outermost to innermost, ie. the first - decorator will be the outermost one, and so on. Decorators with arguments - are called just like a function call. - - :: - - (with-decorator decorator-fun - (defn some-function [] ...) - - (with-decorator decorator1 decorator2 ... - (defn some-function [] ...) - - (with-decorator (decorator arg) .. - (defn some-function [] ...) - - - In the following example, ``inc-decorator`` is used to decorate the function - ``addition`` with a function that takes two parameters and calls the - decorated function with values that are incremented by 1. When - the decorated ``addition`` is called with values 1 and 1, the end result - will be 4 (``1+1 + 1+1``). - - :: - - => (defn inc-decorator [func] - ... (fn [value-1 value-2] (func (+ value-1 1) (+ value-2 1)))) - => (defn inc2-decorator [func] - ... (fn [value-1 value-2] (func (+ value-1 2) (+ value-2 2)))) - - => (with-decorator inc-decorator (defn addition [a b] (+ a b))) - => (addition 1 1) - 4 - => (with-decorator inc2-decorator inc-decorator - ... (defn addition [a b] (+ a b))) - => (addition 1 1) - 8 - .. hy:function:: (yield [object]) ``yield`` is used to create a generator object that returns one or more values. diff --git a/docs/cheatsheet.json b/docs/cheatsheet.json index 27f6378af..c3da83774 100644 --- a/docs/cheatsheet.json +++ b/docs/cheatsheet.json @@ -96,7 +96,6 @@ "while", "with", "with/a", - "with-decorator", "yield", "yield-from" ] From d61479b105a03c99e58cb4a49379f54402c0dd79 Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Sun, 8 May 2022 12:50:23 -0400 Subject: [PATCH 09/12] Correct a docs example for `match` --- docs/api.rst | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index 40cb6460e..2c8ab4a55 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -898,13 +898,12 @@ base names, such that ``hy.core.macros.foo`` can be called as just ``foo``. positionally if its ``__match_args__`` attribute is defined, see `pep 636 `_):: - => (with-decorator - ... dataclass - ... (defclass Point [] - ... (^int x) - ... (^int y))) + => (import dataclasses [dataclass]) + => (defclass [dataclass] Point [] + ... (^int x) + ... (^int y)) => (match (Point 1 2) - ... (Point 1 x) :if (= (% x 2) 0) x + ... (Point 1 x) :if (= (% x 2) 0) x) 2 .. hy:function:: (defclass [class-name super-classes #* body]) From 70561bb8be795791402fc5051219530d982f949c Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Sun, 8 May 2022 13:06:03 -0400 Subject: [PATCH 10/12] Document decorator lists --- docs/api.rst | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index 2c8ab4a55..1c9b97e00 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -164,6 +164,13 @@ base names, such that ``hy.core.macros.foo`` can be called as just ``foo``. If there at least two body forms, and the first of them is a string literal, this string becomes the :term:`py:docstring` of the function. + ``defn`` accepts two additional, optional arguments: a bracketed list of + :term:`decorators ` and an annotation (see :hy:data:`^`) for + the return value. These are placed before the function name (in that order, + if both are present):: + + (defn [decorator1 decorator2] ^annotation name [params] …) + Parameters may be prefixed with the following special symbols. If you use more than one, they can only appear in the given order (so all positional only arguments must precede ``/``, all positional or keyword arguments must precede a ``#*`` rest @@ -908,8 +915,10 @@ base names, such that ``hy.core.macros.foo`` can be called as just ``foo``. .. hy:function:: (defclass [class-name super-classes #* body]) - New classes are declared with ``defclass``. It can take optional parameters in the following order: - a list defining (a) possible super class(es) and a string (:term:`py:docstring`). + New classes are declared with ``defclass``. It can take optional parameters + in the following order: a list defining (a) possible super class(es) and a + string (:term:`py:docstring`). The class name may also be preceded by a list + of :term:`decorators `, as in :hy:func:`defn`. :strong:`Examples` From 38ff1eb3d20125f9ce135084b566ae81715192a9 Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Fri, 6 May 2022 13:07:22 -0400 Subject: [PATCH 11/12] Update NEWS --- NEWS.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/NEWS.rst b/NEWS.rst index 01bb12bdc..5449d3882 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -10,6 +10,8 @@ Removals `(defreader foo (setv arg (.parse-one-form &reader)) …)`. * `hy.read-str` has been removed. Use `hy.read`, which now accepts strings, instead. +* `with-decorator` and `#@` have been removed in favor of decorator + lists (see below). Breaking Changes ------------------------------ @@ -21,7 +23,6 @@ Breaking Changes (cond [a b] [x y z]) ; Old (cond a b x (do y z)) ; New -* `@#` is now a reader macro and has been moved to Hyrule. * `defmacro` once again requires the macro name as a symbol, not a string literal. * The parser has been completely rewritten. It is mostly @@ -66,6 +67,8 @@ New Features keyword arguments. Hy includes a workaround for a CPython bug that prevents the generation of legal Python code for these cases (`bpo-46520`_). +* `defn` and `defclass` now allow a decorator list as their first + argument. * You can now set the variable `_hy_export_macros` to control what macros are collected by `(require module *)` * New macro `export` From 46b5bf903c0ab983c22966d690b4b01dfcdc9271 Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Fri, 6 May 2022 13:27:30 -0400 Subject: [PATCH 12/12] Autoformat --- hy/core/result_macros.py | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/hy/core/result_macros.py b/hy/core/result_macros.py index 9e5bd1342..65878eb75 100644 --- a/hy/core/result_macros.py +++ b/hy/core/result_macros.py @@ -1404,15 +1404,13 @@ def compile_function_lambda(compiler, expr, root, returns, params, body): return ret + Result(expr=ret.temp_variables[0]) -@pattern_macro(["defn", "defn/a"], [ - maybe(brackets(many(FORM))), - OPTIONAL_ANNOTATION, - SYM, lambda_list, - many(FORM)]) +@pattern_macro( + ["defn", "defn/a"], + [maybe(brackets(many(FORM))), OPTIONAL_ANNOTATION, SYM, lambda_list, many(FORM)], +) def compile_function_def(compiler, expr, root, decorators, returns, name, params, body): node = asty.FunctionDef if root == "defn" else asty.AsyncFunctionDef - decorators, ret, _ = compiler._compile_collect( - decorators[0] if decorators else []) + decorators, ret, _ = compiler._compile_collect(decorators[0] if decorators else []) args, ret2 = compile_lambda_list(compiler, params) ret += ret2 name = mangle(compiler._nonconst(name)) @@ -1421,7 +1419,8 @@ def compile_function_def(compiler, expr, root, decorators, returns, name, params body = compiler._compile_branch(body) return ret + compile_function_node( - compiler, expr, node, decorators, name, args, returns, body) + compiler, expr, node, decorators, name, args, returns, body + ) def compile_function_node(compiler, expr, node, decorators, name, args, returns, body): @@ -1610,16 +1609,18 @@ def compile_yield_from_or_await_expression(compiler, expr, root, arg): # ------------------------------------------------ -@pattern_macro("defclass", [ - maybe(brackets(many(FORM))), SYM, maybe(brackets(many(FORM)) + - maybe(STR) + - many(FORM))]) +@pattern_macro( + "defclass", + [ + maybe(brackets(many(FORM))), + SYM, + maybe(brackets(many(FORM)) + maybe(STR) + many(FORM)), + ], +) def compile_class_expression(compiler, expr, root, decorators, name, rest): base_list, docstring, body = rest or ([[]], None, []) - decorators, ret, _ = compiler._compile_collect( - decorators[0] if decorators else [] - ) + decorators, ret, _ = compiler._compile_collect(decorators[0] if decorators else []) bases_expr, ret2, keywords = compiler._compile_collect( base_list[0], with_kwargs=True )