def find_replacements_in_call(self, call: CallExpr, keys: List[str]) -> List[Expression]: """Find replacement expression for every specifier in str.format() call. In case of an error use TempNode(AnyType). """ result = [] # type: List[Expression] used = set() # type: Set[Expression] for key in keys: if key.isdecimal(): expr = self.get_expr_by_position(int(key), call) if not expr: self.msg.fail('Cannot find replacement for positional' ' format specifier {}'.format(key), call, code=codes.STRING_FORMATTING) expr = TempNode(AnyType(TypeOfAny.from_error)) else: expr = self.get_expr_by_name(key, call) if not expr: self.msg.fail('Cannot find replacement for named' ' format specifier "{}"'.format(key), call, code=codes.STRING_FORMATTING) expr = TempNode(AnyType(TypeOfAny.from_error)) result.append(expr) if not isinstance(expr, TempNode): used.add(expr) # Strictly speaking not using all replacements is not a type error, but most likely # a typo in user code, so we show an error like we do for % formatting. total_explicit = len( [kind for kind in call.arg_kinds if kind in (ARG_POS, ARG_NAMED)]) if len(used) < total_explicit: self.msg.too_many_string_formatting_arguments(call) return result
def get_expr_by_position(self, pos: int, call: CallExpr) -> Optional[Expression]: """Get positional replacement expression from '{0}, {1}'.format(x, y, ...) call. If the type is from *args, return TempNode(<item type>). Return None in case of an error. """ pos_args = [ arg for arg, kind in zip(call.args, call.arg_kinds) if kind == ARG_POS ] if pos < len(pos_args): return pos_args[pos] star_args = [ arg for arg, kind in zip(call.args, call.arg_kinds) if kind == ARG_STAR ] if not star_args: return None # Fall back to *args when present in call. star_arg = star_args[0] varargs_type = get_proper_type(self.chk.type_map[star_arg]) if (not isinstance(varargs_type, Instance) or not varargs_type.type.has_base('typing.Sequence')): # Error should be already reported. return TempNode(AnyType(TypeOfAny.special_form)) iter_info = self.chk.named_generic_type( 'typing.Sequence', [AnyType(TypeOfAny.special_form)]).type return TempNode( map_instance_to_supertype(varargs_type, iter_info).args[0])
def apply_field_accessors(self, spec: ConversionSpecifier, repl: Expression, ctx: Context) -> Expression: """Transform and validate expr in '{.attr[item]}'.format(expr) into expr.attr['item']. If validation fails, return TempNode(AnyType). """ assert spec.key, "Keys must be auto-generated first!" if spec.field == spec.key: return repl assert spec.field # This is a bit of a dirty trick, but it looks like this is the simplest way. temp_errors = self.msg.clean_copy().errors dummy = DUMMY_FIELD_NAME + spec.field[len(spec.key):] temp_ast = parse(dummy, fnam='<format>', module=None, options=self.chk.options, errors=temp_errors) # type: Node if temp_errors.is_errors(): self.msg.fail('Syntax error in format specifier "{}"'.format(spec.field), ctx, code=codes.STRING_FORMATTING) return TempNode(AnyType(TypeOfAny.from_error)) # These asserts are guaranteed by the original regexp. assert isinstance(temp_ast, MypyFile) temp_ast = temp_ast.defs[0] assert isinstance(temp_ast, ExpressionStmt) temp_ast = temp_ast.expr if not self.validate_and_transform_accessors(temp_ast, repl, spec, ctx=ctx): return TempNode(AnyType(TypeOfAny.from_error)) # Check if there are any other errors (like missing members). # TODO: fix column to point to actual start of the format specifier _within_ string. temp_ast.line = ctx.line temp_ast.column = ctx.column self.exprchk.accept(temp_ast) return temp_ast
def get_expr_by_name(self, key: str, call: CallExpr) -> Optional[Expression]: """Get named replacement expression from '{name}'.format(name=...) call. If the type is from **kwargs, return TempNode(<item type>). Return None in case of an error. """ named_args = [ arg for arg, kind, name in zip(call.args, call.arg_kinds, call.arg_names) if kind == ARG_NAMED and name == key ] if named_args: return named_args[0] star_args_2 = [ arg for arg, kind in zip(call.args, call.arg_kinds) if kind == ARG_STAR2 ] if not star_args_2: return None star_arg_2 = star_args_2[0] kwargs_type = get_proper_type(self.chk.type_map[star_arg_2]) if (not isinstance(kwargs_type, Instance) or not kwargs_type.type.has_base('typing.Mapping')): # Error should be already reported. return TempNode(AnyType(TypeOfAny.special_form)) any_type = AnyType(TypeOfAny.special_form) mapping_info = self.chk.named_generic_type('typing.Mapping', [any_type, any_type]).type return TempNode( map_instance_to_supertype(kwargs_type, mapping_info).args[1])
def check_simple_str_interpolation(self, specifiers: List[ConversionSpecifier], replacements: Expression, expr: FormatStringExpr) -> None: """Check % string interpolation with positional specifiers '%s, %d' % ('yes, 42').""" checkers = self.build_replacement_checkers(specifiers, replacements, expr) if checkers is None: return rhs_type = get_proper_type(self.accept(replacements)) rep_types: List[Type] = [] if isinstance(rhs_type, TupleType): rep_types = rhs_type.items elif isinstance(rhs_type, AnyType): return elif isinstance( rhs_type, Instance) and rhs_type.type.fullname == 'builtins.tuple': # Assume that an arbitrary-length tuple has the right number of items. rep_types = [rhs_type.args[0]] * len(checkers) elif isinstance(rhs_type, UnionType): for typ in rhs_type.relevant_items(): temp_node = TempNode(typ) temp_node.line = replacements.line self.check_simple_str_interpolation(specifiers, temp_node, expr) return else: rep_types = [rhs_type] if len(checkers) > len(rep_types): # Only check the fix-length Tuple type. Other Iterable types would skip. if (is_subtype(rhs_type, self.chk.named_type("typing.Iterable")) and not isinstance(rhs_type, TupleType)): return else: self.msg.too_few_string_formatting_arguments(replacements) elif len(checkers) < len(rep_types): self.msg.too_many_string_formatting_arguments(replacements) else: if len(checkers) == 1: check_node, check_type = checkers[0] if isinstance(rhs_type, TupleType) and len( rhs_type.items) == 1: check_type(rhs_type.items[0]) else: check_node(replacements) elif (isinstance(replacements, TupleExpr) and not any( isinstance(item, StarExpr) for item in replacements.items)): for checks, rep_node in zip(checkers, replacements.items): check_node, check_type = checks check_node(rep_node) else: for checks, rep_type in zip(checkers, rep_types): check_node, check_type = checks check_type(rep_type)
def transform_jsonclass_class(ctx: ClassDefContext) -> None: cls = ctx.cls for stmt in cls.defs.body: if not isinstance(stmt, AssignmentStmt): continue lhs = stmt.lvalues[0] if not isinstance(lhs, NameExpr): continue if not stmt.new_syntax: ctx.api.fail('Untyped fields disallowed in JSON Class body.', stmt, code=ERROR_UNTYPED) continue sym = cls.info.names.get(lhs.name) if sym is None: # This name is likely blocked by a star import. We don't need to defer because # defer() is already called by mark_incomplete(). continue node = sym.node if isinstance(node, PlaceholderNode): # This node is not ready yet. return None assert isinstance(node, Var) if node.is_classvar: continue # node_type = get_proper_type(node.type) if is_json_class_types_expr(stmt.rvalue): stmt.rvalue = TempNode(AnyType(TypeOfAny.explicit), False)
def visit_Assign(self, n: ast35.Assign) -> AssignmentStmt: typ = None if hasattr(n, 'annotation') and n.annotation is not None: # type: ignore new_syntax = True else: new_syntax = False if new_syntax and self.pyversion < (3, 6): raise TypeCommentParseError( 'Variable annotation syntax is only ' 'suppoted in Python 3.6, use type ' 'comment instead', n.lineno, n.col_offset) # typed_ast prevents having both type_comment and annotation. if n.type_comment is not None: typ = parse_type_comment(n.type_comment, n.lineno) elif new_syntax: typ = TypeConverter(line=n.lineno).visit( n.annotation) # type: ignore typ.column = n.annotation.col_offset if n.value is None: # always allow 'x: int' rvalue = TempNode(AnyType()) # type: Expression else: rvalue = self.visit(n.value) lvalues = self.translate_expr_list(n.targets) return AssignmentStmt(lvalues, rvalue, type=typ, new_syntax=new_syntax)
def visit_AnnAssign(self, n: ast3.AnnAssign) -> AssignmentStmt: if n.value is None: # always allow 'x: int' rvalue = TempNode(AnyType()) # type: Expression else: rvalue = self.visit(n.value) typ = TypeConverter(self.errors, line=n.lineno).visit(n.annotation) typ.column = n.annotation.col_offset return AssignmentStmt([self.visit(n.target)], rvalue, type=typ, new_syntax=True)
def add_additional_orm_attributes( cls: ClassDef, api: SemanticAnalyzerPluginInterface, attributes: List[util.SQLAlchemyAttribute], ) -> None: """Apply __init__, __table__ and other attributes to the mapped class.""" info = util.info_for_cls(cls, api) if info is None: return is_base = util.get_is_base(info) if "__init__" not in info.names and not is_base: mapped_attr_names = {attr.name: attr.type for attr in attributes} for base in info.mro[1:-1]: if "sqlalchemy" not in info.metadata: continue base_cls_attributes = util.get_mapped_attributes(base, api) if base_cls_attributes is None: continue for attr in base_cls_attributes: mapped_attr_names.setdefault(attr.name, attr.type) arguments = [] for name, typ in mapped_attr_names.items(): if typ is None: typ = AnyType(TypeOfAny.special_form) arguments.append( Argument( variable=Var(name, typ), type_annotation=typ, initializer=TempNode(typ), kind=ARG_NAMED_OPT, ) ) add_method_to_class(api, cls, "__init__", arguments, NoneTyp()) if "__table__" not in info.names and util.get_has_table(info): _apply_placeholder_attr_to_class( api, cls, "sqlalchemy.sql.schema.Table", "__table__" ) if not is_base: _apply_placeholder_attr_to_class( api, cls, "sqlalchemy.orm.mapper.Mapper", "__mapper__" )
def visit_AnnAssign(self, n: ast3.AnnAssign) -> AssignmentStmt: if n.value is None: # always allow 'x: int' rvalue = TempNode(AnyType(TypeOfAny.special_form), no_rhs=True) # type: Expression else: rvalue = self.visit(n.value) typ = TypeConverter(self.errors, line=n.lineno).visit(n.annotation) assert typ is not None typ.column = n.annotation.col_offset s = AssignmentStmt([self.visit(n.target)], rvalue, type=typ, new_syntax=True) return self.set_line(s, n)
def _add_additional_orm_attributes( cls: ClassDef, api: SemanticAnalyzerPluginInterface, cls_metadata: util.DeclClassApplied, ) -> None: """Apply __init__, __table__ and other attributes to the mapped class.""" info = util._info_for_cls(cls, api) if "__init__" not in info.names and cls_metadata.is_mapped: mapped_attr_names = {n: t for n, t in cls_metadata.mapped_attr_names} for mapped_base in cls_metadata.mapped_mro: base_cls_metadata = util.DeclClassApplied.deserialize( mapped_base.type.metadata["_sa_decl_class_applied"], api ) for n, t in base_cls_metadata.mapped_attr_names: mapped_attr_names.setdefault(n, t) arguments = [] for name, typ in mapped_attr_names.items(): if typ is None: typ = AnyType(TypeOfAny.special_form) arguments.append( Argument( variable=Var(name, typ), type_annotation=typ, initializer=TempNode(typ), kind=ARG_NAMED_OPT, ) ) add_method_to_class(api, cls, "__init__", arguments, NoneTyp()) if "__table__" not in info.names and cls_metadata.has_table: _apply_placeholder_attr_to_class( api, cls, "sqlalchemy.sql.schema.Table", "__table__" ) if cls_metadata.is_mapped: _apply_placeholder_attr_to_class( api, cls, "sqlalchemy.orm.mapper.Mapper", "__mapper__" )
def _scan_declarative_decorator_stmt( cls: ClassDef, api: SemanticAnalyzerPluginInterface, stmt: Decorator, attributes: List[util.SQLAlchemyAttribute], ) -> None: """Extract mapping information from a @declared_attr in a declarative class. E.g.:: @reg.mapped class MyClass: # ... @declared_attr def updated_at(cls) -> Column[DateTime]: return Column(DateTime) Will resolve in mypy as:: @reg.mapped class MyClass: # ... updated_at: Mapped[Optional[datetime.datetime]] """ for dec in stmt.decorators: if (isinstance(dec, (NameExpr, MemberExpr, SymbolNode)) and names.type_id_for_named_node(dec) is names.DECLARED_ATTR): break else: return dec_index = cls.defs.body.index(stmt) left_hand_explicit_type: Optional[ProperType] = None if util.name_is_dunder(stmt.name): # for dunder names like __table_args__, __tablename__, # __mapper_args__ etc., rewrite these as simple assignment # statements; otherwise mypy doesn't like if the decorated # function has an annotation like ``cls: Type[Foo]`` because # it isn't @classmethod any_ = AnyType(TypeOfAny.special_form) left_node = NameExpr(stmt.var.name) left_node.node = stmt.var new_stmt = AssignmentStmt([left_node], TempNode(any_)) new_stmt.type = left_node.node.type cls.defs.body[dec_index] = new_stmt return elif isinstance(stmt.func.type, CallableType): func_type = stmt.func.type.ret_type if isinstance(func_type, UnboundType): type_id = names.type_id_for_unbound_type(func_type, cls, api) else: # this does not seem to occur unless the type argument is # incorrect return if (type_id in { names.MAPPED, names.RELATIONSHIP, names.COMPOSITE_PROPERTY, names.MAPPER_PROPERTY, names.SYNONYM_PROPERTY, names.COLUMN_PROPERTY, } and func_type.args): left_hand_explicit_type = get_proper_type(func_type.args[0]) elif type_id is names.COLUMN and func_type.args: typeengine_arg = func_type.args[0] if isinstance(typeengine_arg, UnboundType): sym = api.lookup_qualified(typeengine_arg.name, typeengine_arg) if sym is not None and isinstance(sym.node, TypeInfo): if names.has_base_type_id(sym.node, names.TYPEENGINE): left_hand_explicit_type = UnionType([ infer.extract_python_type_from_typeengine( api, sym.node, []), NoneType(), ]) else: util.fail( api, "Column type should be a TypeEngine " "subclass not '{}'".format(sym.node.fullname), func_type, ) if left_hand_explicit_type is None: # no type on the decorated function. our option here is to # dig into the function body and get the return type, but they # should just have an annotation. msg = ("Can't infer type from @declared_attr on function '{}'; " "please specify a return type from this function that is " "one of: Mapped[<python type>], relationship[<target class>], " "Column[<TypeEngine>], MapperProperty[<python type>]") util.fail(api, msg.format(stmt.var.name), stmt) left_hand_explicit_type = AnyType(TypeOfAny.special_form) left_node = NameExpr(stmt.var.name) left_node.node = stmt.var # totally feeling around in the dark here as I don't totally understand # the significance of UnboundType. It seems to be something that is # not going to do what's expected when it is applied as the type of # an AssignmentStatement. So do a feeling-around-in-the-dark version # of converting it to the regular Instance/TypeInfo/UnionType structures # we see everywhere else. if isinstance(left_hand_explicit_type, UnboundType): left_hand_explicit_type = get_proper_type( util.unbound_to_instance(api, left_hand_explicit_type)) left_node.node.type = api.named_type(names.NAMED_TYPE_SQLA_MAPPED, [left_hand_explicit_type]) # this will ignore the rvalue entirely # rvalue = TempNode(AnyType(TypeOfAny.special_form)) # rewrite the node as: # <attr> : Mapped[<typ>] = # _sa_Mapped._empty_constructor(lambda: <function body>) # the function body is maintained so it gets type checked internally rvalue = util.expr_to_mapped_constructor( LambdaExpr(stmt.func.arguments, stmt.func.body)) new_stmt = AssignmentStmt([left_node], rvalue) new_stmt.type = left_node.node.type attributes.append( util.SQLAlchemyAttribute( name=left_node.name, line=stmt.line, column=stmt.column, typ=left_hand_explicit_type, info=cls.info, )) cls.defs.body[dec_index] = new_stmt
def addoption_class_callback(context: ClassDefContext) -> None: for stmt in context.cls.defs.body: if isinstance(stmt, AssignmentStmt) and stmt.type is not None: stmt.rvalue = TempNode(stmt.type, context=stmt.rvalue)
def expression(self, context: Context) -> TempNode: """Hack to pass unexisting `Expression` to typechecker.""" return TempNode(self.type, context=context)
def analyze_descriptor_access(descriptor_type: Type, mx: MemberContext) -> Type: """Type check descriptor access. Arguments: descriptor_type: The type of the descriptor attribute being accessed (the type of ``f`` in ``a.f`` when ``f`` is a descriptor). mx: The current member access context. Return: The return type of the appropriate ``__get__`` overload for the descriptor. """ instance_type = get_proper_type(mx.original_type) descriptor_type = get_proper_type(descriptor_type) if isinstance(descriptor_type, UnionType): # Map the access over union types return make_simplified_union([ analyze_descriptor_access(typ, mx) for typ in descriptor_type.items ]) elif not isinstance(descriptor_type, Instance): return descriptor_type if not descriptor_type.type.has_readable_member('__get__'): return descriptor_type dunder_get = descriptor_type.type.get_method('__get__') if dunder_get is None: mx.msg.fail( message_registry.DESCRIPTOR_GET_NOT_CALLABLE.format( descriptor_type), mx.context) return AnyType(TypeOfAny.from_error) bound_method = analyze_decorator_or_funcbase_access( defn=dunder_get, itype=descriptor_type, info=descriptor_type.type, self_type=descriptor_type, name='__set__', mx=mx) typ = map_instance_to_supertype(descriptor_type, dunder_get.info) dunder_get_type = expand_type_by_instance(bound_method, typ) if isinstance(instance_type, FunctionLike) and instance_type.is_type_obj(): owner_type = instance_type.items[0].ret_type instance_type = NoneType() elif isinstance(instance_type, TypeType): owner_type = instance_type.item instance_type = NoneType() else: owner_type = instance_type callable_name = mx.chk.expr_checker.method_fullname( descriptor_type, "__get__") dunder_get_type = mx.chk.expr_checker.transform_callee_type( callable_name, dunder_get_type, [ TempNode(instance_type, context=mx.context), TempNode(TypeType.make_normalized(owner_type), context=mx.context) ], [ARG_POS, ARG_POS], mx.context, object_type=descriptor_type, ) _, inferred_dunder_get_type = mx.chk.expr_checker.check_call( dunder_get_type, [ TempNode(instance_type, context=mx.context), TempNode(TypeType.make_normalized(owner_type), context=mx.context) ], [ARG_POS, ARG_POS], mx.context, object_type=descriptor_type, callable_name=callable_name) inferred_dunder_get_type = get_proper_type(inferred_dunder_get_type) if isinstance(inferred_dunder_get_type, AnyType): # check_call failed, and will have reported an error return inferred_dunder_get_type if not isinstance(inferred_dunder_get_type, CallableType): mx.msg.fail( message_registry.DESCRIPTOR_GET_NOT_CALLABLE.format( descriptor_type), mx.context) return AnyType(TypeOfAny.from_error) return inferred_dunder_get_type.ret_type
def visit_temp_node(self, node: TempNode) -> Node: return TempNode(self.type(node.type))
def analyze_descriptor_access(instance_type: Type, descriptor_type: Type, builtin_type: Callable[[str], Instance], msg: MessageBuilder, context: Context, *, chk: 'mypy.checker.TypeChecker') -> Type: """Type check descriptor access. Arguments: instance_type: The type of the instance on which the descriptor attribute is being accessed (the type of ``a`` in ``a.f`` when ``f`` is a descriptor). descriptor_type: The type of the descriptor attribute being accessed (the type of ``f`` in ``a.f`` when ``f`` is a descriptor). context: The node defining the context of this inference. Return: The return type of the appropriate ``__get__`` overload for the descriptor. """ if isinstance(descriptor_type, UnionType): # Map the access over union types return UnionType.make_simplified_union([ analyze_descriptor_access(instance_type, typ, builtin_type, msg, context, chk=chk) for typ in descriptor_type.items ]) elif not isinstance(descriptor_type, Instance): return descriptor_type if not descriptor_type.type.has_readable_member('__get__'): return descriptor_type dunder_get = descriptor_type.type.get_method('__get__') if dunder_get is None: msg.fail( message_registry.DESCRIPTOR_GET_NOT_CALLABLE.format( descriptor_type), context) return AnyType(TypeOfAny.from_error) function = function_type(dunder_get, builtin_type('builtins.function')) bound_method = bind_self(function, descriptor_type) typ = map_instance_to_supertype(descriptor_type, dunder_get.info) dunder_get_type = expand_type_by_instance(bound_method, typ) if isinstance(instance_type, FunctionLike) and instance_type.is_type_obj(): owner_type = instance_type.items()[0].ret_type instance_type = NoneTyp() elif isinstance(instance_type, TypeType): owner_type = instance_type.item instance_type = NoneTyp() else: owner_type = instance_type _, inferred_dunder_get_type = chk.expr_checker.check_call( dunder_get_type, [ TempNode(instance_type), TempNode(TypeType.make_normalized(owner_type)) ], [ARG_POS, ARG_POS], context) if isinstance(inferred_dunder_get_type, AnyType): # check_call failed, and will have reported an error return inferred_dunder_get_type if not isinstance(inferred_dunder_get_type, CallableType): msg.fail( message_registry.DESCRIPTOR_GET_NOT_CALLABLE.format( descriptor_type), context) return AnyType(TypeOfAny.from_error) return inferred_dunder_get_type.ret_type
def temp_node(self, t, context=None): """Create a temporary node with the given, fixed type.""" temp = TempNode(t) if context: temp.set_line(context.get_line()) return temp