Skip to content

Commit 05c861b

Browse files
committed
Merge branch 'import-as-in-stubs'
Closes #453.
2 parents d07c460 + 6437aea commit 05c861b

15 files changed

+275
-195
lines changed

mypy/git.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ def verify_git_integrity_or_abort(datadir: str) -> None:
114114
Potentially output warnings/errors (to stderr), and exit with status 1
115115
if we detected a severe problem.
116116
"""
117-
117+
datadir = datadir or '.'
118118
if not is_git_repo(datadir):
119119
return
120120
if not have_git():

mypy/nodes.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -176,21 +176,21 @@ class ImportBase(Node):
176176
class Import(ImportBase):
177177
"""import m [as n]"""
178178

179-
ids = None # type: List[Tuple[str, str]] # (module id, as id)
179+
ids = None # type: List[Tuple[str, Optional[str]]] # (module id, as id)
180180

181-
def __init__(self, ids: List[Tuple[str, str]]) -> None:
181+
def __init__(self, ids: List[Tuple[str, Optional[str]]]) -> None:
182182
self.ids = ids
183183

184184
def accept(self, visitor: NodeVisitor[T]) -> T:
185185
return visitor.visit_import(self)
186186

187187

188188
class ImportFrom(ImportBase):
189-
"""from m import x, ..."""
189+
"""from m import x [as y], ..."""
190190

191-
names = None # type: List[Tuple[str, str]] # Tuples (name, as name)
191+
names = None # type: List[Tuple[str, Optional[str]]] # Tuples (name, as name)
192192

193-
def __init__(self, id: str, relative: int, names: List[Tuple[str, str]]) -> None:
193+
def __init__(self, id: str, relative: int, names: List[Tuple[str, Optional[str]]]) -> None:
194194
self.id = id
195195
self.names = names
196196
self.relative = relative
@@ -1629,14 +1629,19 @@ class SymbolTableNode:
16291629
mod_id = ''
16301630
# If this not None, override the type of the 'node' attribute.
16311631
type_override = None # type: mypy.types.Type
1632+
# If False, this name won't be imported via 'from <module> import *'.
1633+
# This has no effect on names within classes.
1634+
module_public = True
16321635

16331636
def __init__(self, kind: int, node: SymbolNode, mod_id: str = None,
1634-
typ: 'mypy.types.Type' = None, tvar_id: int = 0) -> None:
1637+
typ: 'mypy.types.Type' = None, tvar_id: int = 0,
1638+
module_public: bool = True) -> None:
16351639
self.kind = kind
16361640
self.node = node
16371641
self.type_override = typ
16381642
self.mod_id = mod_id
16391643
self.tvar_id = tvar_id
1644+
self.module_public = module_public
16401645

16411646
@property
16421647
def fullname(self) -> str:

mypy/parse.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ def parse_import(self) -> Import:
203203
id = self.parse_qualified_name()
204204
if id == self.custom_typing_module:
205205
id = 'typing'
206-
as_id = id
206+
as_id = None # type: Optional[str]
207207
if self.current_str() == 'as':
208208
self.expect('as')
209209
name_tok = self.expect_type(Name)
@@ -227,7 +227,7 @@ def parse_import_from(self) -> Node:
227227

228228
# Parse qualified name to actually import from.
229229
if self.current_str() == "import":
230-
# Empty/defualt values.
230+
# Empty/default values.
231231
name = ""
232232
else:
233233
name = self.parse_qualified_name()
@@ -274,15 +274,15 @@ def parse_import_from(self) -> Node:
274274
self.future_options.extend(target[0] for target in targets)
275275
return node
276276

277-
def parse_import_name(self) -> Tuple[str, str]:
277+
def parse_import_name(self) -> Tuple[str, Optional[str]]:
278278
tok = self.expect_type(Name)
279279
name = tok.string
280280
if self.current_str() == 'as':
281281
self.skip()
282282
as_name = self.expect_type(Name)
283283
return name, as_name.string
284284
else:
285-
return name, name
285+
return name, None
286286

287287
def parse_qualified_name(self) -> str:
288288
"""Parse a name with an optional module qualifier.

