def perform_special_format_checks(self, spec: ConversionSpecifier, call: CallExpr, repl: Expression, actual_type: Type, expected_type: Type) -> None: # TODO: try refactoring to combine this logic with % formatting. if spec.type == 'c': if isinstance(repl, (StrExpr, BytesExpr)) and len(repl.value) != 1: self.msg.requires_int_or_char(call, format_call=True) c_typ = get_proper_type(self.chk.type_map[repl]) if isinstance(c_typ, Instance) and c_typ.last_known_value: c_typ = c_typ.last_known_value if isinstance(c_typ, LiteralType) and isinstance(c_typ.value, str): if len(c_typ.value) != 1: self.msg.requires_int_or_char(call, format_call=True) if (not spec.type or spec.type == 's') and not spec.conversion: if self.chk.options.python_version >= (3, 0): if (has_type_component(actual_type, 'builtins.bytes') and not custom_special_method(actual_type, '__str__')): self.msg.fail( "On Python 3 '{}'.format(b'abc') produces \"b'abc'\", not 'abc'; " "use '{!r}'.format(b'abc') if this is desired behavior", call, code=codes.STR_BYTES_PY3) if spec.flags: numeric_types = UnionType([self.named_type('builtins.int'), self.named_type('builtins.float')]) if (spec.type and spec.type not in NUMERIC_TYPES_NEW or not spec.type and not is_subtype(actual_type, numeric_types) and not custom_special_method(actual_type, '__format__')): self.msg.fail('Numeric flags are only allowed for numeric types', call, code=codes.STRING_FORMATTING)
def check_specs_in_format_call(self, call: CallExpr, specs: List[ConversionSpecifier], format_value: str) -> None: """Perform pairwise checks for conversion specifiers vs their replacements. The core logic for format checking is implemented in this method. """ assert all(s.key for s in specs), "Keys must be auto-generated first!" replacements = self.find_replacements_in_call(call, [cast(str, s.key) for s in specs]) assert len(replacements) == len(specs) for spec, repl in zip(specs, replacements): repl = self.apply_field_accessors(spec, repl, ctx=call) actual_type = repl.type if isinstance(repl, TempNode) else self.chk.type_map.get(repl) assert actual_type is not None # Special case custom formatting. if (spec.format_spec and spec.non_standard_format_spec and # Exclude "dynamic" specifiers (i.e. containing nested formatting). not ('{' in spec.format_spec or '}' in spec.format_spec)): if (not custom_special_method(actual_type, '__format__', check_all=True) or spec.conversion): # TODO: add support for some custom specs like datetime? self.msg.fail('Unrecognized format' ' specification "{}"'.format(spec.format_spec[1:]), call, code=codes.STRING_FORMATTING) continue # Adjust expected and actual types. if not spec.type: expected_type = AnyType(TypeOfAny.special_form) # type: Optional[Type] else: assert isinstance(call.callee, MemberExpr) if isinstance(call.callee.expr, (StrExpr, UnicodeExpr)): format_str = call.callee.expr else: format_str = StrExpr(format_value) expected_type = self.conversion_type(spec.type, call, format_str, format_call=True) if spec.conversion is not None: # If the explicit conversion is given, then explicit conversion is called _first_. if spec.conversion[1] not in 'rsa': self.msg.fail('Invalid conversion type "{}",' ' must be one of "r", "s" or "a"'.format(spec.conversion[1]), call, code=codes.STRING_FORMATTING) actual_type = self.named_type('builtins.str') # Perform the checks for given types. if expected_type is None: continue a_type = get_proper_type(actual_type) actual_items = (get_proper_types(a_type.items) if isinstance(a_type, UnionType) else [a_type]) for a_type in actual_items: if custom_special_method(a_type, '__format__'): continue self.check_placeholder_type(a_type, expected_type, call) self.perform_special_format_checks(spec, call, repl, a_type, expected_type)