Repository: pylint-dev/astroid

Issue:
calling `parent` on a node that is part of a type comment returns the (original) `_ast` module's FunctionDef
Consider this snippet:

```python
def f(s):
    # type: (str) -> int
    return 42
```

Given the `node` from the type comment, `Name.int(name='int')`, if I call its parent (i.e., `node.parent`), I get the original _ast module's FunctionDef, instead of astroid's FunctionDef.



Discussion / hints:
Hi, thanks for the report. I tried to reproduce this but I was not able, can you paste the exact code you used to demonstrate the issue?
@PCManticore, reproducibility of this one is straightforward to me, here's a (partial) example — but note that I had a typo in the original snippet: I missed the colon after `type`, which I just fixed; perhaps this is the reason you couldn't reproduce?

Assume an AST visitor with:

```
def visit_functiondef(self, node: nodes.FunctionDef)
   print(f'the parent of type comment return is {node.type_comment_returns.parent}')
```

To me, this prints:
<img width="565" alt="Screen Shot 2021-01-04 at 12 06 04" src="https://user-images.githubusercontent.com/2905588/103548904-3a42b800-4e85-11eb-8bca-aa2f9a17313f.png">







Fix (patch):
diff --git a/ChangeLog b/ChangeLog
index 9ff42a6e77..be30ed4525 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -119,6 +119,13 @@ Release Date: TBA
 
   Close PyCQA/pylint#3904
 
+* Corrected the parent of function type comment nodes.
+
+  These nodes used to be parented to their original ast.FunctionDef parent
+  but are now correctly parented to their astroid.FunctionDef parent.
+
+  Close PyCQA/astroid#851
+
 
 What's New in astroid 2.4.2?
 ============================
diff --git a/astroid/rebuilder.py b/astroid/rebuilder.py
index 3fc1a83f2b..e56abbf878 100644
--- a/astroid/rebuilder.py
+++ b/astroid/rebuilder.py
@@ -238,7 +238,7 @@ def check_type_comment(self, node, parent):
 
         return type_object.value
 
-    def check_function_type_comment(self, node):
+    def check_function_type_comment(self, node, parent):
         type_comment = getattr(node, "type_comment", None)
         if not type_comment:
             return None
@@ -251,10 +251,10 @@ def check_function_type_comment(self, node):
 
         returns = None
         argtypes = [
-            self.visit(elem, node) for elem in (type_comment_ast.argtypes or [])
+            self.visit(elem, parent) for elem in (type_comment_ast.argtypes or [])
         ]
         if type_comment_ast.returns:
-            returns = self.visit(type_comment_ast.returns, node)
+            returns = self.visit(type_comment_ast.returns, parent)
 
         return returns, argtypes
 
@@ -615,7 +615,7 @@ def _visit_functiondef(self, cls, node, parent):
             returns = None
 
         type_comment_args = type_comment_returns = None
-        type_comment_annotation = self.check_function_type_comment(node)
+        type_comment_annotation = self.check_function_type_comment(node, newnode)
         if type_comment_annotation:
             type_comment_returns, type_comment_args = type_comment_annotation
         newnode.postinit(


Test changes:
diff --git a/tests/unittest_nodes.py b/tests/unittest_nodes.py
index d138ee1704..0ba6b3e541 100644
--- a/tests/unittest_nodes.py
+++ b/tests/unittest_nodes.py
@@ -1181,6 +1181,19 @@ def f_arg_comment(
                 assert actual_arg.as_string() == expected_arg
 
 
+@pytest.mark.skipif(not HAS_TYPED_AST, reason="requires typed_ast")
+def test_correct_function_type_comment_parent():
+    data = """
+        def f(a):
+            # type: (A) -> A
+            pass
+    """
+    astroid = builder.parse(data)
+    f = astroid.body[0]
+    assert f.type_comment_args[0].parent is f
+    assert f.type_comment_returns.parent is f
+
+
 def test_is_generator_for_yield_assignments():
     node = astroid.extract_node(
         """