def filter_types(cls, values_type) -> Set[IType]: if values_type is None: values_type = set() elif not isinstance(values_type, set): if isinstance(values_type, Iterable): values_type = set(values_type) else: values_type = {values_type} if len(values_type) > 1: from boa3.model.type.type import Type if any(t is Type.any or t is Type.none for t in values_type): return {Type.any} actual_types = list(values_type)[:1] for value in list(values_type)[1:]: other = next((x for x in actual_types if x.is_type_of(value) or value.is_type_of(x)), None) if other is not None and value.is_type_of(other): actual_types.remove(other) other = None if other is None: actual_types.append(value) values_type = set(actual_types) if any(not isinstance(x, ICollectionType) for x in values_type): return values_type if all(isinstance(x, PrimitiveType) for x in values_type): return values_type # verifies if all the types are the same collection with different arguments if not all(isinstance(x, ICollectionType) for x in values_type): return {Type.get_generic_type(*values_type)} else: first_item: ICollectionType = list(values_type)[0] collection_type = type(first_item) # first sequence type value_type = type( first_item.item_type) # first sequence values type if all(isinstance(x, collection_type) for x in values_type): # if all the types are the same sequence type, build this sequence with any as parameters types = set(value.item_type for value in values_type) values_type = {collection_type.build(types)} else: generic_type: IType = Type.get_generic_type(*values_type) if (isinstance(generic_type, ICollectionType) and all( isinstance(x.item_type, value_type) for x in values_type)): # the collections doesn't have the same type but the value type is the same # for example: Tuple[int] and List[int] values_type = { generic_type.build_collection(first_item.item_type) } else: # otherwise, built a generic sequence with any as parameters values_type = {generic_type} return values_type
def __init__(self, import_target: str): self.can_be_imported: bool = False self._import_identifier: str = import_target try: module_origin: str = importlib.util.find_spec(import_target).origin except BaseException: return path: List[str] = module_origin.split(os.sep) self.path: str = module_origin.replace(os.sep, '/') super().__init__(ast.Module(body=[]), path[-1]) if import_target == 'typing': self.symbols.update( {symbol_id: symbol for symbol_id, symbol in self._get_types_from_typing_lib().items() if symbol_id not in Type.builtin_types() }) self.can_be_imported = True else: import re inside_python_folder = any(re.search(r'python(\d\.?)*', folder.lower()) for folder in path) updated_tree = None if 'boa3' in path and '/'.join(path[path.index('boa3'):]).startswith('boa3/builtin'): pkg_start_index = path.index('builtin') + 1 if path[pkg_start_index] == path[-1]: self.symbols = self._get_boa3_builtin_symbols() else: pkg = import_target.split('.') pkg = pkg[pkg.index('builtin') + 1:] self.symbols = self._get_boa3_builtin_package(pkg) self.can_be_imported = True elif not (inside_python_folder and 'lib' in path): # TODO: only user modules and typing lib imports are implemented try: from boa3.analyser.analyser import Analyser analyser = Analyser.analyse(module_origin) # include only imported symbols if analyser.is_analysed: self.symbols.update( {symbol_id: symbol for symbol_id, symbol in analyser.symbol_table.items() if symbol_id not in Type.all_types() }) updated_tree = analyser.ast_tree self.can_be_imported = analyser.is_analysed except FileNotFoundError: self.can_be_imported = False if updated_tree is not None: self._tree = updated_tree
def _get_types_from_typing_lib(self) -> Dict[str, ISymbol]: import typing from types import FunctionType type_symbols: Dict[str, ISymbol] = {} all_types: List[str] = typing.__all__ for t_id in all_types: attr = getattr(typing, t_id) if not isinstance(attr, FunctionType): type_id: str = t_id if t_id in Type.all_types() else t_id.lower() if type_id in Type.all_types(): type_symbols[t_id] = Type.all_types()[type_id] return type_symbols
def get_type(self, value: Any) -> IType: """ Returns the type of the given value. :param value: value to get the type :return: Returns the :class:`IType` of the the type of the value. `Type.none` by default. """ # visits if it is a node if isinstance(value, ast.AST): fun_rtype_id: Any = ast.NodeVisitor.visit(self, value) if isinstance(fun_rtype_id, ast.Name): fun_rtype_id = fun_rtype_id.id if isinstance(fun_rtype_id, str) and not isinstance(value, ast.Str): value = self.get_symbol(fun_rtype_id) else: value = fun_rtype_id if (isinstance(value, Attribute) and isinstance(value.attr_symbol, IExpression) and isinstance(value.attr_symbol.type, ClassType)): value = value.attr_symbol if isinstance(value, IType): return value elif isinstance(value, IExpression): return value.type elif isinstance(value, IOperation): return value.result else: return Type.get_type(value)
def filter_types(cls, values_type) -> Set[IType]: if values_type is None: values_type = set() elif not isinstance(values_type, set): if isinstance(values_type, Iterable): values_type = set(values_type) else: values_type = {values_type} if len(values_type) > 1 and all(isinstance(x, MappingType) for x in values_type): first_item: MappingType = list(values_type)[0] mapping_type = type(first_item) # first mapping type k_types = set(value.key_type for value in values_type) v_types = set(value.value_type for value in values_type) if all(isinstance(x, mapping_type) for x in values_type): values_type = {mapping_type(keys_type=k_types, values_type=v_types)} else: from boa3.model.type.type import Type generic_type: IType = Type.get_generic_type(*values_type) if isinstance(generic_type, MappingType): values_type = {generic_type.build_collection(k_types, v_types)} return values_type # if any value is not a map, call the collection filter return super().filter_types(values_type)
def get_types(cls, value: Any) -> Set[IType]: from boa3.model.type.type import Type if isinstance(value, IType): return {value} if not isinstance(value, Iterable): value = {value} types: Set[IType] = { val if isinstance(val, IType) else Type.get_type(val) for val in value } return cls.filter_types(types)
def visit_IfExp(self, if_node: ast.IfExp): """ Verifies if the type of if test is valid :param if_node: the python ast if expression node """ self.validate_if(if_node) body = if_node.body orelse = if_node.orelse if_value = body[-1] if isinstance(body, list) and len(body) > 0 else body else_value = orelse[-1] if isinstance(orelse, list) and len(orelse) > 0 else orelse if_type: IType = self.get_type(if_value) else_type: IType = self.get_type(else_value) return Type.get_generic_type(if_type, else_type)
def visit_Dict(self, dict_node: ast.Dict) -> Dict[Any, Any]: """ Visitor of literal dict node :param dict_node: the python ast dict node :return: a list with each key and value type """ dictionary = {} size = min(len(dict_node.keys), len(dict_node.values)) for index in range(size): key = self.get_type(dict_node.keys[index]) value = self.get_type(dict_node.values[index]) if key in dictionary and dictionary[key] != value: dictionary[key] = Type.get_generic_type(dictionary[key], value) else: dictionary[key] = value return dictionary
def get_type(self, value: Any, use_metatype: bool = False) -> IType: """ Returns the type of the given value. :param value: value to get the type :param use_metatype: whether it should return `Type.type` if the value is an IType implementation. :return: Returns the :class:`IType` of the the type of the value. `Type.none` by default. """ # visits if it is a node if isinstance(value, ast.AST): fun_rtype_id: Any = ast.NodeVisitor.visit(self, value) if isinstance(fun_rtype_id, ast.Name): fun_rtype_id = fun_rtype_id.id if isinstance(fun_rtype_id, str) and not isinstance(value, ast.Str): value = self.get_symbol(fun_rtype_id, origin_node=value) if isinstance(value, IType) and not isinstance(value, MetaType): value = TypeUtils.type.build( value) if use_metatype else value else: value = fun_rtype_id if isinstance(value, Attribute): if ((isinstance(value.attr_symbol, IExpression) and isinstance(value.attr_symbol.type, ClassType)) or (isinstance(value.attr_symbol, IType))): value = value.attr_symbol elif isinstance(value.type, IType): value = value.type if isinstance(value, IType): final_type = value elif isinstance(value, IExpression): final_type = value.type elif isinstance(value, IOperation): final_type = value.result else: final_type = Type.get_type(value) if isinstance(final_type, MetaType) and not use_metatype: return final_type.meta_type else: return final_type
def visit_Dict(self, dict_node: ast.Dict) -> Optional[Dict[Any, Any]]: """ Visitor of literal dict node :param dict_node: the python ast dict node :return: the value of the dict """ dictionary = {} size = min(len(dict_node.keys), len(dict_node.values)) for index in range(size): key = self.get_type(dict_node.keys[index]) value = self.get_type(dict_node.values[index]) if key in dictionary and dictionary[key] != value: dictionary[key] = Type.get_generic_type(dictionary[key], value) else: dictionary[key] = value keys = set(dictionary.keys()) values = set(dictionary.values()) if Type.none in keys or Type.none in values: # if can't define the type of any key or value, let it to be defined in the type checking return None return dictionary
def __include_builtins_symbols(self): """ Include the Python builtins in the global symbol table """ self.symbol_table.update(Type.builtin_types())
def _find_package(self, module_origin: str, origin_file: Optional[str] = None): path: List[str] = module_origin.split(os.sep) package = imports.builtin.get_package(self._import_identifier) if hasattr(package, 'symbols'): if hasattr(package, 'inner_packages'): # when have symbol and packages with the same id, prioritize symbol self.symbols: Dict[str, ISymbol] = package.inner_packages self.symbols.update(package.symbols) else: self.symbols = package.symbols self.can_be_imported = True self.is_builtin_import = True return if (os.path.commonpath([self.path, constants.BOA_PACKAGE_PATH ]) != constants.BOA_PACKAGE_PATH or ('boa3' in path and constants.PATH_SEPARATOR.join( path[path.index('boa3'):]).startswith('boa3/builtin'))): # doesn't analyse boa3.builtin packages that aren't included in the imports.builtin as an user module # TODO: refactor when importing from user modules is accepted import re inside_python_folder = any( re.search(r'python(\d\.?)*', folder.lower()) for folder in path) updated_tree = None if not (inside_python_folder and 'lib' in path): # check circular imports to avoid recursions inside the compiler if self.path in self._import_stack: self.recursive_import = True return # TODO: only user modules and typing lib imports are implemented try: if self.path in self._imported_files: analyser = self._imported_files[self.path] else: from boa3.analyser.analyser import Analyser origin = origin_file.replace(os.sep, constants.PATH_SEPARATOR) files = self._import_stack files.append(origin) if self.is_namespace_package: analyser = Analyser(self.tree, module_origin, self.root_folder, self._log) if self._include_inner_packages(analyser): analyser.is_analysed = True self._imported_files[self.path] = analyser else: analyser = Analyser.analyse( module_origin, root=self.root_folder, imported_files=self._imported_files, import_stack=files, log=self._log) self._include_inner_packages(analyser) if analyser.is_analysed: self._imported_files[self.path] = analyser # include only imported symbols if analyser.is_analysed: for symbol_id, symbol in analyser.symbol_table.items(): if symbol_id not in Type.all_types(): if not self._get_from_entry: symbol.defined_by_entry = False self.symbols[symbol_id] = symbol self.errors.extend(analyser.errors) self.warnings.extend(analyser.warnings) updated_tree = analyser.ast_tree self.analyser = analyser self.can_be_imported = analyser.is_analysed except FileNotFoundError: self.can_be_imported = False if updated_tree is not None: self._tree = updated_tree