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.