Repository: sphinx-doc/sphinx

Issue:
Autodoc ignores documented instance/class attributes and comment when using typing definition
Subject: autodoc ignores documented instance attributes when using typing definition.

<!--
  Important: This is a list of issues for Sphinx, not a forum.
  If you'd like to post a question, please move to sphinx-users group.
  https://groups.google.com/forum/#!forum/sphinx-users

  Thanks,
-->

### Problem
If typing is used in a instance attribute, all instance attributes in the class are ignored.

#### Procedure to reproduce the problem
```
class ExampleClass(object):
    """ Class docs """

    class_attribute_without_typing = None
    """This is a class attribute without typing. This comment is not ignored."""

    class_attribute_with_typing: str = None
    """This is a class attribute with typing. This comment is ignored."""

    def __init__(self):

        self.instance_attribute_without_typing = ""
        """This is an instance attribute without typing. This comment is not ignored."""

        self.instance_attribute_with_typing: str = ""
        """This is an instance attribute with typing. This comment is ignored. This attribute is also ignored."""
```

#### Error logs / results
Sphinx does not generate any warnings or errors.
Here's an example of unexpected documentation:
```
class ExampleClass[source]
    Bases: object

    Class docs
    class_attribute_with_typing = None

    class_attribute_without_typing = None
        This is a class attribute without typing. This comment is not ignored.

    instance_attribute_without_typing = None
        This is an instance attribute without typing. This comment is not ignored.
```

