Repository: vyperlang/vyper

Issue:
Vyper doesn't support UTF-8 in docstrings
### Version Information
https://github.com/vyperlang/vyper/runs/546477614?check_suite_focus=true

### What's your issue about?
```
 error_msg = "'Ā' is not an allowed string literal character"
ast_struct = {'ast_type': 'Str', 'col_offset': 0, 'end_col_offset': 7, 'end_lineno': 1, ...}

    def _raise_syntax_exc(error_msg: str, ast_struct: dict) -> None:
        # helper function to raise a SyntaxException from a dict representing a node
>       raise SyntaxException(
            error_msg,
            ast_struct.get('full_source_code'),
            ast_struct.get('lineno'),
            ast_struct.get('col_offset')
        )
E       vyper.exceptions.SyntaxException: line 1:0 'Ā' is not an allowed string literal character
E       ---> 1 """Ā"""\
E       -------^
E            2

vyper/ast/nodes.py:169: SyntaxException
```


### How can it be fixed?

Fill this in if you know how to fix it.



Discussion:

Resolving patch:
diff --git a/vyper/ast/annotation.py b/vyper/ast/annotation.py
--- a/vyper/ast/annotation.py
+++ b/vyper/ast/annotation.py
@@ -62,6 +62,28 @@ def generic_visit(self, node):
 
         return super().generic_visit(node)
 
+    def _visit_docstring(self, node):
+        """
+        Move a node docstring from body to `doc_string` and annotate it as `DocStr`.
+        """
+        self.generic_visit(node)
+
+        if node.body:
+            n = node.body[0]
+            if isinstance(n, python_ast.Expr) and isinstance(n.value, python_ast.Str):
+                self.generic_visit(n.value)
+                n.value.ast_type = "DocStr"
+                del node.body[0]
+                node.doc_string = n.value
+
+        return node
+
+    def visit_Module(self, node):
+        return self._visit_docstring(node)
+
+    def visit_FunctionDef(self, node):
+        return self._visit_docstring(node)
+
     def visit_ClassDef(self, node):
         """
         Annotate the Class node with it's original type from the Vyper source.
diff --git a/vyper/ast/nodes.py b/vyper/ast/nodes.py
--- a/vyper/ast/nodes.py
+++ b/vyper/ast/nodes.py
@@ -500,17 +500,6 @@ class TopLevel(VyperNode):
     """
     __slots__ = ('body', 'name', 'doc_string')
 
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
-        if (
-            kwargs.get('body') and
-            isinstance(self.body[0], Expr) and
-            isinstance(self.body[0].value, Str)
-        ):
-            # if the first body item is a docstring, move it into it's own field
-            self.doc_string = self.body[0].value
-            del self.body[0]
-
     def __getitem__(self, key):
         return self.body[key]
 
@@ -532,6 +521,19 @@ class FunctionDef(TopLevel):
     __slots__ = ('args', 'returns', 'decorator_list', 'pos')
 
 
+class DocStr(VyperNode):
+    """
+    A docstring.
+
+    Attributes
+    ----------
+    value : str
+        Value of the node, represented as an string.
+    """
+    __slots__ = ('value',)
+    _translated_fields = {'s': 'value'}
+
+
 class arguments(VyperNode):
     __slots__ = ('args', 'defaults', 'default')
     _only_empty_fields = ('vararg', 'kwonlyargs', 'kwarg', 'kw_defaults')


Test patch: