Repository: hylang/hy

Issue:
f-strings tripping up hy2py
The following code


```  
(defmain []

  (setv some-stuff "some stuff")  
  (print f"{(hash some-stuff)}"))
```  
when ran through hy2py leads to a long string of errors


``` 
(hylearn) # hy2py minor.hy
Traceback (most recent call last):
  File "/home/diego/hylearn/bin/hy2py", line 12, in <module>
    sys.exit(hy2py_main())
  File "/home/diego/hylearn/lib/python3.7/site-packages/hy/cmdline.py", line 690, in hy2py_main
    print(astor.code_gen.to_source(_ast))
  File "/home/diego/hylearn/lib/python3.7/site-packages/astor/code_gen.py", line 62, in to_source
    generator.visit(node)
  File "/home/diego/hylearn/lib/python3.7/site-packages/astor/node_util.py", line 143, in visit
    return visitor(node)
  File "/home/diego/hylearn/lib/python3.7/site-packages/astor/code_gen.py", line 876, in visit_Module
    self.write(*node.body)
  File "/home/diego/hylearn/lib/python3.7/site-packages/astor/code_gen.py", line 177, in write
    visit(item)
  File "/home/diego/hylearn/lib/python3.7/site-packages/astor/node_util.py", line 143, in visit
    return visitor(node)
  File "/home/diego/hylearn/lib/python3.7/site-packages/astor/code_gen.py", line 370, in visit_If
    self.body(node.body)
  File "/home/diego/hylearn/lib/python3.7/site-packages/astor/code_gen.py", line 225, in body
    self.write(*statements)
  File "/home/diego/hylearn/lib/python3.7/site-packages/astor/code_gen.py", line 177, in write
    visit(item)
  File "/home/diego/hylearn/lib/python3.7/site-packages/astor/node_util.py", line 143, in visit
    return visitor(node)
  File "/home/diego/hylearn/lib/python3.7/site-packages/astor/code_gen.py", line 334, in visit_FunctionDef
    self.body(node.body)
  File "/home/diego/hylearn/lib/python3.7/site-packages/astor/code_gen.py", line 225, in body
    self.write(*statements)
  File "/home/diego/hylearn/lib/python3.7/site-packages/astor/code_gen.py", line 177, in write
    visit(item)
  File "/home/diego/hylearn/lib/python3.7/site-packages/astor/node_util.py", line 143, in visit
    return visitor(node)
  File "/home/diego/hylearn/lib/python3.7/site-packages/astor/code_gen.py", line 487, in visit_Return
    self.conditional_write(' ', node.value)
  File "/home/diego/hylearn/lib/python3.7/site-packages/astor/code_gen.py", line 213, in conditional_write
    self.write(*stuff)
  File "/home/diego/hylearn/lib/python3.7/site-packages/astor/code_gen.py", line 177, in write
    visit(item)
  File "/home/diego/hylearn/lib/python3.7/site-packages/astor/node_util.py", line 143, in visit
    return visitor(node)
  File "/home/diego/hylearn/lib/python3.7/site-packages/astor/code_gen.py", line 532, in visit_Call
    write(write_comma, arg)
  File "/home/diego/hylearn/lib/python3.7/site-packages/astor/code_gen.py", line 177, in write
    visit(item)
  File "/home/diego/hylearn/lib/python3.7/site-packages/astor/node_util.py", line 143, in visit
    return visitor(node)
  File "/home/diego/hylearn/lib/python3.7/site-packages/astor/code_gen.py", line 564, in visit_JoinedStr
    self._handle_string_constant(node, None, is_joined=True)
  File "/home/diego/hylearn/lib/python3.7/site-packages/astor/code_gen.py", line 620, in _handle_string_constant
    recurse(node)
  File "/home/diego/hylearn/lib/python3.7/site-packages/astor/code_gen.py", line 612, in recurse
    recurse(value.format_spec)
  File "/home/diego/hylearn/lib/python3.7/site-packages/astor/code_gen.py", line 597, in recurse
    for value in node.values:
AttributeError: 'Str' object has no attribute 'values'

``` 

but otherwise runs fine by saying ` hy minor.hy`  (also in the more realistic situation where the bug came up in the first place). The output of ` hy --version` here is ` hy 0.17.0+41.ge777f25` 


Discussion:

Resolving patch:
diff --git a/hy/compiler.py b/hy/compiler.py
--- a/hy/compiler.py
+++ b/hy/compiler.py
@@ -1784,7 +1784,7 @@ def _format_string(self, string, rest, allow_recursion=True):
                item = item[2:].lstrip()
 
            # Look for a format specifier.
-           format_spec = asty.Str(string, s = "")
+           format_spec = None
            if item.startswith(':'):
                if allow_recursion:
                    ret += self._format_string(string,
@@ -1793,6 +1793,8 @@ def _format_string(self, string, rest, allow_recursion=True):
                    format_spec = ret.force_expr
                else:
                    format_spec = asty.Str(string, s=item[1:])
+                   if PY36:
+                       format_spec = asty.JoinedStr(string, values=[format_spec])
            elif item:
                raise self._syntax_error(string,
                    "f-string: trailing junk in field")
@@ -1818,7 +1820,9 @@ def _format_string(self, string, rest, allow_recursion=True):
                            ('!' + conversion if conversion else '') +
                            ':{}}'),
                        attr = 'format', ctx = ast.Load()),
-                   args = [ret.force_expr, format_spec],
+                   args = [
+                       ret.force_expr,
+                       format_spec or asty.Str(string, s = "")],
                    keywords = [], starargs = None, kwargs = None))
 
         return ret + (


Test patch:
diff --git a/tests/resources/pydemo.hy b/tests/resources/pydemo.hy
--- a/tests/resources/pydemo.hy
+++ b/tests/resources/pydemo.hy
@@ -49,6 +49,10 @@ Call me Ishmael. Some years ago—never mind how long precisely—having little
 (setv condexpr (if "" "x" "y"))
 (setv mylambda (fn [x] (+ x "z")))
 
+(setv fstring1 f"hello {(+ 1 1)} world")
+(setv p "xyzzy")
+(setv fstring2 f"a{p !r :9}")
+
 (setv augassign 103)
 (//= augassign 4)
 
diff --git a/tests/test_hy2py.py b/tests/test_hy2py.py
--- a/tests/test_hy2py.py
+++ b/tests/test_hy2py.py
@@ -79,6 +79,9 @@ def assert_stuff(m):
     assert type(m.mylambda) is type(lambda x: x + "z")
     assert m.mylambda("a") == "az"
 
+    assert m.fstring1 == "hello 2 world"
+    assert m.fstring2 == "a'xyzzy'  "
+
     assert m.augassign == 25
 
     assert m.delstatement == ["a", "c", "d", "e"]