mypy/semanal.py

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ class SemanticAnalyzer(NodeVisitor):
161161

162162
loop_depth = 0 # Depth of breakable loops
163163
cur_mod_id = '' # Current module id (or None) (phase 2)
164+
is_stub_file = False # Are we analyzing a stub file?
164165
imports = None # type: Set[str] # Imported modules (during phase 2 analysis)
165166
errors = None # type: Errors # Keeps track of generated errors
166167

@@ -736,16 +737,22 @@ def bind_class_type_variables_in_symbol_table(
736737

737738
def visit_import(self, i: Import) -> None:
738739
for id, as_id in i.ids:
739-
if as_id != id:
740-
self.add_module_symbol(id, as_id, i)
740+
if as_id is not None:
741+
self.add_module_symbol(id, as_id, module_public=True, context=i)
741742
else:
743+
# Modules imported in a stub file without using 'as x' won't get exported when
744+
# doing 'from m import *'.
745+
module_public = not self.is_stub_file
742746
base = id.split('.')[0]
743-
self.add_module_symbol(base, base, i)
747+
self.add_module_symbol(base, base, module_public=module_public,
748+
context=i)
744749

745-
def add_module_symbol(self, id: str, as_id: str, context: Context) -> None:
750+
def add_module_symbol(self, id: str, as_id: str, module_public: bool,
751+
context: Context) -> None:
746752
if id in self.modules:
747753
m = self.modules[id]
748-
self.add_symbol(as_id, SymbolTableNode(MODULE_REF, m, self.cur_mod_id), context)
754+
self.add_symbol(as_id, SymbolTableNode(MODULE_REF, m, self.cur_mod_id,
755+
module_public=module_public), context)
749756
else:
750757
self.add_unknown_symbol(as_id, context)
751758

@@ -759,10 +766,13 @@ def visit_import_from(self, i: ImportFrom) -> None:
759766
node = self.normalize_type_alias(node, i)
760767
if not node:
761768
return
769+
module_public = not self.is_stub_file or as_id is not None
762770
symbol = SymbolTableNode(node.kind, node.node,
763771
self.cur_mod_id,
764-
node.type_override)
765-
self.add_symbol(as_id, symbol, i)
772+
node.type_override,
773+
module_public=module_public)
774+
imported_id = as_id or id
775+
self.add_symbol(imported_id, symbol, i)
766776
else:
767777
message = "Module has no attribute '{}'".format(id)
768778
extra = self.undefined_name_extra_info('{}.{}'.format(i_id, id))
@@ -771,7 +781,7 @@ def visit_import_from(self, i: ImportFrom) -> None:
771781
self.fail(message, i)
772782
else:
773783
for id, as_id in i.names:
774-
self.add_unknown_symbol(as_id, i)
784+
self.add_unknown_symbol(as_id or id, i)
775785

776786
def normalize_type_alias(self, node: SymbolTableNode,
777787
ctx: Context) -> SymbolTableNode:
@@ -803,7 +813,7 @@ def visit_import_all(self, i: ImportAll) -> None:
803813
m = self.modules[i_id]
804814
for name, node in m.names.items():
805815
node = self.normalize_type_alias(node, i)
806-
if not name.startswith('_'):
816+
if not name.startswith('_') and node.module_public:
807817
self.add_symbol(name, SymbolTableNode(node.kind, node.node,
808818
self.cur_mod_id), i)
809819
else:

mypy/strconv.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,13 +84,19 @@ def visit_mypy_file(self, o):
8484
def visit_import(self, o):
8585
a = []
8686
for id, as_id in o.ids:
87-
a.append('{} : {}'.format(id, as_id))
87+
if as_id is not None:
88+
a.append('{} : {}'.format(id, as_id))
89+
else:
90+
a.append(id)
8891
return 'Import:{}({})'.format(o.line, ', '.join(a))
8992

