/
checkexpr.py
1202 lines (1039 loc) · 49.8 KB
/
checkexpr.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
"""Expression type checker. This file is conceptually part of TypeChecker."""
from mtypes import (
Typ, Any, Callable, Overloaded, NoneTyp, Void, TypeVarDef, TypeVars,
TupleType, Instance, TypeVar, TypeTranslator, ErasedType
)
from nodes import (
NameExpr, RefExpr, Var, FuncDef, OverloadedFuncDef, TypeInfo, CallExpr,
Node, MemberExpr, IntExpr, StrExpr, BytesExpr, FloatExpr, OpExpr,
UnaryExpr, IndexExpr, CastExpr, TypeApplication, ListExpr, TupleExpr,
DictExpr, FuncExpr, SuperExpr, ParenExpr, SliceExpr, Context,
ListComprehension, GeneratorExpr
)
from nodes import function_type, method_type
import nodes
import checker
import mtypes
from sametypes import is_same_type
from replacetvars import replace_func_type_vars, replace_type_vars
from messages import MessageBuilder
import messages
from infer import infer_type_arguments, infer_function_type_arguments
from expandtype import expand_type, expand_caller_var_args
from subtypes import is_subtype
import erasetype
from checkmember import analyse_member_access
from semanal import self_type
from constraints import get_actual_type
class ExpressionChecker:
"""Expression type checker.
This clas works closely together with checker.TypeChecker.
"""
# Some services are provided by a TypeChecker instance.
chk = None
# This is shared with TypeChecker, but stored also here for convenience.
msg = None
def __init__(self, chk, msg):
"""Construct an expression type checker."""
self.chk = chk
self.msg = msg
def visit_name_expr(self, e):
"""Type check a name expression (of any kind: local, member or
global)."""
return self.analyse_ref_expr(e)
def analyse_ref_expr(self, e):
result = None
node = e.node
if isinstance(node, Var):
# Variable or constant reference.
v = node
if not v.typ:
# Implicit dynamic type.
result = Any()
else:
# Local or global variable.
result = v.typ.typ
elif isinstance(node, FuncDef):
# Reference to a global function.
f = node
result = function_type(f)
elif isinstance(node, OverloadedFuncDef):
o = node
result = o.typ.typ
elif isinstance(node, TypeInfo):
# Reference to a type object.
result = self.type_object_type(node)
else:
# Unknown reference; use dynamic type implicitly to avoid
# generating extra type errors.
result = Any()
return result
def analyse_direct_member_access(self, name, info, is_lvalue, context):
"""Analyse direct member access via a name expression
(implicit self). This can access private definitions.
"""
raise RuntimeError('Not implemented')
def visit_call_expr(self, e):
"""Type check a call expression."""
self.accept(e.callee)
# Access callee type directly, since accept may return the any type
# even if the type is known (in a dynamically typed function). This
# way we get a more precise callee in dynamically typed functions.
callee_type = self.chk.type_map[e.callee]
return self.check_call_expr_with_callee_type(callee_type, e)
def check_call_expr_with_callee_type(self, callee_type, e):
"""Type check call expression. The given callee type overrides
the type of the callee expression.
"""
return self.check_call(callee_type, e.args, e.arg_kinds, e,
e.arg_names, callable_node=e.callee)
def check_call(self, callee, args, arg_kinds, context, arg_names=None, callable_node=None):
"""Type check a call.
Also infer type arguments if the callee is a generic function.
Arguments:
callee: type of the called value
args: actual argument expressions
arg_kinds: contains nodes.ARG_* constant for each argument in args
describing whether the argument is positional, *arg, etc.
arg_names: names of arguments (optional)
callable_node: associate the inferred callable type to this node,
if specified
"""
is_var_arg = nodes.ARG_STAR in arg_kinds
if isinstance(callee, Callable):
callable = callee
formal_to_actual = map_actuals_to_formals(
arg_kinds, arg_names,
callable.arg_kinds, callable.arg_names,
lambda i: self.accept(args[i]))
if callable.is_generic():
callable = self.infer_function_type_arguments_using_context(
callable)
callable = self.infer_function_type_arguments(
callable, args, arg_kinds, formal_to_actual, context)
arg_types = self.infer_arg_types_in_context2(
callable, args, arg_kinds, formal_to_actual)
self.check_argument_count(callable, arg_types, arg_kinds,
arg_names, formal_to_actual, context)
self.check_argument_types(arg_types, arg_kinds, callable,
formal_to_actual, context)
if callable_node:
# Store the inferred callable type.
self.chk.store_type(callable_node, callable)
return callable.ret_type
elif isinstance(callee, Overloaded):
# Type check arguments in empty context. They will be checked again
# later in a context derived from the signature; these types are
# only used to pick a signature variant.
self.msg.disable_errors()
arg_types = self.infer_arg_types_in_context(None, args)
self.msg.enable_errors()
target = self.overload_call_target(arg_types, is_var_arg,
callee, context)
return self.check_call(target, args, arg_kinds, context, arg_names)
elif isinstance(callee, Any) or self.chk.is_dynamic_function():
self.infer_arg_types_in_context(None, args)
return Any()
else:
return self.msg.not_callable(callee, context)
def infer_arg_types_in_context(self, callee, args):
"""Infer argument expression types using a callable type as context.
For example, if callee argument 2 has type int[], infer the argument
expression with int[] type context.
"""
res = []
fixed = len(args)
if callee:
fixed = min(fixed, callee.max_fixed_args())
for i in range(fixed):
arg = args[i]#FIX refactor
ctx = None
if callee and i < len(callee.arg_types):
ctx = callee.arg_types[i]
res.append(self.accept(arg, ctx))
for j in range(fixed, len(args)):
if callee and callee.is_var_arg:
res.append(self.accept(args[j], callee.arg_types[-1]))
else:
res.append(self.accept(args[j]))
return res
def infer_arg_types_in_context2(self, callee, args, arg_kinds, formal_to_actual):
"""Infer argument expression types using a callable type as context.
For example, if callee argument 2 has type int[], infer the argument
exprsession with int[] type context.
Returns the inferred types of *actual arguments*.
"""
res = [None] * len(args)
for i, actuals in enumerate(formal_to_actual):
for ai in actuals:
if arg_kinds[ai] != nodes.ARG_STAR:
res[ai] = self.accept(args[ai], callee.arg_types[i])
# Fill in the rest of the argument types.
for i, t in enumerate(res):
if not t:
res[i] = self.accept(args[i])
return res
def infer_function_type_arguments_using_context(self, callable):
"""Unify callable return type to type context to infer type vars.
For example, if the return type is set<t> where 't' is a type variable
of callable, and if the context is set<int>, return callable modified
by substituting 't' with 'int'.
"""
ctx = self.chk.type_context[-1]
if not ctx:
return callable
# The return type may have references to function type variables that
# we are inferring right now. We must consider them as indeterminate
# and they are not potential results; thus we replace them with the
# None type. On the other hand, class type variables are valid results.
erased_ctx = replace_func_type_vars(ctx, ErasedType())
args = infer_type_arguments(callable.type_var_ids(), callable.ret_type,
erased_ctx, self.chk.basic_types())
# Only substite non-None and non-erased types.
new_args = []
for arg in args:
if isinstance(arg, NoneTyp) or has_erased_component(arg):
new_args.append(None)
else:
new_args.append(arg)
return self.apply_generic_arguments(callable, new_args, None)
def infer_function_type_arguments(self, callee_type, args, arg_kinds, formal_to_actual, context):
"""Infer the type arguments for a generic callee type.
Infer based on the types of arguments.
Return a derived callable type that has the arguments applied (and
stored as implicit type arguments).
"""
if not self.chk.is_dynamic_function():
# Disable type errors during type inference. There may be errors
# due to partial available context information at this time, but
# these errors can be safely ignored as the arguments will be
# inferred again later.
self.msg.disable_errors()
arg_types = self.infer_arg_types_in_context2(
callee_type, args, arg_kinds, formal_to_actual)
self.msg.enable_errors()
arg_pass_nums = self.get_arg_infer_passes(
callee_type.arg_types, formal_to_actual, len(args))
pass1_args = []
for i, arg in enumerate(arg_types):
if arg_pass_nums[i] > 1:
pass1_args.append(None)
else:
pass1_args.append(arg)
inferred_args = infer_function_type_arguments(
callee_type, pass1_args, arg_kinds, formal_to_actual,
self.chk.basic_types())
if 2 in arg_pass_nums:
# Second pass of type inference.
(callee_type,
inferred_args) = self.infer_function_type_arguments_pass2(
callee_type, args, arg_kinds, formal_to_actual,
inferred_args, context)
else:
# In dynamically typed functions use implicit 'any' types for
# type variables.
inferred_args = [Any()] * len(callee_type.variables.items)
return self.apply_inferred_arguments(callee_type, inferred_args,
context)
def infer_function_type_arguments_pass2(
self, callee_type, args, arg_kinds, formal_to_actual, inferred_args, context):
"""Perform second pass of generic function type argument inference.
The second pass is needed for arguments with types such as func<s(t)>,
where both s and t are type variables, when the actual argument is a
lambda with inferred types. The idea is to infer the type variable t
in the first pass (based on the types of other arguments). This lets
us infer the argument and return type of the lambda expression and
thus also the type variable s in this second pass.
Return (the callee with type vars applied, inferred actual arg types).
"""
# None or erased types in inferred types mean that there was not enough
# information to infer the argument. Replace them with None values so
# that they are not applied yet below.
for i, arg in enumerate(inferred_args):
if isinstance(arg, NoneTyp) or isinstance(arg, ErasedType):
inferred_args[i] = None
callee_type = self.apply_generic_arguments(
callee_type, inferred_args, context)
arg_types = self.infer_arg_types_in_context2(
callee_type, args, arg_kinds, formal_to_actual)
inferred_args = infer_function_type_arguments(
callee_type, arg_types, arg_kinds, formal_to_actual,
self.chk.basic_types())
return callee_type, inferred_args
def get_arg_infer_passes(self, arg_types, formal_to_actual, num_actuals):
"""Return pass numbers for args for two-pass argument type inference.
For each actual, the pass number is either 1 (first pass) or 2 (second
pass).
Two-pass argument type inference primarily lets us infer lambdas
better.
"""
res = [1] * num_actuals
for i, arg in enumerate(arg_types):
if arg.accept(ArgInferSecondPassQuery()):
for j in formal_to_actual[i]:
res[j] = 2
return res
def apply_inferred_arguments(self, callee_type, inferred_args, context):
"""Apply inferred values of type arguments to a generic function.
If implicit_type_vars are given, they correspond to the ids of
the implicit instance type variables; they are stored as the
prefix of inferred_args. Inferred_args contains first the
values of implicit instance type vars (if any), and then
values of function type variables, concatenated together.
"""
# Report error if some of the variables could not be solved. In that
# case assume that all variables have type dynamic to avoid extra
# bogus error messages.
for i in range(len(inferred_args)):
inferred_type = inferred_args[i]
if not inferred_type:
# Could not infer a non-trivial type for a type variable.
self.msg.could_not_infer_type_arguments(
callee_type, i + 1, context)
inferred_args = [Any()] * len(inferred_args)
# Apply the inferred types to the function type. In this case the
# return type must be Callable, since we give the right number of type
# arguments.
return self.apply_generic_arguments(callee_type,
inferred_args, None)
def check_argument_count(self, callee, actual_types, actual_kinds, actual_names, formal_to_actual, context):
"""Check that the number of arguments to a function are valid.
Also check that there are no duplicate values for arguments.
"""
formal_kinds = callee.arg_kinds
# Collect list of all actual arguments matched to formal arguments.
all_actuals = []
for actuals in formal_to_actual:
all_actuals.extend(actuals)
is_error = False # Keep track of errors to avoid duplicate errors.
for i, kind in enumerate(actual_kinds):
if i not in all_actuals and (
kind != nodes.ARG_STAR or
not is_empty_tuple(actual_types[i])):
# Extra actual: not matched by a formal argument.
if kind != nodes.ARG_NAMED:
self.msg.too_many_arguments(callee, context)
else:
self.msg.unexpected_keyword_argument(
callee, actual_names[i], context)
is_error = True
elif kind == nodes.ARG_STAR and (
nodes.ARG_STAR not in formal_kinds):
actual_type = actual_types[i]
if isinstance(actual_type, TupleType):
tuplet = actual_type
if all_actuals.count(i) < len(tuplet.items):
# Too many tuple items as some did not match.
self.msg.too_many_arguments(callee, context)
# *args can be applied even if the function takes a fixed
# number of positional arguments. This may succeed at runtime.
for i, kind in enumerate(formal_kinds):
if kind == nodes.ARG_POS and (not formal_to_actual[i] and
not is_error):
# No actual for a mandatory positional formal.
self.msg.too_few_arguments(callee, context)
elif kind in [nodes.ARG_POS, nodes.ARG_OPT,
nodes.ARG_NAMED] and is_duplicate_mapping(
formal_to_actual[i],
actual_kinds):
self.msg.duplicate_argument_value(callee, i, context)
elif (kind == nodes.ARG_NAMED and formal_to_actual[i] and
actual_kinds[formal_to_actual[i][0]] != nodes.ARG_NAMED):
# Positional argument when expecting a keyword argument.
self.msg.too_many_positional_arguments(callee, context)
def check_argument_types(self, arg_types, arg_kinds, callee, formal_to_actual, context):
"""Check argument types against a callable type.
Report errors if the argument types are not compatible.
"""
# Keep track of consumed tuple *arg items.
tuple_counter = [0]
for i, actuals in enumerate(formal_to_actual):
for actual in actuals:
arg_type = arg_types[actual]
# Check that a *arg is valid as varargs.
if (arg_kinds[actual] == nodes.ARG_STAR and
not self.is_valid_var_arg(arg_type)):
self.msg.invalid_var_arg(arg_type, context)
if (arg_kinds[actual] == nodes.ARG_STAR2 and
not self.is_valid_keyword_var_arg(arg_type)):
self.msg.invalid_keyword_var_arg(arg_type, context)
# Get the type of an inidividual actual argument (for *args
# and **args this is the item type, not the collection type).
actual_type = get_actual_type(arg_type, arg_kinds[actual],
tuple_counter)
self.check_arg(actual_type, arg_type,
callee.arg_types[i],
actual + 1, callee, context)
# There may be some remaining tuple varargs items that haven't
# been checked yet. Handle them.
if (callee.arg_kinds[i] == nodes.ARG_STAR and
arg_kinds[actual] == nodes.ARG_STAR and
isinstance(arg_types[actual], TupleType)):
tuplet = arg_types[actual]
while tuple_counter[0] < len(tuplet.items):
actual_type = get_actual_type(arg_type,
arg_kinds[actual],
tuple_counter)
self.check_arg(actual_type, arg_type,
callee.arg_types[i],
actual + 1, callee, context)
def check_arg(self, caller_type, original_caller_type, callee_type, n, callee, context):
"""Check the type of a single argument in a call."""
if isinstance(caller_type, Void):
self.msg.does_not_return_value(caller_type, context)
elif not is_subtype(caller_type, callee_type):
self.msg.incompatible_argument(n, callee, original_caller_type,
context)
def overload_call_target(self, arg_types, is_var_arg, overload, context):
"""Infer the correct overload item to call with given argument types.
The return value may be Callable or any (if an unique item
could not be determined). If is_var_arg is True, the caller
uses varargs.
"""
# TODO for overlapping signatures we should try to get a more precise
# result than 'any'
match = None # Callable, Any or None
for typ in overload.items():
if self.matches_signature(arg_types, is_var_arg, typ):
if match and (isinstance(match, Any) or
not is_same_type((match).ret_type,
typ.ret_type)):
# Ambiguous return type. Either the function overload is
# overlapping (which results in an error elsewhere) or the
# caller has provided some dynamic argument types; in
# either case can only infer the type to be any, as it is
# not an error to use any types in calls.
# TODO overlapping overloads should be possible in some
# cases
match = Any()
else:
match = typ
if not match:
self.msg.no_variant_matches_arguments(overload, context)
return Any()
else:
return match
def matches_signature(self, arg_types, is_var_arg, callee):
"""Determine whether argument types match the signature.
If is_var_arg is True, the caller uses varargs.
"""
if not is_valid_argc(len(arg_types), False, callee):
return False
if is_var_arg:
if not self.is_valid_var_arg(arg_types[-1]):
return False
arg_types, rest = expand_caller_var_args(arg_types,
callee.max_fixed_args())
# Fixed function arguments.
func_fixed = callee.max_fixed_args()
for i in range(min(len(arg_types), func_fixed)):
if not is_subtype(self.erase(arg_types[i]),
self.erase(
callee.arg_types[i])):
return False
# Function varargs.
if callee.is_var_arg:
for i in range(func_fixed, len(arg_types)):
if not is_subtype(self.erase(arg_types[i]),
self.erase(callee.arg_types[func_fixed])):
return False
return True
def apply_generic_arguments(self, callable, types, context):
"""Apply generic type arguments to a callable type.
For example, applying [int] to 'def <T> (T) -> T' results in
'def [int] (int) -> int'. Here '[int]' is an implicit bound type
variable.
Note that each type can be None; in this case, it will not be applied.
"""
tvars = callable.variables.items
if len(tvars) != len(types):
self.msg.incompatible_type_application(len(tvars), len(types),
context)
return Any()
# Create a map from type variable id to target type.
id_to_type = {}
for i, tv in enumerate(tvars):
if types[i]:
id_to_type[tv.id] = types[i]
# Apply arguments to argument types.
arg_types = [expand_type(at, id_to_type) for at in callable.arg_types]
bound_vars = [(tv.id, id_to_type[tv.id])
for tv in tvars
if tv.id in id_to_type]
# The callable may retain some type vars if only some were applied.
remaining_tvars = [tv for tv in tvars if tv.id not in id_to_type]
return Callable(arg_types,
callable.arg_kinds,
callable.arg_names,
expand_type(callable.ret_type, id_to_type),
callable.is_type_obj(),
callable.name,
TypeVars(remaining_tvars),
callable.bound_vars + bound_vars,
callable.line, callable.repr)
def apply_generic_arguments2(self, overload, types, context):
items = []
for item in overload.items():
applied = self.apply_generic_arguments(item, types, context)
if isinstance(applied, Callable):
items.append(applied)
else:
# There was an error.
return Any()
return Overloaded(items)
def visit_member_expr(self, e):
"""Visit member expression (of form e.id)."""
return self.analyse_ordinary_member_access(e, False)
def analyse_ordinary_member_access(self, e, is_lvalue):
"""Analyse member expression or member lvalue."""
if e.kind is not None:
# This is a reference to a module attribute.
return self.analyse_ref_expr(e)
else:
# This is a reference to a non-module attribute.
return analyse_member_access(e.name, self.accept(e.expr), e,
is_lvalue, False,
self.chk.tuple_type(), self.msg)
def analyse_external_member_access(self, member, base_type, context):
"""Analyse member access that is external, i.e. it cannot
refer to private definitions. Return the result type.
"""
# TODO remove; no private definitions in mypy
return analyse_member_access(member, base_type, context, False, False,
self.chk.tuple_type(), self.msg)
def visit_int_expr(self, e):
"""Type check an integer literal (trivial)."""
return self.named_type('builtins.int')
def visit_str_expr(self, e):
"""Type check a string literal (trivial)."""
return self.named_type('builtins.str')
def visit_bytes_expr(self, e):
"""Type check a bytes literal (trivial)."""
return self.named_type('builtins.bytes')
def visit_float_expr(self, e):
"""Type check a float literal (trivial)."""
return self.named_type('builtins.float')
def visit_op_expr(self, e):
"""Type check a binary operator expression."""
left_type = self.accept(e.left)
right_type = self.accept(e.right) # TODO only evaluate if needed
if e.op == 'in' or e.op == 'not in':
result = self.check_op('__contains__', right_type, e.left, e)
if e.op == 'in':
return result
else:
return self.chk.bool_type()
elif e.op in checker.op_methods:
method = checker.op_methods[e.op]
return self.check_op(method, left_type, e.right, e)
elif e.op == 'and' or e.op == 'or':
return self.check_boolean_op(e.op, left_type, right_type, e)
elif e.op == 'is' or e.op == 'is not':
return self.chk.bool_type()
else:
raise RuntimeError('Unknown operator {}'.format(e.op))
def check_op(self, method, base_type, arg, context):
"""Type check a binary operation which maps to a method call."""
if self.has_non_method(base_type, method):
self.msg.method_expected_as_operator_implementation(
base_type, method, context)
method_type = self.analyse_external_member_access(
method, base_type, context)
return self.check_call(method_type, [arg], [nodes.ARG_POS], context)
def check_boolean_op(self, op, left_type, right_type, context):
"""Type check a boolean operation ("and" or "or")."""
# Any non-void value is valid in a boolean context.
self.check_not_void(left_type, context)
self.check_not_void(right_type, context)
# TODO the result type should be the combination of left_type and
# right_type
return self.chk.bool_type()
def visit_unary_expr(self, e):
"""Type check an unary expression ("not", - or ~)."""
operand_type = self.accept(e.expr)
_x = e.op
if _x == 'not':
self.check_not_void(operand_type, e)
return self.chk.bool_type()
elif _x == '-':
method_type = self.analyse_external_member_access('__neg__',
operand_type, e)
return self.check_call(method_type, [], [], e)
elif _x == '~':
method_type = self.analyse_external_member_access('__invert__',
operand_type, e)
return self.check_call(method_type, [], [], e)
def visit_index_expr(self, e):
"""Type check an index expression (base[index])."""
left_type = self.accept(e.base)
if isinstance(left_type, TupleType):
# Special case for tuples. They support indexing only by integer
# literals.
index = self.unwrap(e.index)
if isinstance(index, IntExpr):
n = (index).value
tuple_type = left_type
if n < len(tuple_type.items):
return tuple_type.items[n]
else:
self.chk.fail(messages.TUPLE_INDEX_OUT_OF_RANGE, e)
return Any()
else:
self.chk.fail(messages.TUPLE_INDEX_MUST_BE_AN_INT_LITERAL, e)
return Any()
else:
return self.check_op('__getitem__', left_type, e.index, e)
def visit_cast_expr(self, expr):
"""Type check a cast expression."""
source_type = self.accept(expr.expr)
target_type = expr.typ
if isinstance(target_type, Any):
return Any()
else:
if not self.is_valid_cast(source_type, target_type):
self.msg.invalid_cast(target_type, source_type, expr)
return target_type
def is_valid_cast(self, source_type, target_type):
"""Is a cast from source_type to target_type valid (i.e. can succeed at
runtime)?
"""
return (is_subtype(target_type, source_type) or
is_subtype(source_type, target_type) or
(isinstance(target_type, Instance) and
(target_type).typ.is_interface) or
(isinstance(source_type, Instance) and
(source_type).typ.is_interface))
def visit_type_application(self, tapp):
"""Type check a type application (expr<...>)."""
expr_type = self.accept(tapp.expr)
if isinstance(expr_type, Callable):
new_type = self.apply_generic_arguments(expr_type,
tapp.types, tapp)
elif isinstance(expr_type, Overloaded):
overload = expr_type
# Only target items with the right number of generic type args.
items = [c for c in overload.items()
if len(c.variables.items) == len(tapp.types)]
new_type = self.apply_generic_arguments2(Overloaded(items),
tapp.types, tapp)
else:
self.chk.fail(messages.INVALID_TYPE_APPLICATION_TARGET_TYPE, tapp)
new_type = Any()
self.chk.type_map[tapp.expr] = new_type
return new_type
def visit_list_expr(self, e):
"""Type check a list expression [...] or <t> [...]."""
constructor = None
if e.typ:
# A list expression with an explicit item type; translate into type
# checking a function call.
constructor = Callable([e.typ],
[nodes.ARG_STAR],
[None],
self.chk.named_generic_type('builtins.list',
[e.typ]),
False,
'<list>')
else:
# A list expression without an explicit type; translate into type
# checking a generic function call.
tv = TypeVar('T', -1)
constructor = Callable([tv],
[nodes.ARG_STAR],
[None],
self.chk.named_generic_type('builtins.list',
[tv]),
False,
'<list>',
TypeVars([TypeVarDef('T', -1)]))
return self.check_call(constructor,
e.items,
[nodes.ARG_POS] * len(e.items), e)
def visit_tuple_expr(self, e):
"""Type check a tuple expression."""
if e.types is None:
ctx = None
# Try to determine type context for type inference.
if isinstance(self.chk.type_context[-1], TupleType):
t = self.chk.type_context[-1]
if len(t.items) == len(e.items):
ctx = t
# Infer item types.
items = []
for i in range(len(e.items)):
item = e.items[i]
tt = None
if not ctx:
tt = self.accept(item)
else:
tt = self.accept(item, ctx.items[i])
self.check_not_void(tt, e)
items.append(tt)
return TupleType(items)
else:
# Explicit item types, i.e. expression of form <t, ...> (e, ...).
for j in range(len(e.types)):
item = e.items[j]
itemtype = self.accept(item)
self.chk.check_subtype(itemtype, e.types[j], item,
messages.INCOMPATIBLE_TUPLE_ITEM_TYPE)
return TupleType(e.types)
def visit_dict_expr(self, e):
if not e.key_type:
# A dict expression without an explicit type; translate into type
# checking a generic function call.
tv1 = TypeVar('KT', -1)
tv2 = TypeVar('VT', -2)
constructor = None
# The callable type represents a function like this:
#
# dict<kt, vt> make_dict<kt, vt>(tuple<kt, vt> *v): ...
constructor = Callable([TupleType([tv1, tv2])],
[nodes.ARG_STAR],
[None],
self.chk.named_generic_type('builtins.dict',
[tv1, tv2]),
False,
'<list>',
TypeVars([TypeVarDef('KT', -1),
TypeVarDef('VT', -2)]))
# Synthesize function arguments.
args = []
for key, value in e.items:
args.append(TupleExpr([key, value]))
return self.check_call(constructor,
args,
[nodes.ARG_POS] * len(args), e)
else:
for key_, value_ in e.items:
kt = self.accept(key_)
vt = self.accept(value_)
self.chk.check_subtype(kt, e.key_type, key_,
messages.INCOMPATIBLE_KEY_TYPE)
self.chk.check_subtype(vt, e.value_type, value_,
messages.INCOMPATIBLE_VALUE_TYPE)
return self.chk.named_generic_type('builtins.dict', [e.key_type,
e.value_type])
def visit_func_expr(self, e):
"""Type check lambda expression."""
inferred_type = self.infer_lambda_type(e)
self.chk.check_func_item(e, type_override=inferred_type)
ret_type = self.chk.type_map[e.expr()]
if inferred_type:
return replace_callable_return_type(inferred_type, ret_type)
elif e.typ:
return replace_callable_return_type(e.typ.typ, ret_type)
else:
# Use default type for lambda.
# TODO infer return type?
return function_type(e)
def infer_lambda_type(self, e):
"""Try to infer lambda expression type using context.
Return None if could not infer type.
"""
ctx = self.chk.type_context[-1]
if not ctx or not isinstance(ctx, Callable):
return None
# The context may have function type variables in it. We replace them
# since these are the type variables we are ultimately trying to infer;
# they must be considered as indeterminate. We use ErasedType since it
# does not affect type inference results (it is for purposes like this
# only).
ctx = replace_func_type_vars(ctx, ErasedType())
callable_ctx = ctx
if callable_ctx.arg_kinds != e.arg_kinds:
# Incompatible context; cannot use it to infer types.
self.chk.fail(messages.CANNOT_INFER_LAMBDA_TYPE, e)
return None
if not e.typ:
return callable_ctx
else:
# The lambda already has a type; only infer the return type.
return replace_callable_return_type(e.typ.typ,
callable_ctx.ret_type)
def visit_super_expr(self, e):
"""Type check a super expression (non-lvalue)."""
t = self.analyse_super(e, False)
return t
def analyse_super(self, e, is_lvalue):
"""Type check a super expression."""
if e.info and e.info.base:
return analyse_member_access(e.name, self_type(e.info), e,
is_lvalue, True,
self.chk.tuple_type(), self.msg,
e.info.base)
else:
# Invalid super. This has been reported by the semantic analyser.
return Any()
def visit_paren_expr(self, e):
"""Type check a parenthesised expression."""
return self.accept(e.expr, self.chk.type_context[-1])
def visit_slice_expr(self, e):
for index in [e.begin_index, e.end_index, e.stride]:
if index:
t = self.accept(index)
self.chk.check_subtype(t, self.named_type('builtins.int'),
index, messages.INVALID_SLICE_INDEX)
return self.named_type('builtins.slice')
def visit_list_comprehension(self, e):
return self.check_generator_or_comprehension(
e.generator, 'builtins.list', '<list-comprehension>')
def visit_generator_expr(self, e):
return self.check_generator_or_comprehension(e, 'builtins.Iterator',
'<generator>')
def check_generator_or_comprehension(self, gen, type_name, id_for_messages):
"""Type check a generator expression or a list comprehension."""
item_type = self.chk.analyse_iterable_item_type(gen.right_expr)
self.chk.analyse_index_variables(gen.index, False, item_type, gen)
if gen.condition:
self.accept(gen.condition)
# Infer the type of the list comprehension by using a synthetic generic
# callable type.
tv = TypeVar('T', -1)
constructor = Callable([tv],
[nodes.ARG_POS],
[None],
self.chk.named_generic_type(type_name, [tv]),
False,
id_for_messages,
TypeVars([TypeVarDef('T', -1)]))
return self.check_call(constructor,
[gen.left_expr], [nodes.ARG_POS], gen)
#
# Helpers
#
def accept(self, node, context=None):
"""Type check a node. Alias for TypeChecker.accept."""
return self.chk.accept(node, context)
def check_not_void(self, typ, context):
"""Generate an error if type is Void."""
self.chk.check_not_void(typ, context)
def is_boolean(self, typ):
"""Is type compatible with bool?"""
return is_subtype(typ, self.chk.bool_type())
def named_type(self, name):
"""Return an instance type with type given by the name and no type
arguments. Alias for TypeChecker.named_type.
"""
return self.chk.named_type(name)
def type_object_type(self, info):
"""Return the type of a type object.
For a generic type G with type variables T and S the type is of form
def <T, S>(...) as G<T, S>,
where ... are argument types for the __init__ method.
"""
if info.is_interface:
return self.chk.type_type()
init_method = info.get_method('__init__')
if not init_method:
# Must be an invalid class definition.
return Any()
else:
# Construct callable type based on signature of __init__. Adjust
# return type and insert type arguments.
init_type = method_type(init_method)
if isinstance(init_type, Callable):
return self.class_callable(init_type, info)
else:
# Overloaded __init__.
items = []
for it in (init_type).items():
items.append(self.class_callable(it, info))
return Overloaded(items)
def class_callable(self, init_type, info):
"""Create a type object type based on the signature of __init__."""
variables = []
for i in range(len(info.type_vars)): # TODO bounds
variables.append(TypeVarDef(info.type_vars[i], i + 1, None))
initvars = init_type.variables.items
variables.extend(initvars)
c = Callable(init_type.arg_types,
init_type.arg_kinds,
init_type.arg_names,
self_type(info),
True,
None,
TypeVars(variables)).with_name(
'"{}"'.format(info.name()))
return convert_class_tvars_to_func_tvars(c, len(initvars))
def is_valid_var_arg(self, typ):
"""Is a type valid as a *args argument?"""
return (isinstance(typ, TupleType) or self.is_list_instance(typ) or
isinstance(typ, Any))
def is_valid_keyword_var_arg(self, typ):
"""Is a type valid as a **kwargs argument?"""
return is_subtype(typ, self.chk.named_generic_type(
'builtins.dict', [self.named_type('builtins.str'), Any()]))
def is_list_instance(self, t):
"""Is the argument an instance type ...[]?"""
return (isinstance(t, Instance) and
(t).typ.full_name() == 'builtins.list')
def has_non_method(self, typ, member):