diff --git a/NEWS.rst b/NEWS.rst index c8b5923..311fd9a 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -14,19 +14,18 @@ Other Breaking Changes * Non-shadow unary `=`, `is`, `<`, etc. now evaluate their argument instead of ignoring it. This change increases consistency a bit and makes accidental unary uses easier to notice. +* `hy-repr` uses registered functions instead of methods New Features ------------------------------ * Added `mangle` and `unmangle` as core functions +* `defclass` in Python 3 now supports specifying metaclasses and other + keyword arguments Bug Fixes ------------------------------ * Fix `(return)` so it works correctly to exit a Python 2 generator -Other Breaking Changes ------------------------------ -* `hy-repr` uses registered functions instead of methods - Misc. Improvements ---------------------------- * `hy-repr` supports more standard types diff --git a/hy/compiler.py b/hy/compiler.py index bd19739..0eb067e 100755 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -2057,11 +2057,12 @@ class HyASTCompiler(object): bases_expr = [] bases = Result() + keywords = [] if expressions: base_list = expressions.pop(0) if not isinstance(base_list, HyList): raise HyTypeError(base_list, "Base classes must be a list.") - bases_expr, bases, _ = self._compile_collect(base_list) + bases_expr, bases, keywords = self._compile_collect(base_list, with_kwargs=PY3) body = Result() @@ -2092,7 +2093,7 @@ class HyASTCompiler(object): expressions, decorator_list=[], name=ast_str(class_name), - keywords=[], + keywords=keywords, starargs=None, kwargs=None, bases=bases_expr, diff --git a/tests/compilers/test_ast.py b/tests/compilers/test_ast.py index fe1c8a1..af87f8c 100644 --- a/tests/compilers/test_ast.py +++ b/tests/compilers/test_ast.py @@ -214,6 +214,13 @@ def test_ast_good_defclass(): can_compile("(defclass a [])") +@pytest.mark.skipif(not PY3, reason="Python 3 supports class keywords") +def test_ast_good_defclass_with_metaclass(): + "Make sure AST can compile valid defclass with keywords" + can_compile("(defclass a [:metaclass b])") + can_compile("(defclass a [:b c])") + + def test_ast_bad_defclass(): "Make sure AST can't compile invalid defclass" cant_compile("(defclass)") diff --git a/tests/native_tests/py36_only_tests.hy b/tests/native_tests/py36_only_tests.hy index c6e4637..6a71fcf 100644 --- a/tests/native_tests/py36_only_tests.hy +++ b/tests/native_tests/py36_only_tests.hy @@ -37,3 +37,11 @@ (setv x (+ x a)) (else (setv x (+ x 50)))) (assert (= x 53))))) + +(defn test-pep-487 [] + (defclass QuestBase [] + [--init-subclass-- (fn [cls swallow &kwargs kwargs] + (setv cls.swallow swallow))]) + + (defclass Quest [QuestBase :swallow "african"]) + (assert (= (. (Quest) swallow) "african"))) diff --git a/tests/native_tests/py3_only_tests.hy b/tests/native_tests/py3_only_tests.hy index d5d67e0..00cd448 100644 --- a/tests/native_tests/py3_only_tests.hy +++ b/tests/native_tests/py3_only_tests.hy @@ -84,3 +84,26 @@ (assert (= (foo :b 20 :a 10 :c 30) (, 10 20 30))))) +(defn test-pep-3115 [] + (defclass member-table [dict] + [--init-- (fn [self] (setv self.member-names [])) + + --setitem-- (fn [self key value] + (if (not-in key self) + (.append self.member-names key)) + (dict.--setitem-- self key value))]) + + (defclass OrderedClass [type] + [--prepare-- (classmethod (fn [metacls name bases] (member-table))) + + --new-- (fn [cls name bases classdict] + (setv result (type.--new-- cls name bases (dict classdict))) + (setv result.member-names classdict.member-names) + result)]) + + (defclass MyClass [:metaclass OrderedClass] + [method1 (fn [self] (pass)) + method2 (fn [self] (pass))]) + + (assert (= (. (MyClass) member-names) + ["__module__" "__qualname__" "method1" "method2"]))) diff --git a/tests/test_bin.py b/tests/test_bin.py index 94411cc..b1e0b25 100644 --- a/tests/test_bin.py +++ b/tests/test_bin.py @@ -12,7 +12,7 @@ import subprocess import pytest -from hy._compat import PY3, PY35, builtins +from hy._compat import PY3, PY35, PY36, builtins from hy.importer import get_bytecode_path @@ -229,9 +229,11 @@ def test_hy2py(): for dirpath, dirnames, filenames in os.walk("tests/native_tests"): for f in filenames: if f.endswith(".hy"): - if f == "py3_only_tests.hy" and not PY3: + if "py3_only" in f and not PY3: continue - if f == "py35_only_tests.hy" and not PY35: + if "py35_only" in f and not PY35: + continue + if "py36_only" in f and not PY36: continue i += 1 output, err = run_cmd("hy2py -s -a " + quote(os.path.join(dirpath, f)))