From d9914f13c2f186cae7edf5a5ffa7c7128759b952 Mon Sep 17 00:00:00 2001 From: tharvik Date: Mon, 18 Jul 2016 16:31:55 +0200 Subject: [PATCH 1/8] stubgen: add type to method arguments --- mypy/stubgen.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/mypy/stubgen.py b/mypy/stubgen.py index b765105c24424..120caf9c67e57 100644 --- a/mypy/stubgen.py +++ b/mypy/stubgen.py @@ -254,22 +254,24 @@ def visit_func_def(self, o: FuncDef) -> None: if init_stmt: if kind == ARG_NAMED and '*' not in args: args.append('*') - arg = '%s=' % name rvalue = init_stmt.rvalue + typename = 'Any' if isinstance(rvalue, IntExpr): - arg += str(rvalue.value) + typename = 'int' elif isinstance(rvalue, StrExpr): - arg += "''" + typename = 'str' elif isinstance(rvalue, BytesExpr): - arg += "b''" + typename = 'bytes' elif isinstance(rvalue, FloatExpr): - arg += "0.0" + typename = 'float' elif isinstance(rvalue, UnaryExpr) and isinstance(rvalue.expr, IntExpr): - arg += '-%s' % rvalue.expr.value - elif isinstance(rvalue, NameExpr) and rvalue.name in ('None', 'True', 'False'): - arg += rvalue.name - else: - arg += '...' + typename = 'int' + elif isinstance(rvalue, NameExpr) and rvalue.name in ('True', 'False'): + typename = 'bool' + elif isinstance(rvalue, NameExpr) and rvalue.name == 'None': + self.add_typing_import('Optional') + typename = 'Optional[Any]' + arg = '{}: {} = ...'.format(name, typename) elif kind == ARG_STAR: arg = '*%s' % name elif kind == ARG_STAR2: From 3913bea9a509cc000daefa412739ce6c58aaff23 Mon Sep 17 00:00:00 2001 From: tharvik Date: Mon, 18 Jul 2016 17:30:56 +0200 Subject: [PATCH 2/8] stubgen: add type to assignments --- mypy/stubgen.py | 56 ++++++++++++++++++++++++++----------------------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/mypy/stubgen.py b/mypy/stubgen.py index 120caf9c67e57..8b8341615837a 100644 --- a/mypy/stubgen.py +++ b/mypy/stubgen.py @@ -239,8 +239,8 @@ def visit_func_def(self, o: FuncDef) -> None: self.add('\n') if not self.is_top_level(): self_inits = find_self_initializers(o) - for init in self_inits: - init_code = self.get_init(init) + for init, value in self_inits: + init_code = self.get_init(init, value) if init_code: self.add(init_code) self.add("%sdef %s(" % (self._indent, o.name())) @@ -254,23 +254,7 @@ def visit_func_def(self, o: FuncDef) -> None: if init_stmt: if kind == ARG_NAMED and '*' not in args: args.append('*') - rvalue = init_stmt.rvalue - typename = 'Any' - if isinstance(rvalue, IntExpr): - typename = 'int' - elif isinstance(rvalue, StrExpr): - typename = 'str' - elif isinstance(rvalue, BytesExpr): - typename = 'bytes' - elif isinstance(rvalue, FloatExpr): - typename = 'float' - elif isinstance(rvalue, UnaryExpr) and isinstance(rvalue.expr, IntExpr): - typename = 'int' - elif isinstance(rvalue, NameExpr) and rvalue.name in ('True', 'False'): - typename = 'bool' - elif isinstance(rvalue, NameExpr) and rvalue.name == 'None': - self.add_typing_import('Optional') - typename = 'Optional[Any]' + typename = self.get_str_type_of_node(init_stmt.rvalue) arg = '{}: {} = ...'.format(name, typename) elif kind == ARG_STAR: arg = '*%s' % name @@ -351,7 +335,7 @@ def visit_assignment_stmt(self, o: AssignmentStmt) -> None: found = False for item in items: if isinstance(item, NameExpr): - init = self.get_init(item.name) + init = self.get_init(item.name, o.rvalue) if init: found = True if not sep and not self._indent and \ @@ -450,7 +434,7 @@ def visit_import(self, o: Import) -> None: self.add_import_line('import %s as %s\n' % (id, target_name)) self.record_name(target_name) - def get_init(self, lvalue: str) -> str: + def get_init(self, lvalue: str, rvalue: Node) -> str: """Return initializer for a variable. Return None if we've generated one already or if the variable is internal. @@ -462,8 +446,8 @@ def get_init(self, lvalue: str) -> str: if self.is_private_name(lvalue) or self.is_not_in_all(lvalue): return None self._vars[-1].append(lvalue) - self.add_typing_import('Any') - return '%s%s = ... # type: Any\n' % (self._indent, lvalue) + typename = self.get_str_type_of_node(rvalue) + return '%s%s = ... # type: %s\n' % (self._indent, lvalue, typename) def add(self, string: str) -> None: """Add text to generated stub.""" @@ -509,6 +493,26 @@ def is_private_name(self, name: str) -> bool: '__setstate__', '__slots__')) + def get_str_type_of_node(self, rvalue: Node) -> str: + if isinstance(rvalue, IntExpr): + return 'int' + if isinstance(rvalue, StrExpr): + return 'str' + if isinstance(rvalue, BytesExpr): + return 'bytes' + if isinstance(rvalue, FloatExpr): + return 'float' + if isinstance(rvalue, UnaryExpr) and isinstance(rvalue.expr, IntExpr): + return 'int' + if isinstance(rvalue, NameExpr) and rvalue.name in ('True', 'False'): + return 'bool' + if isinstance(rvalue, NameExpr) and rvalue.name == 'None': + self.add_typing_import('Optional') + self.add_typing_import('Any') + return 'Optional[Any]' + self.add_typing_import('Any') + return 'Any' + def is_top_level(self) -> bool: """Are we processing the top level of a file?""" return self._indent == '' @@ -526,8 +530,8 @@ def is_recorded_name(self, name: str) -> bool: return self.is_top_level() and name in self._toplevel_names -def find_self_initializers(fdef: FuncBase) -> List[str]: - results = [] # type: List[str] +def find_self_initializers(fdef: FuncBase) -> List[Tuple[str, Node]]: + results = [] # type: List[Tuple[str, Node]] class SelfTraverser(mypy.traverser.TraverserVisitor): def visit_assignment_stmt(self, o: AssignmentStmt) -> None: @@ -535,7 +539,7 @@ def visit_assignment_stmt(self, o: AssignmentStmt) -> None: if (isinstance(lvalue, MemberExpr) and isinstance(lvalue.expr, NameExpr) and lvalue.expr.name == 'self'): - results.append(lvalue.name) + results.append((lvalue.name, o.rvalue)) fdef.accept(SelfTraverser()) return results From 7d20c0774636cd95dc6c2e2b5e18626db9b52346 Mon Sep 17 00:00:00 2001 From: tharvik Date: Mon, 18 Jul 2016 17:41:08 +0200 Subject: [PATCH 3/8] stubgen: set return type for __init__ --- mypy/stubgen.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/mypy/stubgen.py b/mypy/stubgen.py index 8b8341615837a..a46a2ada538b5 100644 --- a/mypy/stubgen.py +++ b/mypy/stubgen.py @@ -263,8 +263,15 @@ def visit_func_def(self, o: FuncDef) -> None: else: arg = name args.append(arg) + retname = None + if o.name() == '__init__': + retname = 'None' + retfield = '' + if retname is not None: + retfield = ' -> ' + retname + self.add(', '.join(args)) - self.add("): ...\n") + self.add("){}: ...\n".format(retfield)) self._state = FUNC def visit_decorator(self, o: Decorator) -> None: From 6ec2f1dde8b3fefd03a71c4e7d66ca7c045fd5ed Mon Sep 17 00:00:00 2001 From: tharvik Date: Mon, 18 Jul 2016 18:23:32 +0200 Subject: [PATCH 4/8] stubgen: sort import output --- mypy/stubgen.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/stubgen.py b/mypy/stubgen.py index a46a2ada538b5..3598a98b29f06 100644 --- a/mypy/stubgen.py +++ b/mypy/stubgen.py @@ -477,7 +477,7 @@ def output(self) -> str: """Return the text for the stub.""" imports = '' if self._imports: - imports += 'from typing import %s\n' % ", ".join(self._imports) + imports += 'from typing import %s\n' % ", ".join(sorted(self._imports)) if self._import_lines: imports += ''.join(self._import_lines) if imports and self._output: From ae032946554a8e677a40423cc03abade831b9889 Mon Sep 17 00:00:00 2001 From: tharvik Date: Mon, 18 Jul 2016 17:53:11 +0200 Subject: [PATCH 5/8] stubgen: update test-data --- test-data/unit/stubgen.test | 73 +++++++++++++++---------------------- 1 file changed, 29 insertions(+), 44 deletions(-) diff --git a/test-data/unit/stubgen.test b/test-data/unit/stubgen.test index ae8e61eb446ed..6fa7a78ad0bc5 100644 --- a/test-data/unit/stubgen.test +++ b/test-data/unit/stubgen.test @@ -20,38 +20,42 @@ def g(arg): ... def f(a, b=2): ... def g(b=-1, c=0): ... [out] -def f(a, b=2): ... -def g(b=-1, c=0): ... +def f(a, b: int = ...): ... +def g(b: int = ..., c: int = ...): ... [case testDefaultArgNone] def f(x=None): ... [out] -def f(x=None): ... +from typing import Any, Optional + +def f(x: Optional[Any] = ...): ... [case testDefaultArgBool] def f(x=True, y=False): ... [out] -def f(x=True, y=False): ... +def f(x: bool = ..., y: bool = ...): ... [case testDefaultArgStr] def f(x='foo'): ... [out] -def f(x=''): ... +def f(x: str = ...): ... [case testDefaultArgBytes] def f(x=b'foo'): ... [out] -def f(x=b''): ... +def f(x: bytes = ...): ... [case testDefaultArgFloat] def f(x=1.2): ... [out] -def f(x=0.0): ... +def f(x: float = ...): ... [case testDefaultArgOther] def f(x=ord): ... [out] -def f(x=...): ... +from typing import Any + +def f(x: Any = ...): ... [case testVarArgs] def f(x, *y): ... @@ -77,26 +81,20 @@ def g(): ... [case testVariable] x = 1 [out] -from typing import Any - -x = ... # type: Any +x = ... # type: int [case testMultipleVariable] x = y = 1 [out] -from typing import Any - -x = ... # type: Any -y = ... # type: Any +x = ... # type: int +y = ... # type: int [case testClassVariable] class C: x = 1 [out] -from typing import Any - class C: - x = ... # type: Any + x = ... # type: int [case testSelfAssignment] class C: @@ -104,11 +102,9 @@ class C: self.x = 1 x.y = 2 [out] -from typing import Any - class C: - x = ... # type: Any - def __init__(self): ... + x = ... # type: int + def __init__(self) -> None: ... [case testSelfAndClassBodyAssignment] x = 1 @@ -118,13 +114,11 @@ class C: self.x = 1 self.x = 1 [out] -from typing import Any - -x = ... # type: Any +x = ... # type: int class C: - x = ... # type: Any - def __init__(self): ... + x = ... # type: int + def __init__(self) -> None: ... [case testEmptyClass] class A: ... @@ -189,8 +183,8 @@ y = ... # type: Any def f(x, *, y=1): ... def g(x, *, y=1, z=2): ... [out] -def f(x, *, y=1): ... -def g(x, *, y=1, z=2): ... +def f(x, *, y: int = ...): ... +def g(x, *, y: int = ..., z: int = ...): ... [case testProperty] class A: @@ -311,10 +305,8 @@ class A: x = 1 def f(self): ... [out] -from typing import Any - class A: - x = ... # type: Any + x = ... # type: int def f(self): ... [case testMultiplePrivateDefs] @@ -332,10 +324,9 @@ from re import match, search, sub __all__ = ['match', 'sub', 'x'] x = 1 [out] -from typing import Any from re import match as match, sub as sub -x = ... # type: Any +x = ... # type: int [case testExportModule_import] import re @@ -343,10 +334,9 @@ __all__ = ['re', 'x'] x = 1 y = 2 [out] -from typing import Any import re as re -x = ... # type: Any +x = ... # type: int [case testExportModuleAs_import] import re as rex @@ -354,10 +344,9 @@ __all__ = ['rex', 'x'] x = 1 y = 2 [out] -from typing import Any import re as rex -x = ... # type: Any +x = ... # type: int [case testExportModuleInPackage_import] import urllib.parse as p @@ -384,11 +373,9 @@ x = 1 class C: def g(self): ... [out] -from typing import Any - def f(): ... -x = ... # type: Any +x = ... # type: int class C: def g(self): ... @@ -521,11 +508,9 @@ class A: def f(self): ... def g(self): ... [out] -from typing import Any - class A: class B: - x = ... # type: Any + x = ... # type: int def f(self): ... def g(self): ... From 61f6a59777a3106e6b562934e253429028c0af4b Mon Sep 17 00:00:00 2001 From: tharvik Date: Wed, 7 Sep 2016 16:07:20 +0200 Subject: [PATCH 6/8] remove specific for Optional --- mypy/stubgen.py | 4 ---- test-data/unit/stubgen.test | 4 ++-- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/mypy/stubgen.py b/mypy/stubgen.py index 3598a98b29f06..c57804feef6ef 100644 --- a/mypy/stubgen.py +++ b/mypy/stubgen.py @@ -513,10 +513,6 @@ def get_str_type_of_node(self, rvalue: Node) -> str: return 'int' if isinstance(rvalue, NameExpr) and rvalue.name in ('True', 'False'): return 'bool' - if isinstance(rvalue, NameExpr) and rvalue.name == 'None': - self.add_typing_import('Optional') - self.add_typing_import('Any') - return 'Optional[Any]' self.add_typing_import('Any') return 'Any' diff --git a/test-data/unit/stubgen.test b/test-data/unit/stubgen.test index 6fa7a78ad0bc5..40778f8e9a073 100644 --- a/test-data/unit/stubgen.test +++ b/test-data/unit/stubgen.test @@ -26,9 +26,9 @@ def g(b: int = ..., c: int = ...): ... [case testDefaultArgNone] def f(x=None): ... [out] -from typing import Any, Optional +from typing import Any -def f(x: Optional[Any] = ...): ... +def f(x: Any = ...): ... [case testDefaultArgBool] def f(x=True, y=False): ... From 266040fe0174f96d3dbdd280d294ecdaab6e8eb9 Mon Sep 17 00:00:00 2001 From: tharvik Date: Wed, 7 Sep 2016 16:22:17 +0200 Subject: [PATCH 7/8] Revert "remove specific for Optional" This reverts commit 61f6a59777a3106e6b562934e253429028c0af4b. --- mypy/stubgen.py | 4 ++++ test-data/unit/stubgen.test | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/mypy/stubgen.py b/mypy/stubgen.py index c57804feef6ef..3598a98b29f06 100644 --- a/mypy/stubgen.py +++ b/mypy/stubgen.py @@ -513,6 +513,10 @@ def get_str_type_of_node(self, rvalue: Node) -> str: return 'int' if isinstance(rvalue, NameExpr) and rvalue.name in ('True', 'False'): return 'bool' + if isinstance(rvalue, NameExpr) and rvalue.name == 'None': + self.add_typing_import('Optional') + self.add_typing_import('Any') + return 'Optional[Any]' self.add_typing_import('Any') return 'Any' diff --git a/test-data/unit/stubgen.test b/test-data/unit/stubgen.test index 40778f8e9a073..6fa7a78ad0bc5 100644 --- a/test-data/unit/stubgen.test +++ b/test-data/unit/stubgen.test @@ -26,9 +26,9 @@ def g(b: int = ..., c: int = ...): ... [case testDefaultArgNone] def f(x=None): ... [out] -from typing import Any +from typing import Any, Optional -def f(x: Any = ...): ... +def f(x: Optional[Any] = ...): ... [case testDefaultArgBool] def f(x=True, y=False): ... From 858cf509a445dfa1b2f14a2efcf68a48229ba331 Mon Sep 17 00:00:00 2001 From: tharvik Date: Wed, 7 Sep 2016 16:25:25 +0200 Subject: [PATCH 8/8] only infer Optional for func --- mypy/stubgen.py | 8 +++++--- test-data/unit/stubgen.test | 12 ++++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/mypy/stubgen.py b/mypy/stubgen.py index 3598a98b29f06..6b5f71c8264ec 100644 --- a/mypy/stubgen.py +++ b/mypy/stubgen.py @@ -254,7 +254,7 @@ def visit_func_def(self, o: FuncDef) -> None: if init_stmt: if kind == ARG_NAMED and '*' not in args: args.append('*') - typename = self.get_str_type_of_node(init_stmt.rvalue) + typename = self.get_str_type_of_node(init_stmt.rvalue, True) arg = '{}: {} = ...'.format(name, typename) elif kind == ARG_STAR: arg = '*%s' % name @@ -500,7 +500,8 @@ def is_private_name(self, name: str) -> bool: '__setstate__', '__slots__')) - def get_str_type_of_node(self, rvalue: Node) -> str: + def get_str_type_of_node(self, rvalue: Node, + can_infer_optional: bool = False) -> str: if isinstance(rvalue, IntExpr): return 'int' if isinstance(rvalue, StrExpr): @@ -513,7 +514,8 @@ def get_str_type_of_node(self, rvalue: Node) -> str: return 'int' if isinstance(rvalue, NameExpr) and rvalue.name in ('True', 'False'): return 'bool' - if isinstance(rvalue, NameExpr) and rvalue.name == 'None': + if can_infer_optional and \ + isinstance(rvalue, NameExpr) and rvalue.name == 'None': self.add_typing_import('Optional') self.add_typing_import('Any') return 'Optional[Any]' diff --git a/test-data/unit/stubgen.test b/test-data/unit/stubgen.test index 6fa7a78ad0bc5..3978ac6e46869 100644 --- a/test-data/unit/stubgen.test +++ b/test-data/unit/stubgen.test @@ -543,5 +543,17 @@ def syslog(a): pass [out] def syslog(a): ... +[case testInferOptionalOnlyFunc] +class A: + x = None + def __init__(self, a=None) -> None: + self.x = [] +[out] +from typing import Any, Optional + +class A: + x = ... # type: Any + def __init__(self, a: Optional[Any] = ...) -> None: ... + -- More features/fixes: -- do not export deleted names