Check % string interpolation with positional specifiers '%s, %d' % ('yes, 42').
(
self,
specifiers: list[ConversionSpecifier],
replacements: Expression,
expr: FormatStringExpr,
)
| 712 | return has_key |
| 713 | |
| 714 | def check_simple_str_interpolation( |
| 715 | self, |
| 716 | specifiers: list[ConversionSpecifier], |
| 717 | replacements: Expression, |
| 718 | expr: FormatStringExpr, |
| 719 | ) -> None: |
| 720 | """Check % string interpolation with positional specifiers '%s, %d' % ('yes, 42').""" |
| 721 | checkers = self.build_replacement_checkers(specifiers, replacements, expr) |
| 722 | if checkers is None: |
| 723 | return |
| 724 | |
| 725 | rhs_type = get_proper_type(self.accept(replacements)) |
| 726 | rep_types: list[Type] = [] |
| 727 | if isinstance(rhs_type, TupleType): |
| 728 | rep_types = rhs_type.items |
| 729 | unpack_index = find_unpack_in_list(rep_types) |
| 730 | if unpack_index is not None: |
| 731 | # TODO: we should probably warn about potentially short tuple. |
| 732 | # However, without special-casing for tuple(f(i) for in other_tuple) |
| 733 | # this causes false positive on mypy self-check in report.py. |
| 734 | extras = max(0, len(checkers) - len(rep_types) + 1) |
| 735 | unpacked = rep_types[unpack_index] |
| 736 | assert isinstance(unpacked, UnpackType) |
| 737 | unpacked = get_proper_type(unpacked.type) |
| 738 | if isinstance(unpacked, TypeVarTupleType): |
| 739 | unpacked = get_proper_type(unpacked.upper_bound) |
| 740 | assert ( |
| 741 | isinstance(unpacked, Instance) and unpacked.type.fullname == "builtins.tuple" |
| 742 | ) |
| 743 | unpack_items = [unpacked.args[0]] * extras |
| 744 | rep_types = rep_types[:unpack_index] + unpack_items + rep_types[unpack_index + 1 :] |
| 745 | elif isinstance(rhs_type, AnyType): |
| 746 | return |
| 747 | elif isinstance(rhs_type, Instance) and rhs_type.type.fullname == "builtins.tuple": |
| 748 | # Assume that an arbitrary-length tuple has the right number of items. |
| 749 | rep_types = [rhs_type.args[0]] * len(checkers) |
| 750 | elif isinstance(rhs_type, UnionType): |
| 751 | for typ in rhs_type.relevant_items(): |
| 752 | temp_node = TempNode(typ) |
| 753 | temp_node.line = replacements.line |
| 754 | self.check_simple_str_interpolation(specifiers, temp_node, expr) |
| 755 | return |
| 756 | else: |
| 757 | rep_types = [rhs_type] |
| 758 | |
| 759 | if len(checkers) > len(rep_types): |
| 760 | # Only check the fix-length Tuple type. Other Iterable types would skip. |
| 761 | if is_subtype(rhs_type, self.chk.named_type("typing.Iterable")) and not isinstance( |
| 762 | rhs_type, TupleType |
| 763 | ): |
| 764 | return |
| 765 | else: |
| 766 | self.msg.too_few_string_formatting_arguments(replacements) |
| 767 | elif len(checkers) < len(rep_types): |
| 768 | self.msg.too_many_string_formatting_arguments(replacements) |
| 769 | else: |
| 770 | if len(checkers) == 1: |
| 771 | check_node, check_type = checkers[0] |
no test coverage detected