forked from python/mypy
-
Notifications
You must be signed in to change notification settings - Fork 0
/
messages.py
2122 lines (1857 loc) · 95.6 KB
/
messages.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
"""Facilities for generating error messages during type checking.
Don't add any non-trivial message construction logic to the type
checker, as it can compromise clarity and make messages less
consistent. Add such logic to this module instead. Literal messages, including those
with format args, should be defined as constants in mypy.message_registry.
Historically we tried to avoid all message string literals in the type
checker but we are moving away from this convention.
"""
from mypy.ordered_dict import OrderedDict
import re
import difflib
from textwrap import dedent
from typing import cast, List, Dict, Any, Sequence, Iterable, Tuple, Set, Optional, Union
from typing_extensions import Final
from mypy.erasetype import erase_type
from mypy.errors import Errors
from mypy.types import (
Type, CallableType, Instance, TypeVarType, TupleType, TypedDictType, LiteralType,
UnionType, NoneType, AnyType, Overloaded, FunctionLike, DeletedType, TypeType, TypeVarDef,
UninhabitedType, TypeOfAny, UnboundType, PartialType, get_proper_type, ProperType,
get_proper_types
)
from mypy.typetraverser import TypeTraverserVisitor
from mypy.nodes import (
TypeInfo, Context, MypyFile, op_methods, op_methods_to_symbols,
FuncDef, reverse_builtin_aliases,
ARG_POS, ARG_OPT, ARG_NAMED, ARG_NAMED_OPT, ARG_STAR, ARG_STAR2,
ReturnStmt, NameExpr, Var, CONTRAVARIANT, COVARIANT, SymbolNode,
CallExpr, SymbolTable, TempNode
)
from mypy.subtypes import (
is_subtype, find_member, get_member_flags,
IS_SETTABLE, IS_CLASSVAR, IS_CLASS_OR_STATIC,
)
from mypy.sametypes import is_same_type
from mypy.util import unmangle
from mypy.errorcodes import ErrorCode
from mypy import message_registry, errorcodes as codes
TYPES_FOR_UNIMPORTED_HINTS = {
'typing.Any',
'typing.Callable',
'typing.Dict',
'typing.Iterable',
'typing.Iterator',
'typing.List',
'typing.Optional',
'typing.Set',
'typing.Tuple',
'typing.TypeVar',
'typing.Union',
'typing.cast',
} # type: Final
ARG_CONSTRUCTOR_NAMES = {
ARG_POS: "Arg",
ARG_OPT: "DefaultArg",
ARG_NAMED: "NamedArg",
ARG_NAMED_OPT: "DefaultNamedArg",
ARG_STAR: "VarArg",
ARG_STAR2: "KwArg",
} # type: Final
# Map from the full name of a missing definition to the test fixture (under
# test-data/unit/fixtures/) that provides the definition. This is used for
# generating better error messages when running mypy tests only.
SUGGESTED_TEST_FIXTURES = {
'builtins.list': 'list.pyi',
'builtins.dict': 'dict.pyi',
'builtins.set': 'set.pyi',
'builtins.tuple': 'tuple.pyi',
'builtins.bool': 'bool.pyi',
'builtins.Exception': 'exception.pyi',
'builtins.BaseException': 'exception.pyi',
'builtins.isinstance': 'isinstancelist.pyi',
'builtins.property': 'property.pyi',
'builtins.classmethod': 'classmethod.pyi',
} # type: Final
class MessageBuilder:
"""Helper class for reporting type checker error messages with parameters.
The methods of this class need to be provided with the context within a
file; the errors member manages the wider context.
IDEA: Support a 'verbose mode' that includes full information about types
in error messages and that may otherwise produce more detailed error
messages.
"""
# Report errors using this instance. It knows about the current file and
# import context.
errors = None # type: Errors
modules = None # type: Dict[str, MypyFile]
# Number of times errors have been disabled.
disable_count = 0
# Hack to deduplicate error messages from union types
disable_type_names = 0
def __init__(self, errors: Errors, modules: Dict[str, MypyFile]) -> None:
self.errors = errors
self.modules = modules
self.disable_count = 0
self.disable_type_names = 0
#
# Helpers
#
def copy(self) -> 'MessageBuilder':
new = MessageBuilder(self.errors.copy(), self.modules)
new.disable_count = self.disable_count
new.disable_type_names = self.disable_type_names
return new
def clean_copy(self) -> 'MessageBuilder':
errors = self.errors.copy()
errors.error_info_map = OrderedDict()
return MessageBuilder(errors, self.modules)
def add_errors(self, messages: 'MessageBuilder') -> None:
"""Add errors in messages to this builder."""
if self.disable_count <= 0:
for errs in messages.errors.error_info_map.values():
for info in errs:
self.errors.add_error_info(info)
def disable_errors(self) -> None:
self.disable_count += 1
def enable_errors(self) -> None:
self.disable_count -= 1
def is_errors(self) -> bool:
return self.errors.is_errors()
def most_recent_context(self) -> Context:
"""Return a dummy context matching the most recent generated error in current file."""
line, column = self.errors.most_recent_error_location()
node = TempNode(NoneType())
node.line = line
node.column = column
return node
def report(self,
msg: str,
context: Optional[Context],
severity: str,
*,
code: Optional[ErrorCode] = None,
file: Optional[str] = None,
origin: Optional[Context] = None,
offset: int = 0) -> None:
"""Report an error or note (unless disabled)."""
if origin is not None:
end_line = origin.end_line
elif context is not None:
end_line = context.end_line
else:
end_line = None
if self.disable_count <= 0:
self.errors.report(context.get_line() if context else -1,
context.get_column() if context else -1,
msg, severity=severity, file=file, offset=offset,
origin_line=origin.get_line() if origin else None,
end_line=end_line,
code=code)
def fail(self,
msg: str,
context: Optional[Context],
*,
code: Optional[ErrorCode] = None,
file: Optional[str] = None,
origin: Optional[Context] = None) -> None:
"""Report an error message (unless disabled)."""
self.report(msg, context, 'error', code=code, file=file, origin=origin)
def note(self,
msg: str,
context: Context,
file: Optional[str] = None,
origin: Optional[Context] = None,
offset: int = 0,
*,
code: Optional[ErrorCode] = None) -> None:
"""Report a note (unless disabled)."""
self.report(msg, context, 'note', file=file, origin=origin,
offset=offset, code=code)
def note_multiline(self, messages: str, context: Context, file: Optional[str] = None,
origin: Optional[Context] = None, offset: int = 0,
code: Optional[ErrorCode] = None) -> None:
"""Report as many notes as lines in the message (unless disabled)."""
for msg in messages.splitlines():
self.report(msg, context, 'note', file=file, origin=origin,
offset=offset, code=code)
#
# Specific operations
#
# The following operations are for generating specific error messages. They
# get some information as arguments, and they build an error message based
# on them.
def has_no_attr(self,
original_type: Type,
typ: Type,
member: str,
context: Context,
module_symbol_table: Optional[SymbolTable] = None) -> Type:
"""Report a missing or non-accessible member.
original_type is the top-level type on which the error occurred.
typ is the actual type that is missing the member. These can be
different, e.g., in a union, original_type will be the union and typ
will be the specific item in the union that does not have the member
attribute.
'module_symbol_table' is passed to this function if the type for which we
are trying to get a member was originally a module. The SymbolTable allows
us to look up and suggests attributes of the module since they are not
directly available on original_type
If member corresponds to an operator, use the corresponding operator
name in the messages. Return type Any.
"""
original_type = get_proper_type(original_type)
typ = get_proper_type(typ)
if (isinstance(original_type, Instance) and
original_type.type.has_readable_member(member)):
self.fail('Member "{}" is not assignable'.format(member), context)
elif member == '__contains__':
self.fail('Unsupported right operand type for in ({})'.format(
format_type(original_type)), context, code=codes.OPERATOR)
elif member in op_methods.values():
# Access to a binary operator member (e.g. _add). This case does
# not handle indexing operations.
for op, method in op_methods.items():
if method == member:
self.unsupported_left_operand(op, original_type, context)
break
elif member == '__neg__':
self.fail('Unsupported operand type for unary - ({})'.format(
format_type(original_type)), context, code=codes.OPERATOR)
elif member == '__pos__':
self.fail('Unsupported operand type for unary + ({})'.format(
format_type(original_type)), context, code=codes.OPERATOR)
elif member == '__invert__':
self.fail('Unsupported operand type for ~ ({})'.format(
format_type(original_type)), context, code=codes.OPERATOR)
elif member == '__getitem__':
# Indexed get.
# TODO: Fix this consistently in format_type
if isinstance(original_type, CallableType) and original_type.is_type_obj():
self.fail('The type {} is not generic and not indexable'.format(
format_type(original_type)), context)
else:
self.fail('Value of type {} is not indexable'.format(
format_type(original_type)), context, code=codes.INDEX)
elif member == '__setitem__':
# Indexed set.
self.fail('Unsupported target for indexed assignment ({})'.format(
format_type(original_type)), context, code=codes.INDEX)
elif member == '__call__':
if isinstance(original_type, Instance) and \
(original_type.type.fullname == 'builtins.function'):
# "'function' not callable" is a confusing error message.
# Explain that the problem is that the type of the function is not known.
self.fail('Cannot call function of unknown type', context, code=codes.OPERATOR)
else:
self.fail('{} not callable'.format(format_type(original_type)), context,
code=codes.OPERATOR)
else:
# The non-special case: a missing ordinary attribute.
extra = ''
if member == '__iter__':
extra = ' (not iterable)'
elif member == '__aiter__':
extra = ' (not async iterable)'
if not self.disable_type_names:
failed = False
if isinstance(original_type, Instance) and original_type.type.names:
alternatives = set(original_type.type.names.keys())
if module_symbol_table is not None:
alternatives |= {key for key in module_symbol_table.keys()}
# in some situations, the member is in the alternatives set
# but since we're in this function, we shouldn't suggest it
if member in alternatives:
alternatives.remove(member)
matches = [m for m in COMMON_MISTAKES.get(member, []) if m in alternatives]
matches.extend(best_matches(member, alternatives)[:3])
if member == '__aiter__' and matches == ['__iter__']:
matches = [] # Avoid misleading suggestion
if member == '__div__' and matches == ['__truediv__']:
# TODO: Handle differences in division between Python 2 and 3 more cleanly
matches = []
if matches:
self.fail(
'{} has no attribute "{}"; maybe {}?{}'.format(
format_type(original_type),
member,
pretty_seq(matches, "or"),
extra,
),
context,
code=codes.ATTR_DEFINED)
failed = True
if not failed:
self.fail(
'{} has no attribute "{}"{}'.format(
format_type(original_type), member, extra),
context,
code=codes.ATTR_DEFINED)
elif isinstance(original_type, UnionType):
# The checker passes "object" in lieu of "None" for attribute
# checks, so we manually convert it back.
typ_format, orig_type_format = format_type_distinctly(typ, original_type)
if typ_format == '"object"' and \
any(type(item) == NoneType for item in original_type.items):
typ_format = '"None"'
self.fail('Item {} of {} has no attribute "{}"{}'.format(
typ_format, orig_type_format, member, extra), context,
code=codes.UNION_ATTR)
return AnyType(TypeOfAny.from_error)
def unsupported_operand_types(self,
op: str,
left_type: Any,
right_type: Any,
context: Context,
*,
code: ErrorCode = codes.OPERATOR) -> None:
"""Report unsupported operand types for a binary operation.
Types can be Type objects or strings.
"""
left_str = ''
if isinstance(left_type, str):
left_str = left_type
else:
left_str = format_type(left_type)
right_str = ''
if isinstance(right_type, str):
right_str = right_type
else:
right_str = format_type(right_type)
if self.disable_type_names:
msg = 'Unsupported operand types for {} (likely involving Union)'.format(op)
else:
msg = 'Unsupported operand types for {} ({} and {})'.format(
op, left_str, right_str)
self.fail(msg, context, code=code)
def unsupported_left_operand(self, op: str, typ: Type,
context: Context) -> None:
if self.disable_type_names:
msg = 'Unsupported left operand type for {} (some union)'.format(op)
else:
msg = 'Unsupported left operand type for {} ({})'.format(
op, format_type(typ))
self.fail(msg, context, code=codes.OPERATOR)
def not_callable(self, typ: Type, context: Context) -> Type:
self.fail('{} not callable'.format(format_type(typ)), context)
return AnyType(TypeOfAny.from_error)
def untyped_function_call(self, callee: CallableType, context: Context) -> Type:
name = callable_name(callee) or '(unknown)'
self.fail('Call to untyped function {} in typed context'.format(name), context,
code=codes.NO_UNTYPED_CALL)
return AnyType(TypeOfAny.from_error)
def incompatible_argument(self,
n: int,
m: int,
callee: CallableType,
arg_type: Type,
arg_kind: int,
context: Context,
outer_context: Context) -> Optional[ErrorCode]:
"""Report an error about an incompatible argument type.
The argument type is arg_type, argument number is n and the
callee type is 'callee'. If the callee represents a method
that corresponds to an operator, use the corresponding
operator name in the messages.
Return the error code that used for the argument (multiple error
codes are possible).
"""
arg_type = get_proper_type(arg_type)
target = ''
callee_name = callable_name(callee)
if callee_name is not None:
name = callee_name
if callee.bound_args and callee.bound_args[0] is not None:
base = format_type(callee.bound_args[0])
else:
base = extract_type(name)
for method, op in op_methods_to_symbols.items():
for variant in method, '__r' + method[2:]:
# FIX: do not rely on textual formatting
if name.startswith('"{}" of'.format(variant)):
if op == 'in' or variant != method:
# Reversed order of base/argument.
self.unsupported_operand_types(op, arg_type, base,
context, code=codes.OPERATOR)
else:
self.unsupported_operand_types(op, base, arg_type,
context, code=codes.OPERATOR)
return codes.OPERATOR
if name.startswith('"__cmp__" of'):
self.unsupported_operand_types("comparison", arg_type, base,
context, code=codes.OPERATOR)
return codes.INDEX
if name.startswith('"__getitem__" of'):
self.invalid_index_type(arg_type, callee.arg_types[n - 1], base, context,
code=codes.INDEX)
return codes.INDEX
if name.startswith('"__setitem__" of'):
if n == 1:
self.invalid_index_type(arg_type, callee.arg_types[n - 1], base, context,
code=codes.INDEX)
return codes.INDEX
else:
msg = '{} (expression has type {}, target has type {})'
arg_type_str, callee_type_str = format_type_distinctly(arg_type,
callee.arg_types[n - 1])
self.fail(msg.format(message_registry.INCOMPATIBLE_TYPES_IN_ASSIGNMENT,
arg_type_str, callee_type_str),
context, code=codes.ASSIGNMENT)
return codes.ASSIGNMENT
target = 'to {} '.format(name)
msg = ''
code = codes.MISC
notes = [] # type: List[str]
if callee_name == '<list>':
name = callee_name[1:-1]
n -= 1
actual_type_str, expected_type_str = format_type_distinctly(arg_type,
callee.arg_types[0])
msg = '{} item {} has incompatible type {}; expected {}'.format(
name.title(), n, actual_type_str, expected_type_str)
code = codes.LIST_ITEM
elif callee_name == '<dict>':
name = callee_name[1:-1]
n -= 1
key_type, value_type = cast(TupleType, arg_type).items
expected_key_type, expected_value_type = cast(TupleType, callee.arg_types[0]).items
# don't increase verbosity unless there is need to do so
if is_subtype(key_type, expected_key_type):
key_type_str = format_type(key_type)
expected_key_type_str = format_type(expected_key_type)
else:
key_type_str, expected_key_type_str = format_type_distinctly(
key_type, expected_key_type)
if is_subtype(value_type, expected_value_type):
value_type_str = format_type(value_type)
expected_value_type_str = format_type(expected_value_type)
else:
value_type_str, expected_value_type_str = format_type_distinctly(
value_type, expected_value_type)
msg = '{} entry {} has incompatible type {}: {}; expected {}: {}'.format(
name.title(), n, key_type_str, value_type_str,
expected_key_type_str, expected_value_type_str)
code = codes.DICT_ITEM
elif callee_name == '<list-comprehension>':
actual_type_str, expected_type_str = map(strip_quotes,
format_type_distinctly(arg_type,
callee.arg_types[0]))
msg = 'List comprehension has incompatible type List[{}]; expected List[{}]'.format(
actual_type_str, expected_type_str)
elif callee_name == '<set-comprehension>':
actual_type_str, expected_type_str = map(strip_quotes,
format_type_distinctly(arg_type,
callee.arg_types[0]))
msg = 'Set comprehension has incompatible type Set[{}]; expected Set[{}]'.format(
actual_type_str, expected_type_str)
elif callee_name == '<dictionary-comprehension>':
actual_type_str, expected_type_str = format_type_distinctly(arg_type,
callee.arg_types[n - 1])
msg = ('{} expression in dictionary comprehension has incompatible type {}; '
'expected type {}').format(
'Key' if n == 1 else 'Value',
actual_type_str,
expected_type_str)
elif callee_name == '<generator>':
actual_type_str, expected_type_str = format_type_distinctly(arg_type,
callee.arg_types[0])
msg = 'Generator has incompatible item type {}; expected {}'.format(
actual_type_str, expected_type_str)
else:
try:
expected_type = callee.arg_types[m - 1]
except IndexError: # Varargs callees
expected_type = callee.arg_types[-1]
arg_type_str, expected_type_str = format_type_distinctly(
arg_type, expected_type, bare=True)
if arg_kind == ARG_STAR:
arg_type_str = '*' + arg_type_str
elif arg_kind == ARG_STAR2:
arg_type_str = '**' + arg_type_str
# For function calls with keyword arguments, display the argument name rather than the
# number.
arg_label = str(n)
if isinstance(outer_context, CallExpr) and len(outer_context.arg_names) >= n:
arg_name = outer_context.arg_names[n - 1]
if arg_name is not None:
arg_label = '"{}"'.format(arg_name)
if (arg_kind == ARG_STAR2
and isinstance(arg_type, TypedDictType)
and m <= len(callee.arg_names)
and callee.arg_names[m - 1] is not None
and callee.arg_kinds[m - 1] != ARG_STAR2):
arg_name = callee.arg_names[m - 1]
assert arg_name is not None
arg_type_str, expected_type_str = format_type_distinctly(
arg_type.items[arg_name],
expected_type,
bare=True)
arg_label = '"{}"'.format(arg_name)
msg = 'Argument {} {}has incompatible type {}; expected {}'.format(
arg_label, target, quote_type_string(arg_type_str),
quote_type_string(expected_type_str))
code = codes.ARG_TYPE
expected_type = get_proper_type(expected_type)
if isinstance(expected_type, UnionType):
expected_types = list(expected_type.items)
else:
expected_types = [expected_type]
for type in get_proper_types(expected_types):
if isinstance(arg_type, Instance) and isinstance(type, Instance):
notes = append_invariance_notes(notes, arg_type, type)
self.fail(msg, context, code=code)
if notes:
for note_msg in notes:
self.note(note_msg, context, code=code)
return code
def incompatible_argument_note(self,
original_caller_type: ProperType,
callee_type: ProperType,
context: Context,
code: Optional[ErrorCode]) -> None:
if (isinstance(original_caller_type, (Instance, TupleType, TypedDictType)) and
isinstance(callee_type, Instance) and callee_type.type.is_protocol):
self.report_protocol_problems(original_caller_type, callee_type, context, code=code)
if (isinstance(callee_type, CallableType) and
isinstance(original_caller_type, Instance)):
call = find_member('__call__', original_caller_type, original_caller_type,
is_operator=True)
if call:
self.note_call(original_caller_type, call, context, code=code)
def invalid_index_type(self, index_type: Type, expected_type: Type, base_str: str,
context: Context, *, code: ErrorCode) -> None:
index_str, expected_str = format_type_distinctly(index_type, expected_type)
self.fail('Invalid index type {} for {}; expected type {}'.format(
index_str, base_str, expected_str), context, code=code)
def too_few_arguments(self, callee: CallableType, context: Context,
argument_names: Optional[Sequence[Optional[str]]]) -> None:
if argument_names is not None:
num_positional_args = sum(k is None for k in argument_names)
arguments_left = callee.arg_names[num_positional_args:callee.min_args]
diff = [k for k in arguments_left if k not in argument_names]
if len(diff) == 1:
msg = 'Missing positional argument'
else:
msg = 'Missing positional arguments'
callee_name = callable_name(callee)
if callee_name is not None and diff and all(d is not None for d in diff):
args = '", "'.join(cast(List[str], diff))
msg += ' "{}" in call to {}'.format(args, callee_name)
else:
msg = 'Too few arguments' + for_function(callee)
else:
msg = 'Too few arguments' + for_function(callee)
self.fail(msg, context, code=codes.CALL_ARG)
def missing_named_argument(self, callee: CallableType, context: Context, name: str) -> None:
msg = 'Missing named argument "{}"'.format(name) + for_function(callee)
self.fail(msg, context, code=codes.CALL_ARG)
def too_many_arguments(self, callee: CallableType, context: Context) -> None:
msg = 'Too many arguments' + for_function(callee)
self.fail(msg, context, code=codes.CALL_ARG)
def too_many_arguments_from_typed_dict(self,
callee: CallableType,
arg_type: TypedDictType,
context: Context) -> None:
# Try to determine the name of the extra argument.
for key in arg_type.items:
if key not in callee.arg_names:
msg = 'Extra argument "{}" from **args'.format(key) + for_function(callee)
break
else:
self.too_many_arguments(callee, context)
return
self.fail(msg, context)
def too_many_positional_arguments(self, callee: CallableType,
context: Context) -> None:
msg = 'Too many positional arguments' + for_function(callee)
self.fail(msg, context)
def unexpected_keyword_argument(self, callee: CallableType, name: str, arg_type: Type,
context: Context) -> None:
msg = 'Unexpected keyword argument "{}"'.format(name) + for_function(callee)
# Suggest intended keyword, look for type match else fallback on any match.
matching_type_args = []
not_matching_type_args = []
for i, kwarg_type in enumerate(callee.arg_types):
callee_arg_name = callee.arg_names[i]
if callee_arg_name is not None and callee.arg_kinds[i] != ARG_STAR:
if is_subtype(arg_type, kwarg_type):
matching_type_args.append(callee_arg_name)
else:
not_matching_type_args.append(callee_arg_name)
matches = best_matches(name, matching_type_args)
if not matches:
matches = best_matches(name, not_matching_type_args)
if matches:
msg += "; did you mean {}?".format(pretty_seq(matches[:3], "or"))
self.fail(msg, context, code=codes.CALL_ARG)
module = find_defining_module(self.modules, callee)
if module:
assert callee.definition is not None
fname = callable_name(callee)
if not fname: # an alias to function with a different name
fname = 'Called function'
self.note('{} defined here'.format(fname), callee.definition,
file=module.path, origin=context, code=codes.CALL_ARG)
def duplicate_argument_value(self, callee: CallableType, index: int,
context: Context) -> None:
self.fail('{} gets multiple values for keyword argument "{}"'.
format(callable_name(callee) or 'Function', callee.arg_names[index]),
context)
def does_not_return_value(self, callee_type: Optional[Type], context: Context) -> None:
"""Report an error about use of an unusable type."""
name = None # type: Optional[str]
callee_type = get_proper_type(callee_type)
if isinstance(callee_type, FunctionLike):
name = callable_name(callee_type)
if name is not None:
self.fail('{} does not return a value'.format(capitalize(name)), context,
code=codes.FUNC_RETURNS_VALUE)
else:
self.fail('Function does not return a value', context, code=codes.FUNC_RETURNS_VALUE)
def deleted_as_rvalue(self, typ: DeletedType, context: Context) -> None:
"""Report an error about using an deleted type as an rvalue."""
if typ.source is None:
s = ""
else:
s = " '{}'".format(typ.source)
self.fail('Trying to read deleted variable{}'.format(s), context)
def deleted_as_lvalue(self, typ: DeletedType, context: Context) -> None:
"""Report an error about using an deleted type as an lvalue.
Currently, this only occurs when trying to assign to an
exception variable outside the local except: blocks.
"""
if typ.source is None:
s = ""
else:
s = " '{}'".format(typ.source)
self.fail('Assignment to variable{} outside except: block'.format(s), context)
def no_variant_matches_arguments(self,
plausible_targets: List[CallableType],
overload: Overloaded,
arg_types: List[Type],
context: Context,
*,
code: Optional[ErrorCode] = None) -> None:
code = code or codes.CALL_OVERLOAD
name = callable_name(overload)
if name:
name_str = ' of {}'.format(name)
else:
name_str = ''
arg_types_str = ', '.join(format_type(arg) for arg in arg_types)
num_args = len(arg_types)
if num_args == 0:
self.fail('All overload variants{} require at least one argument'.format(name_str),
context, code=code)
elif num_args == 1:
self.fail('No overload variant{} matches argument type {}'
.format(name_str, arg_types_str), context, code=code)
else:
self.fail('No overload variant{} matches argument types {}'
.format(name_str, arg_types_str), context, code=code)
self.pretty_overload_matches(plausible_targets, overload, context, offset=2, max_items=2,
code=code)
def wrong_number_values_to_unpack(self, provided: int, expected: int,
context: Context) -> None:
if provided < expected:
if provided == 1:
self.fail('Need more than 1 value to unpack ({} expected)'.format(expected),
context)
else:
self.fail('Need more than {} values to unpack ({} expected)'.format(
provided, expected), context)
elif provided > expected:
self.fail('Too many values to unpack ({} expected, {} provided)'.format(
expected, provided), context)
def unpacking_strings_disallowed(self, context: Context) -> None:
self.fail("Unpacking a string is disallowed", context)
def type_not_iterable(self, type: Type, context: Context) -> None:
self.fail('\'{}\' object is not iterable'.format(type), context)
def incompatible_operator_assignment(self, op: str,
context: Context) -> None:
self.fail('Result type of {} incompatible in assignment'.format(op),
context)
def overload_signature_incompatible_with_supertype(
self, name: str, name_in_super: str, supertype: str,
overload: Overloaded, context: Context) -> None:
target = self.override_target(name, name_in_super, supertype)
self.fail('Signature of "{}" incompatible with {}'.format(
name, target), context, code=codes.OVERRIDE)
note_template = 'Overload variants must be defined in the same order as they are in "{}"'
self.note(note_template.format(supertype), context, code=codes.OVERRIDE)
def signature_incompatible_with_supertype(
self, name: str, name_in_super: str, supertype: str,
context: Context) -> None:
target = self.override_target(name, name_in_super, supertype)
self.fail('Signature of "{}" incompatible with {}'.format(
name, target), context, code=codes.OVERRIDE)
def argument_incompatible_with_supertype(
self, arg_num: int, name: str, type_name: Optional[str],
name_in_supertype: str, arg_type_in_supertype: Type, supertype: str,
context: Context) -> None:
target = self.override_target(name, name_in_supertype, supertype)
arg_type_in_supertype_f = format_type_bare(arg_type_in_supertype)
self.fail('Argument {} of "{}" is incompatible with {}; '
'supertype defines the argument type as "{}"'
.format(arg_num, name, target, arg_type_in_supertype_f),
context,
code=codes.OVERRIDE)
self.note(
'This violates the Liskov substitution principle',
context,
code=codes.OVERRIDE)
self.note(
'See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides',
context,
code=codes.OVERRIDE)
if name == "__eq__" and type_name:
multiline_msg = self.comparison_method_example_msg(class_name=type_name)
self.note_multiline(multiline_msg, context, code=codes.OVERRIDE)
def comparison_method_example_msg(self, class_name: str) -> str:
return dedent('''\
It is recommended for "__eq__" to work with arbitrary objects, for example:
def __eq__(self, other: object) -> bool:
if not isinstance(other, {class_name}):
return NotImplemented
return <logic to compare two {class_name} instances>
'''.format(class_name=class_name))
def return_type_incompatible_with_supertype(
self, name: str, name_in_supertype: str, supertype: str,
original: Type, override: Type,
context: Context) -> None:
target = self.override_target(name, name_in_supertype, supertype)
override_str, original_str = format_type_distinctly(override, original)
self.fail('Return type {} of "{}" incompatible with return type {} in {}'
.format(override_str, name, original_str, target),
context,
code=codes.OVERRIDE)
def override_target(self, name: str, name_in_super: str,
supertype: str) -> str:
target = 'supertype "{}"'.format(supertype)
if name_in_super != name:
target = '"{}" of {}'.format(name_in_super, target)
return target
def incompatible_type_application(self, expected_arg_count: int,
actual_arg_count: int,
context: Context) -> None:
if expected_arg_count == 0:
self.fail('Type application targets a non-generic function or class',
context)
elif actual_arg_count > expected_arg_count:
self.fail('Type application has too many types ({} expected)'
.format(expected_arg_count), context)
else:
self.fail('Type application has too few types ({} expected)'
.format(expected_arg_count), context)
def could_not_infer_type_arguments(self, callee_type: CallableType, n: int,
context: Context) -> None:
callee_name = callable_name(callee_type)
if callee_name is not None and n > 0:
self.fail('Cannot infer type argument {} of {}'.format(n, callee_name), context)
else:
self.fail('Cannot infer function type argument', context)
def invalid_var_arg(self, typ: Type, context: Context) -> None:
self.fail('List or tuple expected as variable arguments', context)
def invalid_keyword_var_arg(self, typ: Type, is_mapping: bool, context: Context) -> None:
typ = get_proper_type(typ)
if isinstance(typ, Instance) and is_mapping:
self.fail('Keywords must be strings', context)
else:
suffix = ''
if isinstance(typ, Instance):
suffix = ', not {}'.format(format_type(typ))
self.fail(
'Argument after ** must be a mapping{}'.format(suffix),
context, code=codes.ARG_TYPE)
def undefined_in_superclass(self, member: str, context: Context) -> None:
self.fail('"{}" undefined in superclass'.format(member), context)
def first_argument_for_super_must_be_type(self, actual: Type, context: Context) -> None:
actual = get_proper_type(actual)
if isinstance(actual, Instance):
# Don't include type of instance, because it can look confusingly like a type
# object.
type_str = 'a non-type instance'
else:
type_str = format_type(actual)
self.fail('Argument 1 for "super" must be a type object; got {}'.format(type_str), context,
code=codes.ARG_TYPE)
def too_few_string_formatting_arguments(self, context: Context) -> None:
self.fail('Not enough arguments for format string', context,
code=codes.STRING_FORMATTING)
def too_many_string_formatting_arguments(self, context: Context) -> None:
self.fail('Not all arguments converted during string formatting', context,
code=codes.STRING_FORMATTING)
def unsupported_placeholder(self, placeholder: str, context: Context) -> None:
self.fail('Unsupported format character \'%s\'' % placeholder, context,
code=codes.STRING_FORMATTING)
def string_interpolation_with_star_and_key(self, context: Context) -> None:
self.fail('String interpolation contains both stars and mapping keys', context,
code=codes.STRING_FORMATTING)
def requires_int_or_char(self, context: Context,
format_call: bool = False) -> None:
self.fail('"{}c" requires int or char'.format(':' if format_call else '%'),
context, code=codes.STRING_FORMATTING)
def key_not_in_mapping(self, key: str, context: Context) -> None:
self.fail('Key \'%s\' not found in mapping' % key, context,
code=codes.STRING_FORMATTING)
def string_interpolation_mixing_key_and_non_keys(self, context: Context) -> None:
self.fail('String interpolation mixes specifier with and without mapping keys', context,
code=codes.STRING_FORMATTING)
def cannot_determine_type(self, name: str, context: Context) -> None:
self.fail("Cannot determine type of '%s'" % name, context, code=codes.HAS_TYPE)
def cannot_determine_type_in_base(self, name: str, base: str, context: Context) -> None:
self.fail("Cannot determine type of '%s' in base class '%s'" % (name, base), context)
def no_formal_self(self, name: str, item: CallableType, context: Context) -> None:
self.fail('Attribute function "%s" with type %s does not accept self argument'
% (name, format_type(item)), context)
def incompatible_self_argument(self, name: str, arg: Type, sig: CallableType,
is_classmethod: bool, context: Context) -> None:
kind = 'class attribute function' if is_classmethod else 'attribute function'
self.fail('Invalid self argument %s to %s "%s" with type %s'
% (format_type(arg), kind, name, format_type(sig)), context)
def incompatible_conditional_function_def(self, defn: FuncDef) -> None:
self.fail('All conditional function variants must have identical '
'signatures', defn)
def cannot_instantiate_abstract_class(self, class_name: str,
abstract_attributes: List[str],
context: Context) -> None:
attrs = format_string_list(["'%s'" % a for a in abstract_attributes])
self.fail("Cannot instantiate abstract class '%s' with abstract "
"attribute%s %s" % (class_name, plural_s(abstract_attributes),
attrs),
context, code=codes.ABSTRACT)
def base_class_definitions_incompatible(self, name: str, base1: TypeInfo,
base2: TypeInfo,
context: Context) -> None:
self.fail('Definition of "{}" in base class "{}" is incompatible '
'with definition in base class "{}"'.format(
name, base1.name, base2.name), context)
def cant_assign_to_method(self, context: Context) -> None:
self.fail(message_registry.CANNOT_ASSIGN_TO_METHOD, context,
code=codes.ASSIGNMENT)
def cant_assign_to_classvar(self, name: str, context: Context) -> None:
self.fail('Cannot assign to class variable "%s" via instance' % name, context)
def final_cant_override_writable(self, name: str, ctx: Context) -> None:
self.fail('Cannot override writable attribute "{}" with a final one'.format(name), ctx)
def cant_override_final(self, name: str, base_name: str, ctx: Context) -> None:
self.fail('Cannot override final attribute "{}"'
' (previously declared in base class "{}")'.format(name, base_name), ctx)
def cant_assign_to_final(self, name: str, attr_assign: bool, ctx: Context) -> None:
"""Warn about a prohibited assignment to a final attribute.
Pass `attr_assign=True` if the assignment assigns to an attribute.
"""
kind = "attribute" if attr_assign else "name"
self.fail('Cannot assign to final {} "{}"'.format(kind, unmangle(name)), ctx)
def protocol_members_cant_be_final(self, ctx: Context) -> None:
self.fail("Protocol member cannot be final", ctx)
def final_without_value(self, ctx: Context) -> None:
self.fail("Final name must be initialized with a value", ctx)
def read_only_property(self, name: str, type: TypeInfo,
context: Context) -> None:
self.fail('Property "{}" defined in "{}" is read-only'.format(
name, type.name), context)
def incompatible_typevar_value(self,
callee: CallableType,
typ: Type,
typevar_name: str,
context: Context) -> None:
self.fail(message_registry.INCOMPATIBLE_TYPEVAR_VALUE
.format(typevar_name, callable_name(callee) or 'function', format_type(typ)),
context,
code=codes.TYPE_VAR)
def dangerous_comparison(self, left: Type, right: Type, kind: str, ctx: Context) -> None:
left_str = 'element' if kind == 'container' else 'left operand'
right_str = 'container item' if kind == 'container' else 'right operand'
message = 'Non-overlapping {} check ({} type: {}, {} type: {})'
left_typ, right_typ = format_type_distinctly(left, right)
self.fail(message.format(kind, left_str, left_typ, right_str, right_typ), ctx,
code=codes.COMPARISON_OVERLAP)
def overload_inconsistently_applies_decorator(self, decorator: str, context: Context) -> None:
self.fail(
'Overload does not consistently use the "@{}" '.format(decorator)
+ 'decorator on all function signatures.',
context)
def overloaded_signatures_overlap(self, index1: int, index2: int, context: Context) -> None:
self.fail('Overloaded function signatures {} and {} overlap with '
'incompatible return types'.format(index1, index2), context)