Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions mypy/checkstrformat.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,13 +151,11 @@ def check_simple_str_interpolation(self, specifiers: List[ConversionSpecifier],

def check_mapping_str_interpolation(self, specifiers: List[ConversionSpecifier],
replacements: Expression) -> None:
if (isinstance(replacements, DictExpr) and
all(isinstance(k, (StrExpr, BytesExpr))
for k, v in replacements.items)):
if isinstance(replacements, DictExpr):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is generally not right. Consider this example:

x = 'q'
'%(q)d' % {'a': 1, x: 2}   # actually okay

What about still using the all(...) check (include also unicode), but only for the key_not_in_mapping error? Item types can still be checked even if some keys are not literals -- we can assume that there are no duplicate keys.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about the case with a non-literal key? It might be pretty rare, but here's one example where it would be kind of reasonable:

# constants.py
key1 = 'key1'
key2 = 'key2'

# code.py
from constants import key1, key2
...
'%(key1)d, %(key2)d' % {key1: 1, key2: 2}

Not sure whether we are likely to encounter code like that in practice.

mapping = {} # type: Dict[str, Type]
for k, v in replacements.items:
key_str = cast(StrExpr, k).value
mapping[key_str] = self.accept(v)
if isinstance(k, (StrExpr, BytesExpr, UnicodeExpr)):
mapping[k.value] = self.accept(v)

for specifier in specifiers:
if specifier.type == '%':
Expand Down
7 changes: 7 additions & 0 deletions test-data/unit/check-expressions.test
Original file line number Diff line number Diff line change
Expand Up @@ -1083,9 +1083,16 @@ a = None # type: Any
'%(a)d' % {'a': 1, 'b': 2, 'c': 3}
'%(q)d' % {'a': 1, 'b': 2, 'c': 3} # E: Key 'q' not found in mapping
'%(a)d %%' % {'a': 1}
'%(q)d' % {'a': 1, 'b': 2, 0: 3} # E: Key 'q' not found in mapping

[builtins fixtures/dict.pyi]

[case testStringInterpolationMappingKeys_python2]
'%(q)d' % {u'a': 1, 'b': 2, 'c': 3} # E: Key 'q' not found in mapping
'%(a)d' % {u'a': 1, 'b': 2, 'c': 3}

[builtins_py2 fixtures/python2.pyi]

[case testStringInterpolationMappingDictTypes]
from typing import Any, Dict
a = None # type: Any
Expand Down
8 changes: 7 additions & 1 deletion test-data/unit/fixtures/python2.pyi
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Generic, Iterable, TypeVar
from typing import Generic, Iterable, Mapping, TypeVar

class object:
def __init__(self) -> None: pass
Expand All @@ -9,10 +9,16 @@ class type:
class function: pass

class int: pass
class float: pass
class str: pass
class unicode: pass
class tuple: pass

T = TypeVar('T')
class list(Iterable[T], Generic[T]): pass

KT = TypeVar('KT')
VT = TypeVar('VT')
class dict(Iterable[KT], Mapping[KT, VT], Generic[KT, VT]): pass

# Definition of None is implicit