Repository: dropbox/pyannotate

Issue:
Fail to apply typeinfo to a python module in a subdirectory
The following will fail : 

```
> python toto\toto.py 
( this generates a type_info.json in current directory )
```

```
>dir
[...]
25/07/2018  06:22    <DIR>          toto
25/07/2018  06:21               784 type_info.json
```

```
>pyannotate -3 toto\toto.py --type-info type_info.json
No files need to be modified.
NOTE: this was a dry run; use -w to write files
```

strange, there are type annotations in type_info.json with the correct path
```
>type type_info.json
[
    {
        "path": "toto\\toto.py",
        "line": 2,
        "func_name": "add",
        "type_comments": [
            "(*int) -> int",
            "(*List[int]) -> List[int]",
            "(*Tuple[int, int]) -> Tuple[int, int]"
        ],
        "samples": 3
    },
    {
        "path": "toto\\toto.py",
        "line": 8,
        "func_name": "add2",
        "type_comments": [
            "(Tuple[int, int], Tuple[int, int]) -> Tuple[int, int, int, int]",
            "(List[int], List[int]) -> List[int]",
            "(int, int) -> int"
        ],
        "samples": 3
    },
    {
        "path": "toto\\toto.py",
        "line": 11,
        "func_name": "main",
        "type_comments": [
            "() -> None"
        ],
        "samples": 1
    }
]
```

edit the type_info.json to remove the "toto\\"
```
>type type_info.json
[
    {
        "path": "toto.py",
        "line": 2,
        "func_name": "add",
        "type_comments": [
            "(*int) -> int",
            "(*List[int]) -> List[int]",
            "(*Tuple[int, int]) -> Tuple[int, int]"
        ],
        "samples": 3
    },
    {
        "path": "toto.py",
        "line": 8,
        "func_name": "add2",
        "type_comments": [
            "(Tuple[int, int], Tuple[int, int]) -> Tuple[int, int, int, int]",
            "(List[int], List[int]) -> List[int]",
            "(int, int) -> int"
        ],
        "samples": 3
    },
    {
        "path": "toto.py",
        "line": 11,
        "func_name": "main",
        "type_comments": [
            "() -> None"
        ],
        "samples": 1
    }
]

```
try again
```
>pyannotate -3 toto\toto.py --type-info type_info.json
Refactored toto\toto.py
--- toto\toto.py        (original)
+++ toto\toto.py        (refactored)
@@ -1,14 +1,18 @@
+from typing import Any
+from typing import List
+from typing import Tuple
+from typing import Union

-def add(*args):
+def add(*args: Any) -> Union[List[int], Tuple[int, int], int]:
     ret = args[0]
     for v in args:
         ret += v
     return v

-def add2(v1, v2):
+def add2(v1: Union[List[int], Tuple[int, int], int], v2: Union[List[int], Tuple[int, int], int]) -> Union[List[int], Tuple[int, int, int, int], int]:
     return v1+v2

-def main():
+def main() -> None:
     print( add(1,2,3) )
     print( add([1,2], [3,4]) )
     print( add((1,2), (3,4)) )
Files that need to be modified:
toto\toto.py
NOTE: this was a dry run; use -w to write files

```

it worked...

It looks like pyannotate is trimming directories from type_info.json too agressively.



Comments:

Patch:
diff --git a/pyannotate_tools/fixes/fix_annotate_json.py b/pyannotate_tools/fixes/fix_annotate_json.py
index 5d2c9cd..3fed7f2 100644
--- a/pyannotate_tools/fixes/fix_annotate_json.py
+++ b/pyannotate_tools/fixes/fix_annotate_json.py
@@ -202,10 +202,12 @@ class FixAnnotateJson(FixAnnotate):
             self.init_stub_json()
         data = self.__class__.stub_json
         # We are using relative paths in the JSON.
-        items = [it for it in data
-                 if it['func_name'] == funcname and
-                    (os.path.join(self.__class__.top_dir, it['path']) ==
-                     os.path.abspath(self.filename))]
+        items = [
+            it for it in data
+            if it['func_name'] == funcname and
+               (it['path'] == self.filename or
+                os.path.join(self.__class__.top_dir, it['path']) == os.path.abspath(self.filename))
+        ]
         if len(items) > 1:
             # this can happen, because of
             # 1) nested functions


Test patch:
diff --git a/tests/integration_test.py b/tests/integration_test.py
index e672dab..033cbf6 100644
--- a/tests/integration_test.py
+++ b/tests/integration_test.py
@@ -88,7 +88,6 @@ class IntegrationTest(unittest.TestCase):
         assert b'+    # type: () -> None' in lines
         assert b'+    # type: (int, int) -> int' in lines
 
-    @unittest.skip("Doesn't work yet")
     def test_subdir(self):
         os.makedirs('foo')
         with open('foo/gcd.py', 'w') as f:
@@ -99,7 +98,9 @@ class IntegrationTest(unittest.TestCase):
             f.write('from gcd import main\n')
             f.write(driver)
         subprocess.check_call([sys.executable, 'driver.py'])
-        output = subprocess.check_output([sys.executable, '-m', 'pyannotate_tools.annotations', 'foo/gcd.py'])
+        output = subprocess.check_output([sys.executable, '-m', 'pyannotate_tools.annotations',
+                                          # Construct platform-correct pathname:
+                                          os.path.join('foo', 'gcd.py')])
         lines = output.splitlines()
         assert b'+    # type: () -> None' in lines
         assert b'+    # type: (int, int) -> int' in lines