9093
def visit_import_from(self, o):
9194
a = []
9295
for name, as_name in o.names:
93-
a.append('{} : {}'.format(name, as_name))
96+
if as_name is not None:
97+
a.append('{} : {}'.format(name, as_name))
98+
else:
99+
a.append(name)
94100
return 'ImportFrom:{}({}, [{}])'.format(o.line, "." * o.relative + o.id, ', '.join(a))
95101

96102
def visit_import_all(self, o):

mypy/stubgen.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -335,21 +335,21 @@ def visit_import_from(self, o):
335335
if self._all_:
336336
# Include import froms that import names defined in __all__.
337337
names = [name for name, alias in o.names
338-
if name in self._all_ and name == alias]
338+
if name in self._all_ and alias is None]
339339
self.import_and_export_names(o.id, o.relative, names)
340340
else:
341341
# Include import from targets that import from a submodule of a package.
342342
if o.relative:
343343
names = [name for name, alias in o.names
344-
if name == alias]
344+
if alias is None]
345345
self.import_and_export_names(o.id, o.relative, names)
346346
# Import names used as base classes.
347347
names = [(name, alias) for name, alias in o.names
348-
if alias in self._base_classes]
348+
if alias or name in self._base_classes]
349349
if names:
350350
imp_names = []
351351
for name, alias in names:
352-
if alias != name:
352+
if alias is not None and alias != name:
353353
imp_names.append('%s as %s' % (name, alias))
354354
else:
355355
imp_names.append(name)

mypy/test/data/parse.test

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1026,8 +1026,8 @@ import x
10261026
import y.z.foo, __foo__.bar
10271027
[out]
10281028
MypyFile:1(
1029-
Import:1(x : x)
1030-
Import:2(y.z.foo : y.z.foo, __foo__.bar : __foo__.bar))
1029+
Import:1(x)
1030+
Import:2(y.z.foo, __foo__.bar))
10311031

10321032
[case testVariableTypeWithQualifiedName]
10331033
x = None # type: x.y
@@ -1053,16 +1053,16 @@ from m import x
10531053
from m.n import x, y, z
10541054
[out]
10551055
MypyFile:1(
1056-
ImportFrom:1(m, [x : x])
1057-
ImportFrom:2(m.n, [x : x, y : y, z : z]))
1056+
ImportFrom:1(m, [x])
1057+
ImportFrom:2(m.n, [x, y, z]))
10581058

10591059
[case testImportFromAs]
10601060
from m import x as y
1061-
from x import y, z as a, c
1061+
from x import y, z as a, c as c
10621062
[out]
10631063
MypyFile:1(
10641064
ImportFrom:1(m, [x : y])
1065-
ImportFrom:2(x, [y : y, z : a, c : c]))
1065+
ImportFrom:2(x, [y, z : a, c : c]))
10661066

10671067
[case testImportStar]
10681068
from x import *
@@ -1080,18 +1080,18 @@ def f():
10801080
MypyFile:1(
10811081
ExpressionStmt:1(
10821082
IntExpr(1))
1083-
Import:2(x : x)
1083+
Import:2(x)
10841084
FuncDef:3(
10851085
f
10861086
Block:3(
1087-
ImportFrom:4(x, [y : y])
1087+
ImportFrom:4(x, [y])
10881088
ImportAll:5(z))))
10891089

10901090
[case testImportWithExtraComma]
10911091
from x import (y, z,)
10921092
[out]
10931093
MypyFile:1(
1094-
ImportFrom:1(x, [y : y, z : z]))
1094+
ImportFrom:1(x, [y, z]))
10951095

10961096
[case testDefaultArgs]
10971097
def f(x=1):
@@ -1700,8 +1700,8 @@ from x import (y,
17001700
z)
17011701
[out]
17021702
MypyFile:1(
1703-
ImportFrom:1(x, [y : y])
1704-
ImportFrom:2(x, [y : y, z : z]))
1703+
ImportFrom:1(x, [y])
1704+
ImportFrom:2(x, [y, z]))
17051705