#### Expected results
Module attributes, class attributes, and instance attributes should be documented if they contain docstrings, even with typing definition.
```
class ExampleClass[source]
    Bases: object

    Class docs
    class_attribute_with_typing (str) = None
        This is a class attribute with typing. This comment is ignored.

    class_attribute_without_typing = None
        This is a class attribute without typing. This comment is not ignored.

    instance_attribute_without_typing = None
        This is an instance attribute without typing. This comment is not ignored.
    
    instance_attribute_with_typing (str) = None
        This is an instance attribute with typing. This comment is ignored. This attribute is also ignored.
```
### Environment info
- OS: Ubuntu 16.04, 64-bit
- Python version: 3.6.2
- Sphinx version: 1.8.0


Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/sphinx-doc/sphinx/pull/4813?src=pr&el=h1) Report
> Merging [#4813](https://codecov.io/gh/sphinx-doc/sphinx/pull/4813?src=pr&el=desc) into [1.7](https://codecov.io/gh/sphinx-doc/sphinx/commit/e81f80880a3319fe88cb12c6d5c8844ed31268b3?src=pr&el=desc) will **increase** coverage by `<.01%`.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/sphinx-doc/sphinx/pull/4813/graphs/tree.svg?token=VFlFSELbkw&width=650&height=150&src=pr)](https://codecov.io/gh/sphinx-doc/sphinx/pull/4813?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##              1.7    #4813      +/-   ##
==========================================
+ Coverage   81.86%   81.87%   +<.01%     
==========================================
  Files         279      279              
  Lines       37423    37440      +17     
  Branches     5819     5821       +2     
==========================================
+ Hits        30637    30653      +16     
  Misses       5483     5483              
- Partials     1303     1304       +1
```


| [Impacted Files](https://codecov.io/gh/sphinx-doc/sphinx/pull/4813?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [sphinx/pycode/parser.py](https://codecov.io/gh/sphinx-doc/sphinx/pull/4813/diff?src=pr&el=tree#diff-c3BoaW54L3B5Y29kZS9wYXJzZXIucHk=) | `95.7% <100%> (+0.16%)` | :arrow_up: |
| [tests/test\_pycode\_parser.py](https://codecov.io/gh/sphinx-doc/sphinx/pull/4813/diff?src=pr&el=tree#diff-dGVzdHMvdGVzdF9weWNvZGVfcGFyc2VyLnB5) | `100% <100%> (ø)` | :arrow_up: |
| [sphinx/builders/html.py](https://codecov.io/gh/sphinx-doc/sphinx/pull/4813/diff?src=pr&el=tree#diff-c3BoaW54L2J1aWxkZXJzL2h0bWwucHk=) | `82.54% <0%> (-0.12%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/sphinx-doc/sphinx/pull/4813?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/sphinx-doc/sphinx/pull/4813?src=pr&el=footer). Last update [e81f808...201c4b1](https://codecov.io/gh/sphinx-doc/sphinx/pull/4813?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/CHANGES b/CHANGES
index 48e22847c..d3ad98ec6 100644
--- a/CHANGES
+++ b/CHANGES
@@ -22,6 +22,7 @@ Bugs fixed
 * #4789: imgconverter: confused by convert.exe of Windows
 * #4783: On windows, Sphinx crashed when drives of srcdir and outdir are
   different
+* #4812: autodoc ignores type annotated variables
 
 Testing
 --------
diff --git a/sphinx/directives/other.py b/sphinx/directives/other.py
index e1ba1a279..6aa8889af 100644
--- a/sphinx/directives/other.py
+++ b/sphinx/directives/other.py
@@ -23,7 +23,7 @@
 
 if False:
     # For type annotation
-    from typing import Any, Dict, List, Tuple  # NOQA
+    from typing import Any, Dict, Generator, List, Tuple  # NOQA
     from sphinx.application import Sphinx  # NOQA
 
 
diff --git a/sphinx/pycode/parser.py b/sphinx/pycode/parser.py
index f15da664a..deba48b1c 100644
--- a/sphinx/pycode/parser.py
+++ b/sphinx/pycode/parser.py
@@ -12,6 +12,7 @@
 import inspect
 import itertools
 import re
+import sys
 import tokenize
 from token import NAME, NEWLINE, INDENT, DEDENT, NUMBER, OP, STRING
 from tokenize import COMMENT, NL
@@ -27,6 +28,21 @@
 emptyline_re = re.compile(u'^\\s*(#.*)?$')
 
 
+if sys.version_info >= (3, 6):
+    ASSIGN_NODES = (ast.Assign, ast.AnnAssign)
+else:
+    ASSIGN_NODES = (ast.Assign)
+
+
+def get_assign_targets(node):
+    # type: (ast.AST) -> List[ast.expr]
+    """Get list of targets from Assign and AnnAssign node."""
+    if isinstance(node, ast.Assign):
+        return node.targets
+    else:
+        return [node.target]  # type: ignore
+
+
 def get_lvar_names(node, self=None):
     # type: (ast.AST, ast.expr) -> List[unicode]
     """Convert assignment-AST to variable names.
@@ -284,7 +300,8 @@ def visit_Assign(self, node):
         # type: (ast.Assign) -> None
         """Handles Assign node and pick up a variable comment."""
         try:
-            varnames = sum([get_lvar_names(t, self=self.get_self()) for t in node.targets], [])
+            targets = get_assign_targets(node)
+            varnames = sum([get_lvar_names(t, self=self.get_self()) for t in targets], [])
             current_line = self.get_line(node.lineno)
         except TypeError:
             return  # this assignment is not new definition!
@@ -320,12 +337,18 @@ def visit_Assign(self, node):
         for varname in varnames:
             self.add_entry(varname)
 
+    def visit_AnnAssign(self, node):
+        # type: (ast.AST) -> None
+        """Handles AnnAssign node and pick up a variable comment."""
+        self.visit_Assign(node)  # type: ignore
+
     def visit_Expr(self, node):
         # type: (ast.Expr) -> None
         """Handles Expr node and pick up a comment if string."""
-        if (isinstance(self.previous, ast.Assign) and isinstance(node.value, ast.Str)):
+        if (isinstance(self.previous, ASSIGN_NODES) and isinstance(node.value, ast.Str)):
             try:
-                varnames = get_lvar_names(self.previous.targets[0], self.get_self())
+                targets = get_assign_targets(self.previous)
+                varnames = get_lvar_names(targets[0], self.get_self())
                 for varname in varnames:
                     if isinstance(node.value.s, text_type):
                         docstring = node.value.s


Test patch:
diff --git a/tests/test_pycode_parser.py b/tests/test_pycode_parser.py
index b9327999b..09f1f41f5 100644
--- a/tests/test_pycode_parser.py
+++ b/tests/test_pycode_parser.py
@@ -9,6 +9,8 @@
     :license: BSD, see LICENSE for details.
 """
 
+import sys
+
 import pytest
 from six import PY2
 
@@ -94,6 +96,18 @@ def test_comment_picker_location():
                                ('Foo', 'attr3'): 'comment for attr3(3)'}
 
 
+@pytest.mark.skipif(sys.version_info < (3, 6), reason='tests for py36+ syntax')
+def test_annotated_assignment_py36():
+    source = ('a: str = "Sphinx"  #: comment\n'
+              'b: int = 1\n'
+              '"""string on next line"""')
+    parser = Parser(source)
+    parser.parse()
+    assert parser.comments == {('', 'a'): 'comment',
+                               ('', 'b'): 'string on next line'}
+    assert parser.definitions == {}
+
+
 def test_complex_assignment():
     source = ('a = 1 + 1; b = a  #: compound statement\n'
               'c, d = (1, 1)  #: unpack assignment\n'