class Crystal::MainVisitor
inherits Crystal::SemanticVisitor
¶
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.
Constants¶
ValidClassVarAnnotations = ["ThreadLocal"] of ::String
¶
["ThreadLocal"] of ::String
ValidGlobalAnnotations = ["ThreadLocal"] of ::String
¶
["ThreadLocal"] of ::String
Class methods¶
Methods¶
#check_automatic_cast(value, var_type, assign = nil)
¶
(value, var_type, assign = nil)
See if we can automatically cast the value if the types don't exactly match
#check_call_in_initialize(node)
¶
(node)
Checks if it's a call to self. In that case, all instance variables not mentioned so far will be considered nil.
#check_mutably_closured(meta_var, var)
¶
(meta_var, var)
If the meta_var is closured but not readonly, then bind var to it (it gets all types assigned to meta_var). Otherwise, add it to the local vars so that they could be bond later on, if the meta_var stops being readonly.
#check_super_or_previous_def_in_initialize(node)
¶
(node)
If it's a super or previous_def call inside an initialize we treat set instance vars from superclasses to not-nil.
#convert_struct_or_union_numeric_argument(node, unaliased_type, expected_type, actual_type)
¶
(node, unaliased_type, expected_type, actual_type)
#filter_vars(filters)
¶
(filters)
If we have:
if a ... end
then inside the if 'a' must not be nil.
This is what we do here: we create a meta-variable for it and filter it accordingly. This also applied to .is_a? and .responds_to?.
This also applies to 'while' conditions and also to the else part of an if, but with filters inverted.
#get_expression_var(exp)
¶
(exp)
Get the variable of an expression. If it's a variable, it's that variable. If it's an assignment to a variable, it's that variable.
#last_block_kind : Symbol?
¶
: Symbol?
It means the last block kind, that is one of block, while and
ensure. It is used to detect break or next from ensure.
begin
# `last_block_kind == nil`
ensure
# `last_block_kind == :ensure`
while true
# `last_block_kind == :while`
end
loop do
# `last_block_kind == :block`
end
# `last_block_kind == :ensure`
end
#last_block_kind=(last_block_kind : Symbol?)
¶
(last_block_kind : Symbol?)
It means the last block kind, that is one of block, while and
ensure. It is used to detect break or next from ensure.
begin
# `last_block_kind == nil`
ensure
# `last_block_kind == :ensure`
while true
# `last_block_kind == :while`
end
loop do
# `last_block_kind == :block`
end
# `last_block_kind == :ensure`
end
#merge_if_vars(node, cond_vars, then_vars, else_vars, before_then_vars, before_else_vars, then_unreachable, else_unreachable)
¶
(node, cond_vars, then_vars, else_vars, before_then_vars, before_else_vars, then_unreachable, else_unreachable)
Here we merge the variables from both branches of an if. We basically: - Create a variable whose type is the merged types of the last type of each branch. - Make the variable nilable if the variable wasn't declared before the 'if' and it doesn't appear in one of the branches. - Don't use the type of a branch that is unreachable (ends with return, break or with a call that is NoReturn)
#merge_while_vars(cond, endless, before_cond_vars_copy, before_cond_vars, after_cond_vars, while_vars, all_break_vars)
¶
(cond, endless, before_cond_vars_copy, before_cond_vars, after_cond_vars, while_vars, all_break_vars)
Here we assign the types of variables after a while.
#meta_vars : MetaVars
¶
: MetaVars
Here we store the cumulative types of variables as we traverse the nodes.
#node_exp_or_nil_literal(node)
¶
(node)
Returns node.exp if it's not nil. Otherwise,
creates a NilLiteral node that has the same location
as node, and returns that.
We use this NilLiteral when the user writes
return, next or break without arguments,
so that in the error trace we can show it right
(those expressions have a NoReturn type so we can't
directly bind to them).
#special_c_struct_or_union_new_with_named_args(node, type, named_args)
¶
(node, type, named_args)
Rewrite:
LibFoo::Struct.new arg0: value0, argN: value0
To:
temp = LibFoo::Struct.new temp.arg0 = value0 temp.argN = valueN temp
#vars : Hash(String, Crystal::MetaVar)
¶
: Hash(String, Crystal::MetaVar)
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.
#yield_vars : Array(Var)?
¶
: Array(Var)?
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.
#yield_vars=(yield_vars : Array(Var)?)
¶
(yield_vars : Array(Var)?)
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.