17061706
[case testContinueStmt]
17071707
while 1:
@@ -2142,11 +2142,11 @@ MypyFile:1(
21422142

21432143
[case testImportAs]
21442144
import x as y
2145-
import x, z as y, a.b as c
2145+
import x, z as y, a.b as c, d as d
21462146
[out]
21472147
MypyFile:1(
21482148
Import:1(x : y)
2149-
Import:2(x : x, z : y, a.b : c))
2149+
Import:2(x, z : y, a.b : c, d : d))
21502150

21512151
[case testForAndElse]
21522152
for x in y:
@@ -3171,13 +3171,13 @@ MypyFile:1(
31713171
from ... import x
31723172
[out]
31733173
MypyFile:1(
3174-
ImportFrom:1(..., [x : x]))
3174+
ImportFrom:1(..., [x]))
31753175

31763176
[case testRelativeImportWithEllipsis2]
31773177
from .... import x
31783178
[out]
31793179
MypyFile:1(
3180-
ImportFrom:1(...., [x : x]))
3180+
ImportFrom:1(...., [x]))
31813181

31823182
[case testParseExtendedSlicing]
31833183
a[:, :]
@@ -3242,7 +3242,7 @@ MypyFile:1(
32423242
import x # type: ignore
32433243
[out]
32443244
MypyFile:1(
3245-
Import:1(x : x)
3245+
Import:1(x)
32463246
IgnoredLines(1))
32473247

32483248
[case testIgnore2Lines]
@@ -3317,7 +3317,7 @@ from m import ( # type: ignore
33173317
)
33183318
[out]
33193319
MypyFile:1(
3320-
ImportFrom:1(m, [x : x, y : y])
3320+
ImportFrom:1(m, [x, y])
33213321
IgnoredLines(1))
33223322

33233323
[case testYieldExpression]

mypy/test/data/semanal-abstractclasses.test

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ class A(metaclass=ABCMeta):
99
def f(self) -> 'A': return self
1010
[out]
1111
MypyFile:1(
12-
ImportFrom:1(abc, [abstractmethod : abstractmethod, ABCMeta : ABCMeta])
13-
Import:2(typing : typing)
12+
ImportFrom:1(abc, [abstractmethod, ABCMeta])
13+
Import:2(typing)
1414
ClassDef:4(
1515
A
1616
Metaclass(ABCMeta)
@@ -45,8 +45,8 @@ class B(metaclass=ABCMeta): pass
4545
class C(A, B): pass
4646
[out]
4747
MypyFile:1(
48-
ImportFrom:1(abc, [abstractmethod : abstractmethod, ABCMeta : ABCMeta])
49-
Import:2(typing : typing)
48+
ImportFrom:1(abc, [abstractmethod, ABCMeta])
49+
Import:2(typing)
5050
ClassDef:4(
5151
A
5252
Metaclass(ABCMeta)
@@ -71,8 +71,8 @@ class A(Generic[T]):
7171
def f(self) -> 'A[T]': pass
7272
[out]
7373
MypyFile:1(
74-
ImportFrom:1(abc, [abstractmethod : abstractmethod])
75-
ImportFrom:2(typing, [Generic : Generic, TypeVar : TypeVar])
74+
ImportFrom:1(abc, [abstractmethod])
75+
ImportFrom:2(typing, [Generic, TypeVar])
7676
AssignmentStmt:3(
7777
NameExpr(T* [__main__.T])
7878
TypeVarExpr:3())
@@ -101,9 +101,9 @@ class A(metaclass=ABCMeta):
101101
def g(self) -> 'A': pass
102102
[out]
103103
MypyFile:1(
104-
Import:1(abc : abc)
105-
ImportFrom:2(abc, [ABCMeta : ABCMeta])
106-
Import:3(typing : typing)
104+
Import:1(abc)
105+
ImportFrom:2(abc, [ABCMeta])
106+
Import:3(typing)
107107
ClassDef:5(
108108
A
109109
Metaclass(ABCMeta)

0 commit comments

Comments
 (0)