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)