Repository: hylang/hy

Issue:
"foo".format is legal syntax but does the wrong thing
`(. "foo" format)` works, but `"foo".format` doesn't (`NameError: name '' is not defined`). hy2py shows that `"foo".format` is parsed as two separate statements, `"foo"` and `.format`.

"foo".format is legal syntax but does the wrong thing
`(. "foo" format)` works, but `"foo".format` doesn't (`NameError: name '' is not defined`). hy2py shows that `"foo".format` is parsed as two separate statements, `"foo"` and `.format`.



Discussion:
IMO this isn't really a bug because of Lisp's notion of expressions. The main bug would be that doing `.x` as an expression is possible. It should be a syntax error.

Maybe the error message could read more like `cannot have attribute without a name (in order to get attributes of expressions, use `(.format <expression>)`)`. @Kodiologist would that be good enough?

For your use case, `(.format "foo")` works well enough.

Yeah, I think that makes sense. (I want `(. "foo" format)` rather than `(.format "foo")` in this case because I want to put the bound method in a list rather than call it immediately.)

IMO this isn't really a bug because of Lisp's notion of expressions. The main bug would be that doing `.x` as an expression is possible. It should be a syntax error.

Maybe the error message could read more like `cannot have attribute without a name (in order to get attributes of expressions, use `(.format <expression>)`)`. @Kodiologist would that be good enough?

For your use case, `(.format "foo")` works well enough.

Yeah, I think that makes sense. (I want `(. "foo" format)` rather than `(.format "foo")` in this case because I want to put the bound method in a list rather than call it immediately.)


Resolving patch:
diff --git a/hy/compiler.py b/hy/compiler.py
--- a/hy/compiler.py
+++ b/hy/compiler.py
@@ -2549,6 +2549,19 @@ def compile_complex(self, number):
     def compile_symbol(self, symbol):
         if "." in symbol:
             glob, local = symbol.rsplit(".", 1)
+
+            if not glob:
+                raise HyTypeError(symbol, 'cannot access attribute on '
+                                          'anything other than a name '
+                                          '(in order to get attributes of'
+                                          'expressions, use '
+                                          '`(. <expression> {attr})` or '
+                                          '`(.{attr} <expression>)`)'.format(
+                                              attr=local))
+
+            if not local:
+                raise HyTypeError(symbol, 'cannot access empty attribute')
+
             glob = HySymbol(glob).replace(symbol)
             ret = self.compile_symbol(glob)
 


Test patch:
diff --git a/tests/compilers/test_ast.py b/tests/compilers/test_ast.py
--- a/tests/compilers/test_ast.py
+++ b/tests/compilers/test_ast.py
@@ -528,6 +528,15 @@ def test_attribute_access():
     cant_compile("(. foo bar baz [0] quux {frob})")
 
 
+def test_attribute_empty():
+    """Ensure using dot notation with a non-expression is an error"""
+    cant_compile(".")
+    cant_compile("foo.")
+    cant_compile(".foo")
+    cant_compile('"bar".foo')
+    cant_compile('[2].foo')
+
+
 def test_cons_correct():
     """Ensure cons gets compiled correctly"""
     can_compile("(cons a b)")