def generate_html_report(tree: MypyFile, path: str, type_map: Dict[Expression, Type], output_dir: str) -> None: if is_special_module(path): return # There may be more than one right answer for "what should we do here?" # but this is a reasonable one. path = os.path.relpath(path) if path.startswith('..'): return visitor = StatisticsVisitor(inferred=True, filename=tree.fullname(), typemap=type_map, all_nodes=True) tree.accept(visitor) assert not os.path.isabs(path) and not path.startswith('..') # This line is *wrong* if the preceding assert fails. target_path = os.path.join(output_dir, 'html', path) # replace .py or .pyi with .html target_path = os.path.splitext(target_path)[0] + '.html' assert target_path.endswith('.html') ensure_dir_exists(os.path.dirname(target_path)) output = [] # type: List[str] append = output.append append('''\ <html> <head> <style> .red { background-color: #faa; } .yellow { background-color: #ffa; } .white { } .lineno { color: #999; } </style> </head> <body> <pre>''') num_imprecise_lines = 0 num_lines = 0 with open(path) as input_file: for i, line in enumerate(input_file): lineno = i + 1 status = visitor.line_map.get(lineno, TYPE_PRECISE) style_map = { TYPE_PRECISE: 'white', TYPE_IMPRECISE: 'yellow', TYPE_ANY: 'red' } style = style_map[status] append('<span class="lineno">%4d</span> ' % lineno + '<span class="%s">%s</span>' % (style, cgi.escape(line))) if status != TYPE_PRECISE: num_imprecise_lines += 1 if line.strip(): num_lines += 1 append('</pre>') append('</body></html>') with open(target_path, 'w') as output_file: output_file.writelines(output) target_path = target_path[len(output_dir) + 1:] html_files.append((path, target_path, num_lines, num_imprecise_lines))
def on_file(self, tree: MypyFile, type_map: Dict[Expression, Type]) -> None: self.last_xml = None path = os.path.relpath(tree.path) if stats.is_special_module(path): return if path.startswith('..'): return if 'stubs' in path.split('/'): return visitor = stats.StatisticsVisitor(inferred=True, typemap=type_map, all_nodes=True) tree.accept(visitor) root = etree.Element('mypy-report-file', name=path, module=tree._fullname) doc = etree.ElementTree(root) file_info = FileInfo(path, tree._fullname) with tokenize.open(path) as input_file: for lineno, line_text in enumerate(input_file, 1): status = visitor.line_map.get(lineno, stats.TYPE_EMPTY) file_info.counts[status] += 1 etree.SubElement(root, 'line', number=str(lineno), precision=stats.precision_names[status], content=line_text[:-1]) # Assumes a layout similar to what XmlReporter uses. xslt_path = os.path.relpath('mypy-html.xslt', path) transform_pi = etree.ProcessingInstruction('xml-stylesheet', 'type="text/xsl" href="%s"' % cgi.escape(xslt_path, True)) root.addprevious(transform_pi) self.schema.assertValid(doc) self.last_xml = doc self.files.append(file_info)
def strip_file_top_level(self, file_node: MypyFile) -> None: """Strip a module top-level (don't recursive into functions).""" self.names = file_node.names self.file_node = file_node self.recurse_into_functions = False file_node.plugin_deps.clear() file_node.accept(self)
def get_dependencies(target: MypyFile, type_map: Dict[Expression, Type], python_version: Tuple[int, int]) -> Dict[str, Set[str]]: """Get all dependencies of a node, recursively.""" visitor = DependencyVisitor(type_map, python_version, target.alias_deps) target.accept(visitor) return visitor.map
def get_dependencies(target: MypyFile, type_map: Dict[Expression, Type], python_version: Tuple[int, int]) -> Dict[str, Set[str]]: """Get all dependencies of a node, recursively.""" visitor = DependencyVisitor(type_map, python_version) target.accept(visitor) return visitor.map
def dump_type_stats(tree: MypyFile, path: str, modules: Dict[str, MypyFile], inferred: bool = False, typemap: Optional[Dict[Expression, Type]] = None) -> None: if is_special_module(path): return print(path) visitor = StatisticsVisitor(inferred, filename=tree.fullname(), modules=modules, typemap=typemap) tree.accept(visitor) for line in visitor.output: print(line) print(' ** precision **') print(' precise ', visitor.num_precise_exprs) print(' imprecise', visitor.num_imprecise_exprs) print(' any ', visitor.num_any_exprs) print(' ** kinds **') print(' simple ', visitor.num_simple_types) print(' generic ', visitor.num_generic_types) print(' function ', visitor.num_function_types) print(' tuple ', visitor.num_tuple_types) print(' TypeVar ', visitor.num_typevar_types) print(' complex ', visitor.num_complex_types) print(' any ', visitor.num_any_types)
def on_file(self, tree: MypyFile, type_map: Dict[Expression, Type], options: Options) -> None: path = os.path.relpath(tree.path) visitor = stats.StatisticsVisitor(inferred=True, filename=tree.fullname(), typemap=type_map, all_nodes=True) tree.accept(visitor) class_name = os.path.basename(path) file_info = FileInfo(path, tree._fullname) class_element = etree.Element('class', filename=path, complexity='1.0', name=class_name) etree.SubElement(class_element, 'methods') lines_element = etree.SubElement(class_element, 'lines') with tokenize.open(path) as input_file: class_lines_covered = 0 class_total_lines = 0 for lineno, _ in enumerate(input_file, 1): status = visitor.line_map.get(lineno, stats.TYPE_EMPTY) hits = 0 branch = False if status == stats.TYPE_EMPTY: continue class_total_lines += 1 if status != stats.TYPE_ANY: class_lines_covered += 1 hits = 1 if status == stats.TYPE_IMPRECISE: branch = True file_info.counts[status] += 1 line_element = etree.SubElement(lines_element, 'line', number=str(lineno), precision=stats.precision_names[status], hits=str(hits), branch=str(branch).lower()) if branch: line_element.attrib['condition-coverage'] = '50% (1/2)' class_element.attrib['branch-rate'] = '0' class_element.attrib['line-rate'] = get_line_rate(class_lines_covered, class_total_lines) # parent_module is set to whichever module contains this file. For most files, we want # to simply strip the last element off of the module. But for __init__.py files, # the module == the parent module. parent_module = file_info.module.rsplit('.', 1)[0] if file_info.name.endswith('__init__.py'): parent_module = file_info.module if parent_module not in self.root_package.packages: self.root_package.packages[parent_module] = CoberturaPackage(parent_module) current_package = self.root_package.packages[parent_module] packages_to_update = [self.root_package, current_package] for package in packages_to_update: package.total_lines += class_total_lines package.covered_lines += class_lines_covered current_package.classes[class_name] = class_element
def find_classes(node: MypyFile) -> Set[str]: results = set() # type: Set[str] class ClassTraverser(mypy.traverser.TraverserVisitor): def visit_class_def(self, o: ClassDef) -> None: results.add(o.name) node.accept(ClassTraverser()) return results
def strip_file_top_level(self, file_node: MypyFile) -> None: """Strip a module top-level (don't recursive into functions).""" self.recurse_into_functions = False file_node.plugin_deps.clear() file_node.accept(self) for name in file_node.names.copy(): # TODO: this is a hot fix, we should delete all names, # see https://github.com/python/mypy/issues/6422. if '@' not in name: del file_node.names[name]
def on_file(self, tree: MypyFile, type_map: Dict[Expression, Type], options: Options) -> None: visitor = stats.StatisticsVisitor(inferred=True, filename=tree.fullname(), typemap=type_map, all_nodes=True) tree.accept(visitor) num_total = visitor.num_imprecise + visitor.num_precise + visitor.num_any if num_total > 0: self.counts[tree.fullname()] = (visitor.num_any, num_total)
def generate_html_report(tree: MypyFile, path: str, type_map: Dict[Expression, Type], output_dir: str) -> None: if is_special_module(path): return # There may be more than one right answer for "what should we do here?" # but this is a reasonable one. path = os.path.relpath(path) if path.startswith('..'): return visitor = StatisticsVisitor(inferred=True, typemap=type_map, all_nodes=True) tree.accept(visitor) assert not os.path.isabs(path) and not path.startswith('..') # This line is *wrong* if the preceding assert fails. target_path = os.path.join(output_dir, 'html', path) # replace .py or .pyi with .html target_path = os.path.splitext(target_path)[0] + '.html' assert target_path.endswith('.html') ensure_dir_exists(os.path.dirname(target_path)) output = [] # type: List[str] append = output.append append('''\ <html> <head> <style> .red { background-color: #faa; } .yellow { background-color: #ffa; } .white { } .lineno { color: #999; } </style> </head> <body> <pre>''') num_imprecise_lines = 0 num_lines = 0 with open(path) as input_file: for i, line in enumerate(input_file): lineno = i + 1 status = visitor.line_map.get(lineno, TYPE_PRECISE) style_map = {TYPE_PRECISE: 'white', TYPE_IMPRECISE: 'yellow', TYPE_ANY: 'red'} style = style_map[status] append('<span class="lineno">%4d</span> ' % lineno + '<span class="%s">%s</span>' % (style, cgi.escape(line))) if status != TYPE_PRECISE: num_imprecise_lines += 1 if line.strip(): num_lines += 1 append('</pre>') append('</body></html>') with open(target_path, 'w') as output_file: output_file.writelines(output) target_path = target_path[len(output_dir) + 1:] html_files.append((path, target_path, num_lines, num_imprecise_lines))
def on_file(self, tree: MypyFile, type_map: Dict[Node, Type]) -> None: tree_source = open(tree.path).readlines() coverage_visitor = LineCoverageVisitor(tree_source) tree.accept(coverage_visitor) covered_lines = [] for line_number, (_, typed) in enumerate(coverage_visitor.lines_covered): if typed: covered_lines.append(line_number + 1) self.lines_covered[os.path.abspath(tree.path)] = covered_lines
def on_file(self, tree: MypyFile, type_map: Dict[Node, Type]) -> None: physical_lines = len(open(tree.path).readlines()) func_counter = FuncCounterVisitor() tree.accept(func_counter) unannotated_funcs, annotated_funcs = func_counter.counts total_funcs = annotated_funcs + unannotated_funcs imputed_annotated_lines = (physical_lines * annotated_funcs // total_funcs if total_funcs else physical_lines) self.counts[tree._fullname] = (imputed_annotated_lines, physical_lines, annotated_funcs, total_funcs)
def anal_code(self, code: MypyFile) -> Dict[str, List[str]]: logger.debug(f"code.fullname = {code.fullname!r}, self.cache = {self.cache!r}") if code.fullname not in self.cache: try: visitor = RelationshipVisitor() except TypeError: # pragma: no cover # Only supports versions that are bigger than 0.820 return {} code.accept(visitor) self.cache[code.fullname] = visitor.relationships return self.cache[code.fullname]
def on_file(self, tree: MypyFile, type_map: Dict[Node, Type]) -> None: # Count physical lines. This assumes the file's encoding is a # superset of ASCII (or at least uses \n in its line endings). physical_lines = len(open(tree.path, "rb").readlines()) func_counter = FuncCounterVisitor() tree.accept(func_counter) unannotated_funcs, annotated_funcs = func_counter.counts total_funcs = annotated_funcs + unannotated_funcs imputed_annotated_lines = physical_lines * annotated_funcs // total_funcs if total_funcs else physical_lines self.counts[tree._fullname] = (imputed_annotated_lines, physical_lines, annotated_funcs, total_funcs)
def on_file(self, tree: MypyFile, type_map: Dict[Expression, Type], options: Options) -> None: visitor = stats.StatisticsVisitor(inferred=True, filename=tree.fullname(), typemap=type_map, all_nodes=True) tree.accept(visitor) num_unanalyzed_lines = list(visitor.line_map.values()).count( stats.TYPE_UNANALYZED) # count each line of dead code as one expression of type "Any" num_any = visitor.num_any + num_unanalyzed_lines num_total = visitor.num_imprecise + visitor.num_precise + num_any if num_total > 0: self.counts[tree.fullname()] = (num_any, num_total)
def on_file(self, tree: MypyFile, type_map: Dict[Expression, Type], options: Options) -> None: visitor = stats.StatisticsVisitor(inferred=True, filename=tree.fullname(), typemap=type_map, all_nodes=True, visit_untyped_defs=False) tree.accept(visitor) self.any_types_counter[tree.fullname()] = visitor.type_of_any_counter num_unanalyzed_lines = list(visitor.line_map.values()).count(stats.TYPE_UNANALYZED) # count each line of dead code as one expression of type "Any" num_any = visitor.num_any_exprs + num_unanalyzed_lines num_total = visitor.num_imprecise_exprs + visitor.num_precise_exprs + num_any if num_total > 0: self.counts[tree.fullname()] = (num_any, num_total)
def on_file(self, tree: MypyFile, type_map: Dict[Expression, Type], options: Options) -> None: with open(tree.path) as f: tree_source = f.readlines() coverage_visitor = LineCoverageVisitor(tree_source) tree.accept(coverage_visitor) covered_lines = [] for line_number, (_, typed) in enumerate(coverage_visitor.lines_covered): if typed: covered_lines.append(line_number + 1) self.lines_covered[os.path.abspath(tree.path)] = covered_lines
def on_file(self, tree: MypyFile, type_map: Dict[Node, Type]) -> None: # Count physical lines. This assumes the file's encoding is a # superset of ASCII (or at least uses \n in its line endings). physical_lines = len(open(tree.path, 'rb').readlines()) func_counter = FuncCounterVisitor() tree.accept(func_counter) unannotated_funcs, annotated_funcs = func_counter.counts total_funcs = annotated_funcs + unannotated_funcs imputed_annotated_lines = (physical_lines * annotated_funcs // total_funcs if total_funcs else physical_lines) self.counts[tree._fullname] = (imputed_annotated_lines, physical_lines, annotated_funcs, total_funcs)
def on_file(self, tree: MypyFile, modules: Dict[str, MypyFile], type_map: Dict[Expression, Type], options: Options) -> None: visitor = stats.StatisticsVisitor(inferred=True, filename=tree.fullname, modules=modules, typemap=type_map, all_nodes=True, visit_untyped_defs=False) tree.accept(visitor) self.any_types_counter[tree.fullname] = visitor.type_of_any_counter num_unanalyzed_lines = list(visitor.line_map.values()).count( stats.TYPE_UNANALYZED) # count each line of dead code as one expression of type "Any" num_any = visitor.num_any_exprs + num_unanalyzed_lines num_total = visitor.num_imprecise_exprs + visitor.num_precise_exprs + num_any if num_total > 0: self.counts[tree.fullname] = (num_any, num_total)
def on_file(self, tree: MypyFile, modules: Dict[str, MypyFile], type_map: Dict[Expression, Type], options: Options) -> None: self.last_xml = None try: path = os.path.relpath(tree.path) except ValueError: return if should_skip_path(path): return visitor = stats.StatisticsVisitor(inferred=True, filename=tree.fullname, modules=modules, typemap=type_map, all_nodes=True) tree.accept(visitor) root = etree.Element('mypy-report-file', name=path, module=tree._fullname) doc = etree.ElementTree(root) file_info = FileInfo(path, tree._fullname) for lineno, line_text in iterate_python_lines(path): status = visitor.line_map.get(lineno, stats.TYPE_EMPTY) file_info.counts[status] += 1 etree.SubElement( root, 'line', any_info=self._get_any_info_for_line(visitor, lineno), content=line_text.rstrip('\n').translate(self.control_fixer), number=str(lineno), precision=stats.precision_names[status]) # Assumes a layout similar to what XmlReporter uses. xslt_path = os.path.relpath('mypy-html.xslt', path) transform_pi = etree.ProcessingInstruction( 'xml-stylesheet', 'type="text/xsl" href="%s"' % pathname2url(xslt_path)) root.addprevious(transform_pi) self.schema.assertValid(doc) self.last_xml = doc self.files.append(file_info)
def on_file(self, tree: MypyFile, type_map: Dict[Expression, Type], options: Options) -> None: self.last_xml = None path = os.path.relpath(tree.path) if stats.is_special_module(path): return if path.startswith('..'): return if 'stubs' in path.split('/'): return visitor = stats.StatisticsVisitor(inferred=True, filename=tree.fullname(), typemap=type_map, all_nodes=True) tree.accept(visitor) root = etree.Element('mypy-report-file', name=path, module=tree._fullname) doc = etree.ElementTree(root) file_info = FileInfo(path, tree._fullname) with tokenize.open(path) as input_file: for lineno, line_text in enumerate(input_file, 1): status = visitor.line_map.get(lineno, stats.TYPE_EMPTY) file_info.counts[status] += 1 etree.SubElement(root, 'line', number=str(lineno), precision=stats.precision_names[status], content=line_text.rstrip('\n').translate( self.control_fixer), any_info=self._get_any_info_for_line( visitor, lineno)) # Assumes a layout similar to what XmlReporter uses. xslt_path = os.path.relpath('mypy-html.xslt', path) transform_pi = etree.ProcessingInstruction( 'xml-stylesheet', 'type="text/xsl" href="%s"' % pathname2url(xslt_path)) root.addprevious(transform_pi) self.schema.assertValid(doc) self.last_xml = doc self.files.append(file_info)
def on_file(self, tree: MypyFile, type_map: Dict[Node, Type]) -> None: import lxml.etree as etree self.last_xml = None path = os.path.relpath(tree.path) if stats.is_special_module(path): return if path.startswith('..'): return if 'stubs' in path.split('/'): return visitor = stats.StatisticsVisitor(inferred=True, typemap=type_map, all_nodes=True) tree.accept(visitor) root = etree.Element('mypy-report-file', name=path, module=tree._fullname) doc = etree.ElementTree(root) file_info = FileInfo(path, tree._fullname) with open(path) as input_file: for lineno, line_text in enumerate(input_file, 1): status = visitor.line_map.get(lineno, stats.TYPE_EMPTY) file_info.counts[status] += 1 etree.SubElement(root, 'line', number=str(lineno), precision=stats.precision_names[status], content=line_text[:-1]) # Assumes a layout similar to what XmlReporter uses. xslt_path = os.path.relpath('mypy-html.xslt', path) xml_pi = etree.ProcessingInstruction('xml', 'version="1.0" encoding="utf-8"') transform_pi = etree.ProcessingInstruction( 'xml-stylesheet', 'type="text/xsl" href="%s"' % cgi.escape(xslt_path, True)) root.addprevious(xml_pi) root.addprevious(transform_pi) self.schema.assertValid(doc) self.last_xml = doc self.files.append(file_info)
def on_file(self, tree: MypyFile, modules: Dict[str, MypyFile], type_map: Dict[Expression, Type], options: Options) -> None: path = os.path.relpath(tree.path) if should_skip_path(path): return visitor = stats.StatisticsVisitor(inferred=True, filename=tree.fullname(), modules=modules, typemap=type_map, all_nodes=True) tree.accept(visitor) file_info = FileInfo(path, tree._fullname) for lineno, _ in iterate_python_lines(path): status = visitor.line_map.get(lineno, stats.TYPE_EMPTY) file_info.counts[status] += 1 self.files.append(file_info)
def dump_type_stats(tree: MypyFile, path: str, inferred: bool = False, typemap: Dict[Node, Type] = None) -> None: if is_special_module(path): return print(path) visitor = StatisticsVisitor(inferred, typemap) tree.accept(visitor) for line in visitor.output: print(line) print(' ** precision **') print(' precise ', visitor.num_precise) print(' imprecise', visitor.num_imprecise) print(' any ', visitor.num_any) print(' ** kinds **') print(' simple ', visitor.num_simple) print(' generic ', visitor.num_generic) print(' function ', visitor.num_function) print(' tuple ', visitor.num_tuple) print(' TypeVar ', visitor.num_typevar) print(' complex ', visitor.num_complex) print(' any ', visitor.num_any)
def dump_type_stats(tree: MypyFile, path: str, inferred: bool = False, typemap: Optional[Dict[Expression, Type]] = None) -> None: if is_special_module(path): return print(path) visitor = StatisticsVisitor(inferred, filename=tree.fullname(), typemap=typemap) tree.accept(visitor) for line in visitor.output: print(line) print(' ** precision **') print(' precise ', visitor.num_precise_exprs) print(' imprecise', visitor.num_imprecise_exprs) print(' any ', visitor.num_any_exprs) print(' ** kinds **') print(' simple ', visitor.num_simple_types) print(' generic ', visitor.num_generic_types) print(' function ', visitor.num_function_types) print(' tuple ', visitor.num_tuple_types) print(' TypeVar ', visitor.num_typevar_types) print(' complex ', visitor.num_complex_types) print(' any ', visitor.num_any_types)
def on_file(self, tree: MypyFile, type_map: Dict[Expression, Type], options: Options) -> None: # Count physical lines. This assumes the file's encoding is a # superset of ASCII (or at least uses \n in its line endings). with open(tree.path, 'rb') as f: physical_lines = len(f.readlines()) func_counter = FuncCounterVisitor() tree.accept(func_counter) unannotated_funcs, annotated_funcs = func_counter.counts total_funcs = annotated_funcs + unannotated_funcs # Don't count lines or functions as annotated if they have their errors ignored. if options.ignore_errors: annotated_funcs = 0 imputed_annotated_lines = (physical_lines * annotated_funcs // total_funcs if total_funcs else physical_lines) self.counts[tree._fullname] = (imputed_annotated_lines, physical_lines, annotated_funcs, total_funcs)
def build_ir(module: MypyFile, types: Dict[Expression, Type]) -> ModuleIR: mapper = Mapper() builder = IRBuilder(types, mapper) module.accept(builder) return ModuleIR(builder.imports, builder.functions, builder.classes)
def visit_file(self, file: MypyFile, fnam: str, mod_id: str, options: Options) -> None: """Perform the first analysis pass. Populate module global table. Resolve the full names of definitions not nested within functions and construct type info structures, but do not resolve inter-definition references such as base classes. Also add implicit definitions such as __name__. In this phase we don't resolve imports. For 'from ... import', we generate dummy symbol table nodes for the imported names, and these will get resolved in later phases of semantic analysis. """ if options.allow_redefinition: # Perform renaming across the AST to allow variable redefinitions file.accept(VariableRenameVisitor()) sem = self.sem self.sem.options = options # Needed because we sometimes call into it self.pyversion = options.python_version self.platform = options.platform sem.cur_mod_id = mod_id sem.cur_mod_node = file sem.errors.set_file(fnam, mod_id, scope=sem.scope) sem.globals = SymbolTable() sem.global_decls = [set()] sem.nonlocal_decls = [set()] sem.block_depth = [0] sem.scope.enter_file(mod_id) defs = file.defs with state.strict_optional_set(options.strict_optional): # Add implicit definitions of module '__name__' etc. for name, t in implicit_module_attrs.items(): # unicode docstrings should be accepted in Python 2 if name == '__doc__': if self.pyversion >= (3, 0): typ = UnboundType('__builtins__.str') # type: Type else: typ = UnionType([UnboundType('__builtins__.str'), UnboundType('__builtins__.unicode')]) else: assert t is not None, 'type should be specified for {}'.format(name) typ = UnboundType(t) v = Var(name, typ) v._fullname = self.sem.qualified_name(name) self.sem.globals[name] = SymbolTableNode(GDEF, v) for i, d in enumerate(defs): d.accept(self) if isinstance(d, AssertStmt) and assert_will_always_fail(d, options): # We've encountered an assert that's always false, # e.g. assert sys.platform == 'lol'. Truncate the # list of statements. This mutates file.defs too. del defs[i + 1:] break # Add implicit definition of literals/keywords to builtins, as we # cannot define a variable with them explicitly. if mod_id == 'builtins': literal_types = [ ('None', NoneTyp()), # reveal_type is a mypy-only function that gives an error with # the type of its arg. ('reveal_type', AnyType(TypeOfAny.special_form)), # reveal_locals is a mypy-only function that gives an error with the types of # locals ('reveal_locals', AnyType(TypeOfAny.special_form)), ] # type: List[Tuple[str, Type]] # TODO(ddfisher): This guard is only needed because mypy defines # fake builtins for its tests which often don't define bool. If # mypy is fast enough that we no longer need those, this # conditional check should be removed. if 'bool' in self.sem.globals: bool_type = self.sem.named_type('bool') literal_types.extend([ ('True', bool_type), ('False', bool_type), ('__debug__', bool_type), ]) else: # We are running tests without 'bool' in builtins. # TODO: Find a permanent solution to this problem. # Maybe add 'bool' to all fixtures? literal_types.append(('True', AnyType(TypeOfAny.special_form))) for name, typ in literal_types: v = Var(name, typ) v._fullname = self.sem.qualified_name(name) self.sem.globals[name] = SymbolTableNode(GDEF, v) del self.sem.options sem.scope.leave()
def mypyfile(self, node: MypyFile) -> MypyFile: new = node.accept(self) assert isinstance(new, MypyFile) new.set_line(node.line) return new
def visit_file(self, file: MypyFile, fnam: str, mod_id: str, options: Options) -> None: """Perform the first analysis pass. Populate module global table. Resolve the full names of definitions not nested within functions and construct type info structures, but do not resolve inter-definition references such as base classes. Also add implicit definitions such as __name__. In this phase we don't resolve imports. For 'from ... import', we generate dummy symbol table nodes for the imported names, and these will get resolved in later phases of semantic analysis. """ if options.allow_redefinition: # Perform renaming across the AST to allow variable redefinitions file.accept(VariableRenameVisitor()) sem = self.sem self.sem.options = options # Needed because we sometimes call into it self.pyversion = options.python_version self.platform = options.platform sem.cur_mod_id = mod_id sem.cur_mod_node = file sem.errors.set_file(fnam, mod_id, scope=sem.scope) sem.globals = SymbolTable() sem.global_decls = [set()] sem.nonlocal_decls = [set()] sem.block_depth = [0] sem.scope.enter_file(mod_id) defs = file.defs with state.strict_optional_set(options.strict_optional): # Add implicit definitions of module '__name__' etc. for name, t in implicit_module_attrs.items(): # unicode docstrings should be accepted in Python 2 if name == '__doc__': if self.pyversion >= (3, 0): typ = UnboundType('__builtins__.str') # type: Type else: typ = UnionType([ UnboundType('__builtins__.str'), UnboundType('__builtins__.unicode') ]) else: assert t is not None, 'type should be specified for {}'.format( name) typ = UnboundType(t) v = Var(name, typ) v._fullname = self.sem.qualified_name(name) self.sem.globals[name] = SymbolTableNode(GDEF, v) for i, d in enumerate(defs): d.accept(self) if isinstance(d, AssertStmt) and assert_will_always_fail( d, options): # We've encountered an assert that's always false, # e.g. assert sys.platform == 'lol'. Truncate the # list of statements. This mutates file.defs too. del defs[i + 1:] break # Add implicit definition of literals/keywords to builtins, as we # cannot define a variable with them explicitly. if mod_id == 'builtins': literal_types = [ ('None', NoneTyp()), # reveal_type is a mypy-only function that gives an error with # the type of its arg. ('reveal_type', AnyType(TypeOfAny.special_form)), # reveal_locals is a mypy-only function that gives an error with the types of # locals ('reveal_locals', AnyType(TypeOfAny.special_form)), ] # type: List[Tuple[str, Type]] # TODO(ddfisher): This guard is only needed because mypy defines # fake builtins for its tests which often don't define bool. If # mypy is fast enough that we no longer need those, this # conditional check should be removed. if 'bool' in self.sem.globals: bool_type = self.sem.named_type('bool') literal_types.extend([ ('True', bool_type), ('False', bool_type), ('__debug__', bool_type), ]) else: # We are running tests without 'bool' in builtins. # TODO: Find a permanent solution to this problem. # Maybe add 'bool' to all fixtures? literal_types.append( ('True', AnyType(TypeOfAny.special_form))) for name, typ in literal_types: v = Var(name, typ) v._fullname = self.sem.qualified_name(name) self.sem.globals[name] = SymbolTableNode(GDEF, v) del self.sem.options sem.scope.leave()