def analyze_type_alias(node: Node, lookup_func: Callable[[str, Context], SymbolTableNode], lookup_fqn_func: Callable[[str], SymbolTableNode], fail_func: Callable[[str, Context], None]) -> Type: """Return type if node is valid as a type alias rvalue. Return None otherwise. 'node' must have been semantically analyzed. """ # Quickly return None if the expression doesn't look like a type. Note # that we don't support straight string literals as type aliases # (only string literals within index expressions). if isinstance(node, RefExpr): if not (isinstance(node.node, TypeInfo) or node.fullname == 'typing.Any' or node.kind == TYPE_ALIAS): return None elif isinstance(node, IndexExpr): base = node.base if isinstance(base, RefExpr): if not (isinstance(base.node, TypeInfo) or base.fullname in type_constructors): return None else: return None else: return None # It's a type alias (though it may be an invalid one). try: type = expr_to_unanalyzed_type(node) except TypeTranslationError: fail_func('Invalid type alias', node) return None analyzer = TypeAnalyser(lookup_func, lookup_fqn_func, fail_func) return type.accept(analyzer)
def check_newtype_args(self, name: str, call: CallExpr, context: Context) -> Optional[Type]: has_failed = False args, arg_kinds = call.args, call.arg_kinds if len(args) != 2 or arg_kinds[0] != ARG_POS or arg_kinds[1] != ARG_POS: self.fail("NewType(...) expects exactly two positional arguments", context) return None # Check first argument if not isinstance(args[0], (StrExpr, BytesExpr, UnicodeExpr)): self.fail("Argument 1 to NewType(...) must be a string literal", context) has_failed = True elif args[0].value != name: msg = "String argument 1 '{}' to NewType(...) does not match variable name '{}'" self.fail(msg.format(args[0].value, name), context) has_failed = True # Check second argument msg = "Argument 2 to NewType(...) must be a valid type" try: unanalyzed_type = expr_to_unanalyzed_type(args[1]) except TypeTranslationError: self.fail(msg, context) return None # We want to use our custom error message (see above), so we suppress # the default error message for invalid types here. old_type = self.api.anal_type(unanalyzed_type, report_invalid_types=False) # The caller of this function assumes that if we return a Type, it's always # a valid one. So, we translate AnyTypes created from errors into None. if isinstance(old_type, AnyType) and old_type.is_from_error: self.fail(msg, context) return None return None if has_failed else old_type
def parse_namedtuple_fields_with_types(self, nodes: List[Expression], context: Context ) -> Optional[Tuple[List[str], List[Type], List[Expression], bool]]: """Parse typed named tuple fields. Return (names, types, defaults, error ocurred), or None if at least one of the types is not ready. """ items = [] # type: List[str] types = [] # type: List[Type] for item in nodes: if isinstance(item, TupleExpr): if len(item.items) != 2: return self.fail_namedtuple_arg("Invalid NamedTuple field definition", item) name, type_node = item.items if isinstance(name, (StrExpr, BytesExpr, UnicodeExpr)): items.append(name.value) else: return self.fail_namedtuple_arg("Invalid NamedTuple() field name", item) try: type = expr_to_unanalyzed_type(type_node) except TypeTranslationError: return self.fail_namedtuple_arg('Invalid field type', type_node) analyzed = self.api.anal_type(type) # These should be all known, otherwise we would defer in visit_assignment_stmt(). if analyzed is None: return None types.append(analyzed) else: return self.fail_namedtuple_arg("Tuple expected as NamedTuple() field", item) return items, types, [], True
def parse_typeddict_fields_with_types( self, dict_items: List[Tuple[Optional[Expression], Expression]], context: Context) -> Optional[Tuple[List[str], List[Type], bool]]: """Parse typed dict items passed as pairs (name expression, type expression). Return names, types, was there an error. If some type is not ready, return None. """ items = [] # type: List[str] types = [] # type: List[Type] for (field_name_expr, field_type_expr) in dict_items: if isinstance(field_name_expr, (StrExpr, BytesExpr, UnicodeExpr)): items.append(field_name_expr.value) else: name_context = field_name_expr or field_type_expr self.fail_typeddict_arg("Invalid TypedDict() field name", name_context) return [], [], False try: type = expr_to_unanalyzed_type(field_type_expr) except TypeTranslationError: self.fail_typeddict_arg('Invalid field type', field_type_expr) return [], [], False analyzed = self.api.anal_type(type) if analyzed is None: return None types.append(analyzed) return items, types, True
def _attribute_from_attrib_maker(ctx: 'mypy.plugin.ClassDefContext', auto_attribs: bool, lhs: NameExpr, rvalue: CallExpr, stmt: AssignmentStmt) -> Optional[Attribute]: """Return an Attribute from the assignment or None if you can't make one.""" if auto_attribs and not stmt.new_syntax: # auto_attribs requires an annotation on *every* attr.ib. assert lhs.node is not None ctx.api.msg.need_annotation_for_var(lhs.node, stmt) return None if len(stmt.lvalues) > 1: ctx.api.fail("Too many names for one attribute", stmt) return None # This is the type that belongs in the __init__ method for this attrib. init_type = stmt.type # Read all the arguments from the call. init = _get_bool_argument(ctx, rvalue, 'init', True) # TODO: Check for attr.NOTHING attr_has_default = bool(_get_argument(rvalue, 'default')) # If the type isn't set through annotation but is passed through `type=` use that. type_arg = _get_argument(rvalue, 'type') if type_arg and not init_type: try: un_type = expr_to_unanalyzed_type(type_arg) except TypeTranslationError: ctx.api.fail('Invalid argument to type', type_arg) else: init_type = ctx.api.anal_type(un_type) if init_type and isinstance(lhs.node, Var) and not lhs.node.type: # If there is no annotation, add one. lhs.node.type = init_type lhs.is_inferred_def = False # Note: convert is deprecated but works the same as converter. converter = _get_argument(rvalue, 'converter') convert = _get_argument(rvalue, 'convert') if convert and converter: ctx.api.fail("Can't pass both `convert` and `converter`.", rvalue) elif convert: ctx.api.fail("convert is deprecated, use converter", rvalue) converter = convert converter_name = _get_converter_name(ctx, converter) return Attribute(lhs.name, ctx.cls.info, attr_has_default, init, converter_name, stmt)
def parse_typeddict_fields_with_types( self, dict_items: List[Tuple[Optional[Expression], Expression]], context: Context) -> Tuple[List[str], List[Type], bool]: items = [] # type: List[str] types = [] # type: List[Type] for (field_name_expr, field_type_expr) in dict_items: if isinstance(field_name_expr, (StrExpr, BytesExpr, UnicodeExpr)): items.append(field_name_expr.value) else: name_context = field_name_expr or field_type_expr self.fail_typeddict_arg("Invalid TypedDict() field name", name_context) return [], [], False try: type = expr_to_unanalyzed_type(field_type_expr) except TypeTranslationError: self.fail_typeddict_arg('Invalid field type', field_type_expr) return [], [], False types.append(self.api.anal_type(type)) return items, types, True
def parse_namedtuple_fields_with_types(self, nodes: List[Expression], context: Context ) -> Tuple[List[str], List[Type], List[Expression], bool]: items = [] # type: List[str] types = [] # type: List[Type] for item in nodes: if isinstance(item, TupleExpr): if len(item.items) != 2: return self.fail_namedtuple_arg("Invalid NamedTuple field definition", item) name, type_node = item.items if isinstance(name, (StrExpr, BytesExpr, UnicodeExpr)): items.append(name.value) else: return self.fail_namedtuple_arg("Invalid NamedTuple() field name", item) try: type = expr_to_unanalyzed_type(type_node) except TypeTranslationError: return self.fail_namedtuple_arg('Invalid field type', type_node) types.append(self.api.anal_type(type)) else: return self.fail_namedtuple_arg("Tuple expected as NamedTuple() field", item) return items, types, [], True
def analyze_type_alias( node: Expression, api: SemanticAnalyzerInterface, tvar_scope: TypeVarScope, plugin: Plugin, options: Options, is_typeshed_stub: bool, allow_unnormalized: bool = False, in_dynamic_func: bool = False, global_scope: bool = True, warn_bound_tvar: bool = False) -> Optional[Tuple[Type, Set[str]]]: """Analyze r.h.s. of a (potential) type alias definition. If `node` is valid as a type alias rvalue, return the resulting type and a set of full names of type aliases it depends on (directly or indirectly). Return None otherwise. 'node' must have been semantically analyzed. """ # Quickly return None if the expression doesn't look like a type. Note # that we don't support straight string literals as type aliases # (only string literals within index expressions). if isinstance(node, RefExpr): # Note that this misses the case where someone tried to use a # class-referenced type variable as a type alias. It's easier to catch # that one in checkmember.py if node.kind == TVAR: api.fail( 'Type variable "{}" is invalid as target for type alias'. format(node.fullname), node) return None if not (isinstance(node.node, TypeInfo) or node.fullname == 'typing.Any' or node.kind == TYPE_ALIAS): return None elif isinstance(node, IndexExpr): base = node.base if isinstance(base, RefExpr): if not (isinstance(base.node, TypeInfo) or base.fullname in type_constructors or base.kind == TYPE_ALIAS): return None # Enums can't be generic, and without this check we may incorrectly interpret indexing # an Enum class as creating a type alias. if isinstance(base.node, TypeInfo) and base.node.is_enum: return None else: return None elif isinstance(node, CallExpr): if (isinstance(node.callee, NameExpr) and len(node.args) == 1 and isinstance(node.args[0], NameExpr)): call = api.lookup_qualified(node.callee.name, node.callee) arg = api.lookup_qualified(node.args[0].name, node.args[0]) if (call is not None and call.node and call.node.fullname() == 'builtins.type' and arg is not None and arg.node and arg.node.fullname() == 'builtins.None'): return NoneTyp(), set() return None return None else: return None # It's a type alias (though it may be an invalid one). try: type = expr_to_unanalyzed_type(node) except TypeTranslationError: api.fail('Invalid type alias', node) return None analyzer = TypeAnalyser(api, tvar_scope, plugin, options, is_typeshed_stub, aliasing=True, allow_unnormalized=allow_unnormalized, warn_bound_tvar=warn_bound_tvar) analyzer.in_dynamic_func = in_dynamic_func analyzer.global_scope = global_scope res = type.accept(analyzer) return res, analyzer.aliases_used
def analyze_type_alias(node: Expression, lookup_func: Callable[[str, Context], Optional[SymbolTableNode]], lookup_fqn_func: Callable[[str], SymbolTableNode], tvar_scope: TypeVarScope, fail_func: Callable[[str, Context], None], note_func: Callable[[str, Context], None], plugin: Plugin, options: Options, is_typeshed_stub: bool, allow_unnormalized: bool = False, in_dynamic_func: bool = False, global_scope: bool = True, warn_bound_tvar: bool = False) -> Optional[Type]: """Return type if node is valid as a type alias rvalue. Return None otherwise. 'node' must have been semantically analyzed. """ # Quickly return None if the expression doesn't look like a type. Note # that we don't support straight string literals as type aliases # (only string literals within index expressions). if isinstance(node, RefExpr): # Note that this misses the case where someone tried to use a # class-referenced type variable as a type alias. It's easier to catch # that one in checkmember.py if node.kind == TVAR: fail_func('Type variable "{}" is invalid as target for type alias'.format( node.fullname), node) return None if not (isinstance(node.node, TypeInfo) or node.fullname == 'typing.Any' or node.kind == TYPE_ALIAS): return None elif isinstance(node, IndexExpr): base = node.base if isinstance(base, RefExpr): if not (isinstance(base.node, TypeInfo) or base.fullname in type_constructors or base.kind == TYPE_ALIAS): return None # Enums can't be generic, and without this check we may incorrectly interpret indexing # an Enum class as creating a type alias. if isinstance(base.node, TypeInfo) and base.node.is_enum: return None else: return None elif isinstance(node, CallExpr): if (isinstance(node.callee, NameExpr) and len(node.args) == 1 and isinstance(node.args[0], NameExpr)): call = lookup_func(node.callee.name, node.callee) arg = lookup_func(node.args[0].name, node.args[0]) if (call is not None and call.node and call.node.fullname() == 'builtins.type' and arg is not None and arg.node and arg.node.fullname() == 'builtins.None'): return NoneTyp() return None return None else: return None # It's a type alias (though it may be an invalid one). try: type = expr_to_unanalyzed_type(node) except TypeTranslationError: fail_func('Invalid type alias', node) return None analyzer = TypeAnalyser(lookup_func, lookup_fqn_func, tvar_scope, fail_func, note_func, plugin, options, is_typeshed_stub, aliasing=True, allow_unnormalized=allow_unnormalized, warn_bound_tvar=warn_bound_tvar) analyzer.in_dynamic_func = in_dynamic_func analyzer.global_scope = global_scope return type.accept(analyzer)
def _attribute_from_attrib_maker(ctx: 'mypy.plugin.ClassDefContext', auto_attribs: bool, kw_only: bool, lhs: NameExpr, rvalue: CallExpr, stmt: AssignmentStmt) -> Optional[Attribute]: """Return an Attribute from the assignment or None if you can't make one.""" if auto_attribs and not stmt.new_syntax: # auto_attribs requires an annotation on *every* attr.ib. assert lhs.node is not None ctx.api.msg.need_annotation_for_var(lhs.node, stmt) return None if len(stmt.lvalues) > 1: ctx.api.fail("Too many names for one attribute", stmt) return None # This is the type that belongs in the __init__ method for this attrib. init_type = stmt.type # Read all the arguments from the call. init = _get_bool_argument(ctx, rvalue, 'init', True) # Note: If the class decorator says kw_only=True the attribute is ignored. # See https://github.com/python-attrs/attrs/issues/481 for explanation. kw_only |= _get_bool_argument(ctx, rvalue, 'kw_only', False) if kw_only and ctx.api.options.python_version[0] < 3: ctx.api.fail(KW_ONLY_PYTHON_2_UNSUPPORTED, stmt) return None # TODO: Check for attr.NOTHING attr_has_default = bool(_get_argument(rvalue, 'default')) attr_has_factory = bool(_get_argument(rvalue, 'factory')) if attr_has_default and attr_has_factory: ctx.api.fail("Can't pass both `default` and `factory`.", rvalue) elif attr_has_factory: attr_has_default = True # If the type isn't set through annotation but is passed through `type=` use that. type_arg = _get_argument(rvalue, 'type') if type_arg and not init_type: try: un_type = expr_to_unanalyzed_type(type_arg) except TypeTranslationError: ctx.api.fail('Invalid argument to type', type_arg) else: init_type = ctx.api.anal_type(un_type) if init_type and isinstance(lhs.node, Var) and not lhs.node.type: # If there is no annotation, add one. lhs.node.type = init_type lhs.is_inferred_def = False # Note: convert is deprecated but works the same as converter. converter = _get_argument(rvalue, 'converter') convert = _get_argument(rvalue, 'convert') if convert and converter: ctx.api.fail("Can't pass both `convert` and `converter`.", rvalue) elif convert: ctx.api.fail("convert is deprecated, use converter", rvalue) converter = convert converter_info = _parse_converter(ctx, converter) name = unmangle(lhs.name) return Attribute(name, ctx.cls.info, attr_has_default, init, kw_only, converter_info, stmt, init_type)
def analyze_type_alias(node: Expression, lookup_func: Callable[[str, Context], Optional[SymbolTableNode]], lookup_fqn_func: Callable[[str], SymbolTableNode], tvar_scope: TypeVarScope, fail_func: Callable[[str, Context], None], note_func: Callable[[str, Context], None], plugin: Plugin, options: Options, is_typeshed_stub: bool, allow_unnormalized: bool = False, in_dynamic_func: bool = False, global_scope: bool = True, warn_bound_tvar: bool = False) -> Optional[Type]: """Return type if node is valid as a type alias rvalue. Return None otherwise. 'node' must have been semantically analyzed. """ # Quickly return None if the expression doesn't look like a type. Note # that we don't support straight string literals as type aliases # (only string literals within index expressions). if isinstance(node, RefExpr): # Note that this misses the case where someone tried to use a # class-referenced type variable as a type alias. It's easier to catch # that one in checkmember.py if node.kind == TVAR: fail_func( 'Type variable "{}" is invalid as target for type alias'. format(node.fullname), node) return None if not (isinstance(node.node, TypeInfo) or node.fullname == 'typing.Any' or node.kind == TYPE_ALIAS): return None elif isinstance(node, IndexExpr): base = node.base if isinstance(base, RefExpr): if not (isinstance(base.node, TypeInfo) or base.fullname in type_constructors or base.kind == TYPE_ALIAS): return None # Enums can't be generic, and without this check we may incorrectly interpret indexing # an Enum class as creating a type alias. if isinstance(base.node, TypeInfo) and base.node.is_enum: return None else: return None elif isinstance(node, CallExpr): if (isinstance(node.callee, NameExpr) and len(node.args) == 1 and isinstance(node.args[0], NameExpr)): call = lookup_func(node.callee.name, node.callee) arg = lookup_func(node.args[0].name, node.args[0]) if (call is not None and call.node and call.node.fullname() == 'builtins.type' and arg is not None and arg.node and arg.node.fullname() == 'builtins.None'): return NoneTyp() return None return None else: return None # It's a type alias (though it may be an invalid one). try: type = expr_to_unanalyzed_type(node) except TypeTranslationError: fail_func('Invalid type alias', node) return None analyzer = TypeAnalyser(lookup_func, lookup_fqn_func, tvar_scope, fail_func, note_func, plugin, options, is_typeshed_stub, aliasing=True, allow_unnormalized=allow_unnormalized, warn_bound_tvar=warn_bound_tvar) analyzer.in_dynamic_func = in_dynamic_func analyzer.global_scope = global_scope return type.accept(analyzer)
def _attribute_from_attrib_maker(ctx: 'mypy.plugin.ClassDefContext', auto_attribs: bool, kw_only: bool, lhs: NameExpr, rvalue: CallExpr, stmt: AssignmentStmt) -> Optional[Attribute]: """Return an Attribute from the assignment or None if you can't make one.""" if auto_attribs and not stmt.new_syntax: # auto_attribs requires an annotation on *every* attr.ib. assert lhs.node is not None ctx.api.msg.need_annotation_for_var(lhs.node, stmt) return None if len(stmt.lvalues) > 1: ctx.api.fail("Too many names for one attribute", stmt) return None # This is the type that belongs in the __init__ method for this attrib. init_type = stmt.type # Read all the arguments from the call. init = _get_bool_argument(ctx, rvalue, 'init', True) # Note: If the class decorator says kw_only=True the attribute is ignored. # See https://github.com/python-attrs/attrs/issues/481 for explanation. kw_only |= _get_bool_argument(ctx, rvalue, 'kw_only', False) if kw_only and ctx.api.options.python_version[0] < 3: ctx.api.fail(KW_ONLY_PYTHON_2_UNSUPPORTED, stmt) return None # TODO: Check for attr.NOTHING attr_has_default = bool(_get_argument(rvalue, 'default')) attr_has_factory = bool(_get_argument(rvalue, 'factory')) if attr_has_default and attr_has_factory: ctx.api.fail("Can't pass both `default` and `factory`.", rvalue) elif attr_has_factory: attr_has_default = True # If the type isn't set through annotation but is passed through `type=` use that. type_arg = _get_argument(rvalue, 'type') if type_arg and not init_type: try: un_type = expr_to_unanalyzed_type(type_arg) except TypeTranslationError: ctx.api.fail('Invalid argument to type', type_arg) else: init_type = ctx.api.anal_type(un_type) if init_type and isinstance(lhs.node, Var) and not lhs.node.type: # If there is no annotation, add one. lhs.node.type = init_type lhs.is_inferred_def = False # Note: convert is deprecated but works the same as converter. converter = _get_argument(rvalue, 'converter') convert = _get_argument(rvalue, 'convert') if convert and converter: ctx.api.fail("Can't pass both `convert` and `converter`.", rvalue) elif convert: ctx.api.fail("convert is deprecated, use converter", rvalue) converter = convert converter_info = _parse_converter(ctx, converter) name = unmangle(lhs.name) return Attribute(name, ctx.cls.info, attr_has_default, init, kw_only, converter_info, stmt)
def analyze_type_alias(node: Expression, api: SemanticAnalyzerInterface, tvar_scope: TypeVarScope, plugin: Plugin, options: Options, is_typeshed_stub: bool, allow_unnormalized: bool = False, in_dynamic_func: bool = False, global_scope: bool = True, warn_bound_tvar: bool = False) -> Optional[Tuple[Type, Set[str]]]: """Analyze r.h.s. of a (potential) type alias definition. If `node` is valid as a type alias rvalue, return the resulting type and a set of full names of type aliases it depends on (directly or indirectly). Return None otherwise. 'node' must have been semantically analyzed. """ # Quickly return None if the expression doesn't look like a type. Note # that we don't support straight string literals as type aliases # (only string literals within index expressions). if isinstance(node, RefExpr): # Note that this misses the case where someone tried to use a # class-referenced type variable as a type alias. It's easier to catch # that one in checkmember.py if node.kind == TVAR: api.fail('Type variable "{}" is invalid as target for type alias'.format( node.fullname), node) return None if not (isinstance(node.node, TypeInfo) or node.fullname == 'typing.Any' or node.kind == TYPE_ALIAS): return None elif isinstance(node, IndexExpr): base = node.base if isinstance(base, RefExpr): if not (isinstance(base.node, TypeInfo) or base.fullname in type_constructors or base.kind == TYPE_ALIAS): return None # Enums can't be generic, and without this check we may incorrectly interpret indexing # an Enum class as creating a type alias. if isinstance(base.node, TypeInfo) and base.node.is_enum: return None else: return None elif isinstance(node, CallExpr): if (isinstance(node.callee, NameExpr) and len(node.args) == 1 and isinstance(node.args[0], NameExpr)): call = api.lookup_qualified(node.callee.name, node.callee) arg = api.lookup_qualified(node.args[0].name, node.args[0]) if (call is not None and call.node and call.node.fullname() == 'builtins.type' and arg is not None and arg.node and arg.node.fullname() == 'builtins.None'): return NoneTyp(), set() return None return None else: return None # It's a type alias (though it may be an invalid one). try: type = expr_to_unanalyzed_type(node) except TypeTranslationError: api.fail('Invalid type alias', node) return None analyzer = TypeAnalyser(api, tvar_scope, plugin, options, is_typeshed_stub, aliasing=True, allow_unnormalized=allow_unnormalized, warn_bound_tvar=warn_bound_tvar) analyzer.in_dynamic_func = in_dynamic_func analyzer.global_scope = global_scope res = type.accept(analyzer) return res, analyzer.aliases_used
def _field_from_field_def( self, stmt: nodes.AssignmentStmt, name: nodes.NameExpr, sym: nodes.SymbolTableNode, ) -> Optional[Field]: ctx = self._ctx rhs = stmt.rvalue if isinstance(rhs, nodes.CastExpr): rhs = rhs.expr if not isinstance(rhs, nodes.CallExpr): return None fdef = rhs.callee ftype = None if ( isinstance(fdef, nodes.IndexExpr) and isinstance(fdef.analyzed, nodes.TypeApplication) ): # Explicitly typed Field declaration ctor = fdef.analyzed.expr if len(fdef.analyzed.types) > 1: ctx.api.fail('too many type arguments to Field', fdef) ftype = fdef.analyzed.types[0] else: ctor = fdef ftype = None if ( not isinstance(ctor, nodes.RefExpr) or ctor.fullname not in self._field_makers ): return None type_arg = rhs.args[0] deflt = self._get_default(rhs) if ftype is None: try: un_type = exprtotype.expr_to_unanalyzed_type(type_arg) except exprtotype.TypeTranslationError: ctx.api.fail('Cannot resolve schema field type', type_arg) else: ftype = ctx.api.anal_type(un_type) if ftype is None: raise DeferException is_optional = ( isinstance(deflt, nodes.NameExpr) and deflt.fullname == 'builtins.None' ) if is_optional: ftype = types.UnionType.make_union( [ftype, types.NoneType()], line=ftype.line, column=ftype.column, ) assert isinstance(name.node, nodes.Var) name.node.type = ftype return Field( name=name.name, has_explicit_accessor=self._has_explicit_field_accessor(name.name), has_default=deflt is not None, line=stmt.line, column=stmt.column, type=ftype, )