-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
main_visitor.cr
3557 lines (2928 loc) · 106 KB
/
main_visitor.cr
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
require "./semantic_visitor"
module Crystal
class Program
def visit_main(node, visitor : MainVisitor = MainVisitor.new(self), process_finished_hooks = false, cleanup = true)
node.accept visitor
program.process_finished_hooks(visitor) if process_finished_hooks
missing_types = FixMissingTypes.new(self)
node.accept missing_types
program.process_finished_hooks(missing_types) if process_finished_hooks
node = cleanup node if cleanup
if process_finished_hooks
finished_hooks.map! do |hook|
hook_node = cleanup(hook.node)
FinishedHook.new(hook.scope, hook.macro, hook_node)
end
end
node
end
end
# This is the main visitor of the program, ran after types have been declared
# and their type declarations (like `@x : Int32`) have been processed.
#
# This visits the "main" code of the program and resolves calls, instantiates
# methods and visits them, recursively, with other MainVisitors.
#
# The visitor keeps track of a method's variables (or the main program, split into
# several files, in case of top-level code). It keeps track both of the type of a
# variable at a single point (stored in @vars) and the combined type of all assignments
# to it (in @meta_vars).
#
# Call resolution logic is in `Call#recalculate`, where method lookup is done.
class MainVisitor < SemanticVisitor
ValidGlobalAnnotations = %w(ThreadLocal)
ValidClassVarAnnotations = %w(ThreadLocal)
getter! typed_def
property! untyped_def : Def
setter untyped_def
getter block : Block?
property call : Call?
property path_lookup
property fun_literal_context : Def | Program | Nil
property parent : MainVisitor?
property block_nest = 0
property with_scope : Type?
property match_context : MatchContext?
# These are the variables and types that come from a block specification
# like `&block : Int32 -> Int32`. When doing `yield 1` we need to verify
# that the yielded expression has the type that the block specification said.
property yield_vars : Array(Var)?
# In vars we store the types of variables as we traverse the nodes.
# These type are not cumulative: if you do `x = 1`, 'x' will have
# type Int32. Then if you do `x = false`, 'x' will have type Bool.
getter vars
# Here we store the cumulative types of variables as we traverse the nodes.
getter meta_vars : MetaVars
property is_initialize : Bool
property all_exception_handler_vars : Array(MetaVars)? = nil
private enum BlockKind
None
While
Block
Ensure
end
# It means the last block kind. It is used to detect `break` or `next`
# from `ensure`.
#
# ```
# begin
# # `last_block_kind.none?`
# ensure
# # `last_block_kind.ensure?`
# while true
# # `last_block_kind.while?`
# end
# loop do
# # `last_block_kind.block?`
# end
# # `last_block_kind.ensure?`
# end
# ```
property last_block_kind : BlockKind = :none
property? inside_ensure : Bool = false
property? inside_constant = false
property file_module : FileModule?
@unreachable = false
@is_initialize = false
@inside_is_a = false
@in_type_args = 0
@while_stack = [] of While
@type_filters : TypeFilters?
@needs_type_filters = 0
@typeof_nest = 0
@found_self_in_initialize_call : Array(ASTNode)?
@used_ivars_in_calls_in_initialize : Hash(String, Array(ASTNode))?
@block_context : Block?
@while_vars : MetaVars?
# Type filters for `exp` in `!exp`, used after a `while`
@before_not_type_filters : TypeFilters?
def initialize(program, vars = MetaVars.new, @typed_def = nil, meta_vars = nil)
super(program, vars)
@is_initialize = !!(typed_def && (
typed_def.name == "initialize" ||
typed_def.name.starts_with?("initialize:") # Because of expanded methods from named args
))
# We initialize meta_vars from vars given in the constructor.
# We store those meta vars either in the typed def or in the program
# so the codegen phase knows the cumulative types to do allocas.
unless meta_vars
if typed_def = @typed_def
meta_vars = typed_def.vars = MetaVars.new
else
meta_vars = @program.vars
end
vars.each do |name, var|
meta_var = new_meta_var(name)
meta_var.bind_to(var)
meta_vars[name] = meta_var
end
end
@meta_vars = meta_vars
end
def initialize(*, from_main_visitor : MainVisitor)
super(from_main_visitor.@program, from_main_visitor.@vars)
@meta_vars = from_main_visitor.@meta_vars
@typed_def = from_main_visitor.@typed_def
@scope = from_main_visitor.@scope
@path_lookup = from_main_visitor.@path_lookup
end
def visit_any(node)
@unreachable = false
super
end
def visit(node : FileNode)
old_vars = @vars
old_meta_vars = @meta_vars
old_file_module = @file_module
@vars = MetaVars.new
@file_module = file_module = @program.file_module(node.filename)
@meta_vars = file_module.vars
node.node.accept self
node.type = @program.nil_type
@vars = old_vars
@meta_vars = old_meta_vars
@file_module = old_file_module
false
end
def visit(node : Path)
lookup_scope = @path_lookup || @scope || @current_type
# If the lookup scope is a generic type, like Foo(T), we don't
# want to find T in main code. For example:
#
# class Foo(T)
# Bar(T) # This T is unbound and it shouldn't be found in the lookup
# end
find_root_generic_type_parameters = !lookup_scope.is_a?(GenericType)
type = lookup_scope.lookup_type_var(node,
free_vars: free_vars,
find_root_generic_type_parameters: find_root_generic_type_parameters,
remove_alias: false)
case type
when Const
if !type.value.type? && !type.visited?
type.visited = true
meta_vars = MetaVars.new
const_def = Def.new("const", [] of Arg)
type_visitor = MainVisitor.new(@program, meta_vars, const_def)
type_visitor.current_type = type.namespace
type_visitor.inside_constant = true
type.value.accept type_visitor
type.fake_def = const_def
type.visitor = self
type.used = true
program.const_initializers << type
end
node.target_const = type
node.bind_to type.value
when Type
# We devirtualize the type because in an expression like
#
# T.new
#
# even if T is a virtual type that resulted from a generic
# type argument, creating an instance or invoking methods
# on the type itself don't need to resolve virtually.
#
# It's different if from a virtual type we do `v.class.new`
# because the class could be any in the hierarchy.
node.type = check_type_in_type_args(type.remove_alias_if_simple).devirtualize
node.target_type = type
when ASTNode
type.accept self unless type.type?
node.syntax_replacement = type
node.bind_to type
end
false
end
def visit(node : Generic)
node.in_type_args = @in_type_args > 0
node.inside_is_a = @inside_is_a
node.scope = @scope
node.name.accept self
@in_type_args += 1
node.type_vars.each &.accept self
node.named_args.try &.each &.value.accept self
@in_type_args -= 1
return false if node.type?
name = node.name
if name.is_a?(Path) && name.target_const
node.raise "#{name} is not a type, it's a constant"
end
instance_type = node.name.type.instance_type
unless instance_type.is_a?(GenericType)
node.raise "#{instance_type} is not a generic type, it's a #{instance_type.type_desc}"
end
if instance_type.double_variadic?
unless node.type_vars.empty?
node.raise "can only instantiate NamedTuple with named arguments"
end
else
if node.named_args
node.raise "can only use named arguments with NamedTuple"
end
# Need to count type vars because there might be splats
type_vars_count = 0
knows_count = true
node.type_vars.each do |type_var|
if type_var.is_a?(Splat)
if (type_var_type = type_var.type?)
unless type_var_type.is_a?(TupleInstanceType)
type_var.raise "argument to splat must be a tuple type, not #{type_var_type}"
end
type_vars_count += type_var_type.size
else
knows_count = false
break
end
else
type_vars_count += 1
end
end
if knows_count
if instance_type.splat_index
min_needed = instance_type.type_vars.size
min_needed -= 1 if instance_type.splat_index
if type_vars_count < min_needed
node.wrong_number_of "type vars", instance_type, type_vars_count, "#{min_needed}+"
end
else
needed_count = instance_type.type_vars.size
if type_vars_count != needed_count
node.wrong_number_of "type vars", instance_type, type_vars_count, needed_count
end
end
end
end
node.instance_type = instance_type.as(GenericType)
node.type_vars.each &.add_observer(node)
node.named_args.try &.each &.value.add_observer(node)
node.update
false
end
def visit(node : ProcNotation)
types = [] of Type
@in_type_args += 1
node.inputs.try &.each do |input|
input.accept self
input_type = input.type
check_not_a_constant(input)
MainVisitor.check_type_allowed_as_proc_argument(input, input_type)
types << input_type.virtual_type
end
if output = node.output
output.accept self
output_type = output.type
check_not_a_constant(output)
MainVisitor.check_type_allowed_as_proc_argument(output, output_type)
types << output_type.virtual_type
else
types << program.void
end
@in_type_args -= 1
node.type = program.proc_of(types)
false
end
def visit(node : Union)
@in_type_args += 1
node.types.each &.accept self
@in_type_args -= 1
node.inside_is_a = @inside_is_a
node.update
false
end
def visit(node : Metaclass)
node.name.accept self
node.type = node.name.type.virtual_type.metaclass
false
end
def visit(node : Self)
node.type = the_self(node).instance_type
false
end
def visit(node : Var)
var = @vars[node.name]?
if var
if var.type?.is_a?(Program) && node.name == "self"
node.raise "there's no self in this scope"
end
meta_var = @meta_vars[node.name]
check_closured meta_var
if var.nil_if_read?
# Once we know a variable is nil if read we mark it as nilable
var.bind_to(@program.nil_var)
var.nil_if_read = false
meta_var.bind_to(@program.nil_var) unless meta_var.dependencies.try &.any? &.same?(@program.nil_var)
node.bind_to(@program.nil_var)
end
check_mutably_closured meta_var, var
node.bind_to(var)
if needs_type_filters?
@type_filters = TypeFilters.truthy(node)
end
elsif node.name == "self"
current_type = current_type()
if current_type.is_a?(Program)
node.raise "there's no self in this scope"
else
node.type = current_type.metaclass
end
elsif node.special_var?
special_var = define_special_var(node.name, program.nil_var)
node.bind_to special_var
else
node.raise "read before assignment to local variable '#{node.name}'"
end
false
end
def visit(node : TypeDeclaration)
case var = node.var
when Var
if @meta_vars[var.name]?
node.raise "variable '#{var.name}' already declared"
end
meta_var = new_meta_var(var.name)
meta_var.type = @program.no_return
var.bind_to(meta_var)
@meta_vars[var.name] = meta_var
@in_type_args += 1
node.declared_type.accept self
@in_type_args -= 1
check_not_a_constant(node.declared_type)
if declared_type = node.declared_type.type?
var_type = check_declare_var_type node, declared_type, "a variable"
meta_var.freeze_type = var_type
else
node.raise "can't infer type of type declaration"
end
if value = node.value
type_assign(var, value, node)
node.bind_to value
else
node.type = @program.nil
end
when InstanceVar
if @untyped_def
node.raise "declaring the type of an instance variable must be done at the class level"
end
node.type = @program.nil
when ClassVar
if @untyped_def
node.raise "declaring the type of a class variable must be done at the class level"
end
thread_local = check_class_var_annotations
class_var = lookup_class_var(var)
var.var = class_var
class_var.thread_local = true if thread_local
node.type = @program.nil
else
raise "Bug: unexpected var type: #{var.class}"
end
false
end
def visit(node : UninitializedVar)
case var = node.var
when Var
if @vars[var.name]?
var.raise "variable '#{var.name}' already declared"
end
@in_type_args += 1
node.declared_type.accept self
@in_type_args -= 1
check_not_a_constant(node.declared_type)
# TODO: should we be using a binding here to recompute the type?
if declared_type = node.declared_type.type?
var_type = check_declare_var_type node, declared_type, "a variable"
var_type = var_type.virtual_type
var.type = var_type
else
node.raise "can't infer type of type declaration"
end
meta_var, _ = assign_to_meta_var(var.name)
if (existing_type = meta_var.type?) && existing_type != var_type
node.raise "variable '#{var.name}' already declared with type #{existing_type}"
end
meta_var.bind_to(var)
meta_var.freeze_type = var_type
@vars[var.name] = meta_var
check_exception_handler_vars(var.name, node)
node.type = meta_var.type unless meta_var.type.no_return?
when InstanceVar
type = scope? || current_type
if @untyped_def
@in_type_args += 1
node.declared_type.accept self
@in_type_args -= 1
check_declare_var_type node, node.declared_type.type, "an instance variable"
ivar = lookup_instance_var(var, type)
if @is_initialize
@vars[var.name] = MetaVar.new(var.name, ivar.type)
end
else
# Already handled in a previous visitor
node.type = @program.nil
return false
end
case type
when NonGenericClassType
@in_type_args += 1
node.declared_type.accept self
@in_type_args -= 1
check_declare_var_type node, node.declared_type.type, "an instance variable"
when GenericClassType
# OK
when GenericClassInstanceType
# OK
else
node.raise "can only declare instance variables of a non-generic class, not a #{type.type_desc} (#{type})"
end
when ClassVar
thread_local = check_class_var_annotations
class_var = visit_class_var var
class_var.thread_local = true if thread_local
else
raise "Bug: unexpected var type: #{var.class}"
end
node.type = @program.nil unless node.type?
false
end
def check_not_a_constant(node)
if node.is_a?(Path) && node.target_const
node.raise "#{node.target_const} is not a type, it's a constant"
end
end
def check_exception_handler_vars(var_name, node)
# If inside a begin part of an exception handler, bind this type to
# the variable that will be used in the rescue/else blocks.
@all_exception_handler_vars.try &.each do |exception_handler_vars|
var = (exception_handler_vars[var_name] ||= MetaVar.new(var_name))
var.bind_to(node)
end
end
def visit(node : Out)
case exp = node.exp
when Var
if @meta_vars.has_key?(exp.name)
exp.raise "variable '#{exp.name}' is already defined, `out` must be used to define a variable, use another name"
end
# We declare out variables
@meta_vars[exp.name] = new_meta_var(exp.name)
@vars[exp.name] = new_meta_var(exp.name)
when InstanceVar
var = lookup_instance_var exp
exp.bind_to(var)
if @is_initialize
@vars[exp.name] = MetaVar.new(exp.name)
end
when Underscore
# Nothing to do
else
node.raise "BUG: unexpected out exp: #{exp}"
end
node.bind_to node.exp
false
end
def visit(node : Global)
# Reading from a special global variable is actually
# reading from a local variable with that same not,
# invoking `not_nil!` on it (because these are usually
# accessed after invoking a method that brought them
# into the current scope, and it would be annoying
# to ask the user to always invoke `not_nil!` on it)
case node.name
when "$~", "$?"
expanded = Call.new(Var.new(node.name).at(node), "not_nil!").at(node)
expanded.accept self
node.bind_to expanded
node.expanded = expanded
else
node.raise "BUG: there should be no use of global variables other than $~ and $?"
end
false
end
def undefined_instance_variable(owner, node)
similar_name = owner.lookup_similar_instance_var_name(node.name)
program.undefined_instance_variable(node, owner, similar_name)
end
def first_time_accessing_meta_type_var?(var)
return false if var.uninitialized?
if var.freeze_type
deps = var.dependencies?
# If no dependencies, it's the case of a global for a regex literal.
# If there are dependencies and it's just one, it's the same var
deps ? deps.size == 1 : false
else
!var.dependencies?
end
end
def visit(node : InstanceVar)
var = lookup_instance_var node
node.bind_to(var)
if @is_initialize &&
@typeof_nest == 0 &&
!@vars.has_key?(node.name) &&
!scope.has_instance_var_initializer?(node.name)
ivar = scope.lookup_instance_var(node.name)
ivar.nil_reason ||= NilReason.new(node.name, :used_before_initialized, [node] of ASTNode)
ivar.bind_to program.nil_var
end
false
end
def visit(node : ReadInstanceVar)
visit_read_instance_var node
false
end
def visit_read_instance_var(node)
node.obj.accept self
node.obj.add_observer node
node.update
end
def visit(node : ClassVar)
thread_local = check_class_var_annotations
var = visit_class_var node
var.thread_local = true if thread_local
false
end
def visit_class_var(node)
var = lookup_class_var(node)
node.bind_to var
node.var = var
var
end
private def lookup_instance_var(node)
lookup_instance_var(node, @scope)
end
private def lookup_instance_var(node, scope)
unless scope
node.raise "can't use instance variables at the top level"
end
ivar = scope.remove_typedef.lookup_instance_var(node)
unless ivar
undefined_instance_variable(scope, node)
end
check_self_closured
ivar
end
def visit(node : Expressions)
exp_count = node.expressions.size
node.expressions.each_with_index do |exp, i|
if i == exp_count - 1
exp.accept self
node.bind_to exp
else
ignoring_type_filters { exp.accept self }
end
end
if node.empty?
node.set_type(@program.nil)
end
false
end
def visit(node : Assign)
type_assign node.target, node.value, node
if @is_initialize && !@found_self_in_initialize_call
value = node.value
if value.is_a?(Var) && value.name == "self"
@found_self_in_initialize_call = [value] of ASTNode
end
end
false
end
def type_assign(target : Var, value, node, restriction = nil)
value.accept self
var_name = target.name
meta_var, meta_var_existed = assign_to_meta_var(var_name)
freeze_type = meta_var.freeze_type
if freeze_type
if casted_value = check_automatic_cast(value, freeze_type, node)
value = casted_value
end
end
# If this assign comes from a AssignWithRestriction node, check the restriction
if restriction && (value_type = value.type?)
if value_type.restrict(restriction, match_context.not_nil!)
# OK
else
# Check autocast too
restriction_type = (path_lookup || scope).lookup_type?(restriction, free_vars: free_vars)
if casted_value = check_automatic_cast(value, restriction_type, node)
value = casted_value
else
if value.is_a?(SymbolLiteral) && restriction_type.is_a?(EnumType)
node.raise "can't autocast #{value} to #{restriction_type}: no matching enum member"
end
node.raise "can't restrict #{value.type} to #{restriction}"
end
end
end
target.bind_to value
node.bind_to value
value_type_filters = @type_filters
@type_filters = nil
# Save variable assignment location for debugging output
meta_var.location ||= target.location
begin
meta_var.bind_to value
rescue ex : FrozenTypeException
target.raise ex.message
end
meta_var.assigned_to = true
check_closured meta_var, mark_as_mutably_closured: meta_var_existed
simple_var = MetaVar.new(var_name)
# When we assign to a local variable with a fixed type, and it's
# a Proc, we always want to keep that proc's type.
if freeze_type && freeze_type.is_a?(ProcInstanceType)
simple_var.bind_to(meta_var)
else
simple_var.bind_to(target)
check_mutably_closured(meta_var, simple_var)
end
@vars[var_name] = simple_var
check_exception_handler_vars var_name, value
if needs_type_filters?
@type_filters = TypeFilters.assign_var(value_type_filters, target)
end
if target.special_var?
if typed_def = @typed_def
typed_def.add_special_var(target.name)
# Always bind with a special var with nil, so it's easier to assign it later
# in the codegen (just store the whole value through a pointer)
simple_var.bind_to(@program.nil_var)
meta_var.bind_to(@program.nil_var)
# If we are in a call's block, define the special var in the block
if (call = @call) && call.block
call.parent_visitor.define_special_var(target.name, value)
end
else
node.raise "'#{var_name}' can't be assigned at the top level"
end
end
end
def type_assign(target : InstanceVar, value, node)
# Check if this is an instance variable initializer
unless @scope
# `InstanceVar` assignment appeared in block is not checked
# by `Crystal::InstanceVarsInitializerVisitor` because this block
# may be passed to a macro. So, it checks here.
if current_type.is_a?(Program) || current_type.is_a?(FileModule)
node.raise "can't use instance variables at the top level"
end
# Already handled by InstanceVarsInitializerVisitor
return
end
value.accept self
var = lookup_instance_var target
if casted_value = check_automatic_cast(value, var.type, node)
value = casted_value
end
target.bind_to var
node.bind_to value
begin
var.bind_to value
rescue ex : FrozenTypeException
target.raise ex.message
end
if @is_initialize
var_name = target.name
# Don't track instance variables nilability (for example, if they were
# just assigned inside a branch) if they have an initializer
unless scope.has_instance_var_initializer?(var_name)
meta_var, _ = assign_to_meta_var(var_name)
meta_var.bind_to value
meta_var.assigned_to = true
simple_var = MetaVar.new(var_name)
simple_var.bind_to(target)
end
# Check if an instance variable is being assigned (for the first time)
# and self, or that same instance variable, was used (read) before that.
unless @vars.has_key?(var_name) || scope.has_instance_var_initializer?(var_name)
if (found_self = @found_self_in_initialize_call) ||
(used_ivars_node = @used_ivars_in_calls_in_initialize.try(&.[var_name]?)) ||
(@block_nest > 0)
ivar = scope.lookup_instance_var(var_name)
if found_self
ivar.nil_reason = NilReason.new(var_name, :used_self_before_initialized, found_self)
else
ivar.nil_reason = NilReason.new(var_name, :used_before_initialized, used_ivars_node)
end
ivar.bind_to program.nil_var
end
end
if simple_var
@vars[var_name] = simple_var
check_exception_handler_vars var_name, value
end
end
end
def type_assign(target : Path, value, node)
target.bind_to value
node.type = @program.nil
false
end
def type_assign(target : Global, value, node)
node.raise "BUG: there should be no use of global variables other than $~ and $?"
end
def type_assign(target : ClassVar, value, node)
thread_local = check_class_var_annotations
# Outside a def is already handled by ClassVarsInitializerVisitor
# (@exp_nest is 1 if we are at the top level because it was incremented
# by one since we are inside an Assign)
if !@typed_def && (@exp_nest <= 1) && !inside_block?
var = lookup_class_var(target)
target.var = var
var.thread_local = true if thread_local
return
end
value.accept self
var = lookup_class_var(target)
target.var = var
var.thread_local = true if thread_local
if casted_value = check_automatic_cast(value, var.type, node)
value = casted_value
end
target.bind_to var
node.bind_to value
var.bind_to value
end
def type_assign(target : Underscore, value, node)
value.accept self
node.bind_to value
end
def type_assign(target, value, node)
raise "BUG: unknown assign target in MainVisitor: #{target}"
end
# See if we can automatically cast the value if the types don't exactly match
def check_automatic_cast(value, var_type, assign = nil)
MainVisitor.check_automatic_cast(@program, value, var_type, assign)
end
def self.check_automatic_cast(program, value, var_type, assign = nil)
if value.is_a?(NumberLiteral) && value.type != var_type
literal_type = NumberAutocastType.new(program, value)
restricted = literal_type.restrict(var_type, MatchContext.new(value.type, value.type))
if restricted.is_a?(IntegerType) || restricted.is_a?(FloatType)
value.type = restricted
value.kind = restricted.kind
assign.value = value if assign
return value
end
elsif value.is_a?(SymbolLiteral) && value.type != var_type
literal_type = SymbolAutocastType.new(program, value)
restricted = literal_type.restrict(var_type, MatchContext.new(value.type, value.type))
if restricted.is_a?(EnumType)
member = restricted.find_member(value.value).not_nil!
path = Path.new(member.name)
path.target_const = member
path.type = restricted
value = path
assign.value = value if assign
return value
end
end
nil
end
def visit(node : Yield)
call = @call
unless call
node.raise "can't use `yield` outside a method"
end
if @fun_literal_context
node.raise <<-MSG
can't use `yield` inside a proc literal or captured block
Make sure to read the whole docs section about blocks and procs,
including "Capturing blocks" and "Block forwarding":
https://crystal-lang.org/reference/syntax_and_semantics/blocks_and_procs.html
MSG
end
block = call.block || node.raise("no block given")
# This is the case of a yield when there's a captured block
if block.fun_literal
block_arg_name = typed_def.block_arg.not_nil!.name
block_var = Var.new(block_arg_name).at(node)
call = Call.new(block_var, "call", node.exps).at(node)
call.accept self
node.bind_to call
node.expanded = call
return false
end
node.scope.try &.accept self
node.exps.each &.accept self
# We use a binder to support splats and other complex forms
binder = block.binder ||= YieldBlockBinder.new(@program, block)
binder.add_yield(node, @yield_vars)
binder.update
unless block.visited?
# When we yield, we are no longer inside `untyped_def`, so we un-nest
untyped_def = @untyped_def
untyped_def.block_nest -= 1 if untyped_def
call.bubbling_exception do
if node_scope = node.scope
block.scope = node_scope.type
end
ignoring_type_filters do