From af64cbe9969a6369b0ec4d4b8cca6f13896358cc Mon Sep 17 00:00:00 2001
From: Kodi Arfer <git@arfer.net>
Date: Sun, 30 Jan 2022 15:15:13 -0500
Subject: [PATCH] Get `hy2py` to cope with e.g. `(setv def 1)`

The new loop through the AST doesn't seem to cause a perceptible slowdown. On my system, the test suite took 14.33 s with this change and 14.38 s without it.
---
 hy/_compat.py             | 24 +++++++++++++++++++++++-
 tests/resources/pydemo.hy |  7 +++++--
 tests/test_hy2py.py       |  4 ++++
 3 files changed, 32 insertions(+), 3 deletions(-)

diff --git a/hy/_compat.py b/hy/_compat.py
index bcac7a966..eac7ad53c 100644
--- a/hy/_compat.py
+++ b/hy/_compat.py
@@ -1,3 +1,4 @@
+import ast
 import platform
 import sys
 
@@ -10,11 +11,32 @@
 
 if not PY3_9:
     # Shim `ast.unparse`.
-    import ast
     import astor.code_gen
     ast.unparse = astor.code_gen.to_source
 
 
+if 'def' in ast.unparse(ast.parse('𝕕𝕖𝕗 = 1')):
+    # Overwrite `ast.unparse` to backport https://github.com/python/cpython/pull/31012
+    import copy
+    import keyword
+    true_unparse = ast.unparse
+    def rewriting_unparse(ast_obj):
+        ast_obj = copy.deepcopy(ast_obj)
+        for node in ast.walk(ast_obj):
+            if type(node) in (ast.Constant, ast.Str):
+                # Don't touch string literals.
+                continue
+            for field in node._fields:
+                v = getattr(node, field, None)
+                if (type(v) is str and
+                        keyword.iskeyword(v) and
+                        v not in ('True', 'False', 'None')):
+                    setattr(node, field,
+                        chr(ord(v[0]) - ord('a') + ord('𝐚')) + v[1:])
+        return true_unparse(ast_obj)
+    ast.unparse = rewriting_unparse
+
+
 if not PY3_8:
     # Shim `re.Pattern`.
     import re
diff --git a/tests/resources/pydemo.hy b/tests/resources/pydemo.hy
index a7a1ea883..d8287fd9c 100644
--- a/tests/resources/pydemo.hy
+++ b/tests/resources/pydemo.hy
@@ -9,6 +9,8 @@ Call me Ishmael. Some years ago—never mind how long precisely—having little
 
 (setv identifier-that-has☝️💯☝️-to-be-mangled "ponies")
 (setv 𝔫𝔬𝔯𝔪𝔞𝔩𝔦𝔷𝔢-𝔱𝔥𝔦𝔰 "ok")
+(setv def "variable")
+(setv 𝕚𝕗 "if")
 
 (setv mynumber (+ 1 2))
 (setv myhex 0x123)
@@ -117,11 +119,12 @@ Call me Ishmael. Some years ago—never mind how long precisely—having little
   (else
     (setv ran-try-else True)))
 
-(defn fun [a b [c 9] [d 10] #* args #** kwargs]
+(defn fun [a b [c 9] [from 10] #* args #** kwargs]
   "function docstring"
-  [a b c d args (sorted (.items kwargs))])
+  [a b c from args (sorted (.items kwargs))])
 (setv funcall1 (fun 1 2 3 4 "a" "b" "c" :k1 "v1" :k2 "v2"))
 (setv funcall2 (fun 7 8 #* [9 10 11] #** {"x1" "y1"  "x2" "y2"}))
+(setv funcall3 (fun "x" "y" :from "spain"))
 
 (defn returner []
   (return 1)
diff --git a/tests/test_hy2py.py b/tests/test_hy2py.py
index 5bc348aa8..3f198ba22 100644
--- a/tests/test_hy2py.py
+++ b/tests/test_hy2py.py
@@ -40,6 +40,9 @@ def assert_stuff(m):
 
     assert getattr(m, mangle("identifier-that-has☝️💯☝️-to-be-mangled")) == "ponies"
     assert m.normalize_this == "ok"
+    assert getattr(m, "def") == "variable"
+    assert m.𝕕𝕖𝕗 == "variable"
+    assert getattr(m, "if") == "if"
 
     assert m.mynumber == 3
     assert m.myhex == 0x123
@@ -114,6 +117,7 @@ def assert_stuff(m):
     assert m.fun.__doc__ == "function docstring"
     assert m.funcall1 == [1, 2, 3, 4, ("a", "b", "c"), [("k1", "v1"), ("k2", "v2")]]
     assert m.funcall2 == [7, 8, 9, 10, (11,), [("x1", "y1"), ("x2", "y2")]]
+    assert m.funcall3 == ["x", "y", 9, "spain", (), []]
 
     assert m.myret == 1
     assert m.myyield == ["a", "b", "c"]