class Crystal::MainVisitor

Overview

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.

Direct Known Subclasses

Defined in:

compiler/crystal/semantic/main_visitor.cr

Constant Summary

ValidClassVarAnnotations = ["ThreadLocal"] of ::String
ValidGlobalAnnotations = ["ThreadLocal"] of ::String

Constructors

Class Method Summary

Instance Method Summary

Instance methods inherited from class Crystal::SemanticVisitor

check_allowed_in_lib(node, type = node.type.instance_type) check_allowed_in_lib, check_class_var_annotations check_class_var_annotations, check_declare_var_type(node, declared_type, variable_kind) check_declare_var_type, check_outside_exp(node, op) check_outside_exp, class_var_owner(node) class_var_owner, current_type : ModuleType current_type, current_type=(current_type : ModuleType) current_type=, end_visit_any(node) end_visit_any, eval_macro(node, &) eval_macro, expand_inline_macro(node, mode = nil, accept = true) expand_inline_macro, expand_macro(node, raise_on_missing_const = true, first_pass = false, accept = true)
expand_macro(the_macro, node, mode = nil, *, visibility : Visibility, accept = true, &)
expand_macro
, expand_macro_argument(node, expansion_scope) expand_macro_argument, expand_macro_arguments(call, expansion_scope) expand_macro_arguments, free_vars : Hash(String, TypeVar) | Nil free_vars, inside_exp? inside_exp?, interpret_enum_value(node : ASTNode, target_type : IntegerType | Nil = nil) interpret_enum_value, lookup_annotation(ann) lookup_annotation, lookup_type(node : ASTNode, free_vars = nil, find_root_generic_type_parameters = true) lookup_type, nesting_exp?(node) nesting_exp?, process_annotations(annotations, &) process_annotations, program : Program program, pushing_type(type : ModuleType, &) pushing_type, read_annotations read_annotations, scope : Type scope, scope=(scope : Type)
scope=(scope : Crystal::Type | Nil)
scope=
, scope? : Type | Nil scope?, validate_annotation(annotation_type, ann) validate_annotation, vars : MetaVars vars, vars=(vars : MetaVars) vars=, visit(node : Require)
visit(node : ClassDef)
visit(node : ModuleDef)
visit(node : AnnotationDef)
visit(node : EnumDef)
visit(node : LibDef)
visit(node : Include)
visit(node : Extend)
visit(node : Alias)
visit(node : Def)
visit(node : Macro)
visit(node : Annotation)
visit(node : Call)
visit(node : MacroExpression)
visit(node : MacroIf)
visit(node : MacroFor)
visit(node : MacroVerbatim)
visit(node : ExternalVar | Path | Generic | ProcNotation | Union | Metaclass | Self | TypeOf)
visit(node : ASTNode)
visit
, visit_any(node) visit_any

Constructor methods inherited from class Crystal::SemanticVisitor

new(program : Crystal::Program, vars : Hash(String, Crystal::MetaVar) = MetaVars.new) new

Instance methods inherited from class Crystal::Visitor

accept(node) accept, end_visit(node) end_visit, end_visit_any(node) end_visit_any, visit_any(node) visit_any

Instance methods inherited from class Reference

==(other : self)
==(other : JSON::Any)
==(other : YAML::Any)
==(other)
==
, dup dup, hash(hasher) hash, initialize initialize, inspect(io : IO) : Nil inspect, object_id : UInt64 object_id, pretty_print(pp) : Nil pretty_print, same?(other : Reference) : Bool
same?(other : Nil)
same?
, to_s(io : IO) : Nil to_s

Constructor methods inherited from class Reference

new new, unsafe_construct(address : Pointer, *args, **opts) : self unsafe_construct

Class methods inherited from class Reference

pre_initialize(address : Pointer) pre_initialize

Instance methods inherited from class Object

! : Bool !, !=(other) !=, !~(other) !~, ==(other) ==, ===(other : JSON::Any)
===(other : YAML::Any)
===(other)
===
, =~(other) =~, as(type : Class) as, as?(type : Class) as?, class class, dup dup, hash(hasher)
hash
hash
, in?(collection : Object) : Bool
in?(*values : Object) : Bool
in?
, inspect(io : IO) : Nil
inspect : String
inspect
, is_a?(type : Class) : Bool is_a?, itself itself, nil? : Bool nil?, not_nil!(message)
not_nil!
not_nil!
, pretty_inspect(width = 79, newline = "\n", indent = 0) : String pretty_inspect, pretty_print(pp : PrettyPrint) : Nil pretty_print, responds_to?(name : Symbol) : Bool responds_to?, tap(&) tap, to_json(io : IO) : Nil
to_json : String
to_json
, to_pretty_json(indent : String = " ") : String
to_pretty_json(io : IO, indent : String = " ") : Nil
to_pretty_json
, to_s(io : IO) : Nil
to_s : String
to_s
, to_yaml(io : IO) : Nil
to_yaml : String
to_yaml
, try(&) try, unsafe_as(type : T.class) forall T unsafe_as

Class methods inherited from class Object

from_json(string_or_io : String | IO, root : String)
from_json(string_or_io : String | IO)
from_json
, from_yaml(string_or_io : String | IO) from_yaml

Macros inherited from class Object

class_getter(*names, &block) class_getter, class_getter!(*names) class_getter!, class_getter?(*names, &block) class_getter?, class_property(*names, &block) class_property, class_property!(*names) class_property!, class_property?(*names, &block) class_property?, class_setter(*names) class_setter, def_clone def_clone, def_equals(*fields) def_equals, def_equals_and_hash(*fields) def_equals_and_hash, def_hash(*fields) def_hash, delegate(*methods, to object) delegate, forward_missing_to(delegate) forward_missing_to, getter(*names, &block) getter, getter!(*names) getter!, getter?(*names, &block) getter?, property(*names, &block) property, property!(*names) property!, property?(*names, &block) property?, setter(*names) setter

Constructor Detail

def self.new(program, vars = MetaVars.new, typed_def = nil, meta_vars = nil) #

[View source]
def self.new(*, from_main_visitor : MainVisitor) #

[View source]

Class Method Detail

def self.check_automatic_cast(program, value, var_type, assign = nil) #

[View source]
def self.check_type_allowed_as_proc_argument(node, type) #

[View source]

Instance Method Detail

def all_exception_handler_vars : Array(MetaVars) | Nil #

[View source]
def all_exception_handler_vars=(all_exception_handler_vars : Array(MetaVars) | Nil) #

[View source]
def assign_to_meta_var(name, context = current_context) #

[View source]
def bind_block_var(node, target, meta_vars, before_block_vars) #

[View source]
def bind_initialize_instance_vars(owner) #

[View source]
def bind_meta_var(var : Var) #

[View source]
def bind_vars(from_vars, to_vars, ignored = nil) #

[View source]
def block : Block | Nil #

[View source]
def block=(block) #

[View source]
def block_nest : Int32 #

[View source]
def block_nest=(block_nest : Int32) #

[View source]
def call : Call | Nil #

[View source]
def call=(call : Call | Nil) #

[View source]
def call_needs_splat_expansion?(node) #

[View source]
def check_automatic_cast(value, var_type, assign = nil) #

See if we can automatically cast the value if the types don't exactly match


[View source]
def check_call_in_initialize(node) #

Checks if it's a call to self. In that case, all instance variables not mentioned so far will be considered nil.


[View source]
def check_closured(var, mark_as_mutably_closured : Bool = false) #

[View source]
def check_exception_handler_vars(var_name, node) #

[View source]
def check_initialize_instance_vars_types(owner) #

[View source]
def check_lib_call(node, obj_type) #

Fill function literal argument types for C functions


[View source]
def check_lib_call_arg(method, arg_index, &) #

[View source]
def check_mutably_closured(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.


[View source]
def check_not_a_constant(node) #

[View source]
def check_self_closured #

[View source]
def check_slice_literal_call(node, obj_type) #

[View source]
def check_special_new_call(node, obj_type) #

Checks if it's ProcType#new


[View source]
def check_super_or_previous_def_in_initialize(node) #

If it's a super or previous_def call inside an initialize we treat set instance vars from superclasses to not-nil.


[View source]
def check_type_in_type_args(type) #

[View source]
def closure_context #

[View source]
def conditional_no_return(node, var) #

[View source]
def convert_struct_or_union_numeric_argument(node, unaliased_type, expected_type, actual_type) #

[View source]
def current_context #

[View source]
def current_non_block_context #

[View source]
def define_special_var(name, value) #

[View source]
def end_visit(node : Splat) #

[View source]
def end_visit(node : DoubleSplat) #

[View source]
def end_visit(node : RespondsTo) #

[View source]
def end_visit(node : Break) #

[View source]
def end_visit(node : Next) #

[View source]
def end_visit(node : TupleLiteral) #

[View source]
def end_visit(node : NamedTupleLiteral) #

[View source]
def expand(node) #

[View source]
def expand(node, &) #

[View source]
def expand_named(node, generic_type) #

[View source]
def file_module : FileModule | Nil #

[View source]
def file_module=(file_module : FileModule | Nil) #

[View source]
def filter_vars(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.


[View source]
def filter_vars(filters, &) #

[View source]
def first_time_accessing_meta_type_var?(var) #

[View source]
def free_vars #
Description copied from class Crystal::SemanticVisitor

Returns free variables


[View source]
def fun_literal_context : Def | Program | Nil #

[View source]
def fun_literal_context=(fun_literal_context : Def | Program | Nil) #

[View source]
def gather_instance_vars_read(node) #

[View source]
def get_expression_var(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.


[View source]
def handle_unpacked_block_argument(node, arg, meta_vars, before_block_vars, ignored_vars_after_block) #

[View source]
def ignoring_type_filters(&) #

[View source]
def inside_block? #

[View source]
def inside_constant=(inside_constant : Bool) #

[View source]
def inside_constant? : Bool #

[View source]
def inside_ensure=(inside_ensure : Bool) #

[View source]
def inside_ensure? : Bool #

[View source]
def inside_while? #

[View source]
def is_initialize : Bool #

[View source]
def is_initialize=(is_initialize : Bool) #

[View source]
def last_block_kind : BlockKind #

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

[View source]
def last_block_kind=(last_block_kind : BlockKind) #

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

[View source]
def lookup_class_var(node) #

[View source]
def lookup_similar_class_variable_name(node, owner) #

[View source]
def lookup_similar_var_name(name) #

[View source]
def lookup_var_or_instance_var(var : Var) #

[View source]
def lookup_var_or_instance_var(var : InstanceVar) #

[View source]
def lookup_var_or_instance_var(var) #

[View source]
def mark_as_closured(var, var_context, mark_as_mutably_closured : Bool) #

[View source]
def match_context : MatchContext | Nil #

[View source]
def match_context=(match_context : MatchContext | Nil) #

[View source]
def merge_if_var(name, node, cond_vars, then_var, else_var, before_then_vars, before_else_vars, then_unreachable, else_unreachable) #

[View source]
def merge_if_vars(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)

[View source]
def merge_rescue_vars(body_vars, all_rescue_vars) #

[View source]
def merge_while_vars(endless, before_cond_vars_copy, before_cond_vars, after_cond_vars, after_cond_vars_nil_if_read, all_break_vars) #

Here we assign the types of variables after a while.


[View source]
def meta_vars : MetaVars #

Here we store the cumulative types of variables as we traverse the nodes.


[View source]
def needs_type_filters? #

[View source]
def new_meta_var(name, context = current_context) #

[View source]
def node_exp_or_nil_literal(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).


[View source]
def parent : MainVisitor | Nil #

[View source]
def parent=(parent : MainVisitor | Nil) #

[View source]
def path_lookup : Crystal::Type? #

[View source]
def path_lookup=(path_lookup) #

[View source]
def pointerof_var(node) #

[View source]
def prepare_call(node) #

[View source]
def recalculate_call(node : Call) #

[View source]
def replace_call_splats(node) #

[View source]
def request_type_filters(&) #

[View source]
def special_c_struct_or_union_new_with_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


[View source]
def the_self(node) #

[View source]
def type_assign(target : Var, value, node, restriction = nil) #

[View source]
def type_assign(target : InstanceVar, value, node) #

[View source]
def type_assign(target : Path, value, node) #

[View source]
def type_assign(target : Global, value, node) #

[View source]
def type_assign(target : ClassVar, value, node) #

[View source]
def type_assign(target : Underscore, value, node) #

[View source]
def type_assign(target, value, node) #

[View source]
def typed_def #

[View source]
def typed_def? : Crystal::Def? #

[View source]
def undefined_class_variable(node, owner) #

[View source]
def undefined_instance_variable(owner, node) #

[View source]
def untyped_def : Def #

[View source]
def untyped_def=(untyped_def : Def) #

[View source]
def untyped_def=(untyped_def) #

[View source]
def untyped_def? : Def | Nil #

[View source]
def vars : 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.


[View source]
def visit(node : FileNode) #

[View source]
def visit(node : Path) #

[View source]
def visit(node : Generic) #

[View source]
def visit(node : ProcNotation) #

[View source]
def visit(node : Union) #

[View source]
def visit(node : Metaclass) #

[View source]
def visit(node : Self) #

[View source]
def visit(node : Var) #

[View source]
def visit(node : TypeDeclaration) #

[View source]
def visit(node : UninitializedVar) #

[View source]
def visit(node : Out) #

[View source]
def visit(node : Global) #

[View source]
def visit(node : InstanceVar) #

[View source]
def visit(node : ReadInstanceVar) #

[View source]
def visit(node : ClassVar) #

[View source]
def visit(node : Expressions) #

[View source]
def visit(node : Assign) #

[View source]
def visit(node : Yield) #

[View source]
def visit(node : Block) #

[View source]
def visit(node : ProcLiteral) #

[View source]
def visit(node : ProcPointer) #

[View source]
def visit(node : Call) #

[View source]
def visit(node : Return) #

[View source]
def visit(node : Underscore) #

[View source]
def visit(node : IsA) #

[View source]
def visit(node : Cast | NilableCast) #

[View source]
def visit(node : FunDef) #

[View source]
def visit(node : If) #

[View source]
def visit(node : While) #

[View source]
def visit(node : TupleIndexer) #

[View source]
def visit(node : Primitive) #

[View source]
def visit(node : PointerOf) #

[View source]
def visit(node : TypeOf) #

[View source]
def visit(node : SizeOf) #

[View source]
def visit(node : InstanceSizeOf) #

[View source]
def visit(node : AlignOf) #

[View source]
def visit(node : InstanceAlignOf) #

[View source]
def visit(node : OffsetOf) #

[View source]
def visit(node : Rescue) #

[View source]
def visit(node : ExceptionHandler) #

[View source]
def visit(node : Asm) #

[View source]
def visit(node : Nop) #

[View source]
def visit(node : NilLiteral) #

[View source]
def visit(node : BoolLiteral) #

[View source]
def visit(node : NumberLiteral) #

[View source]
def visit(node : CharLiteral) #

[View source]
def visit(node : SymbolLiteral) #

[View source]
def visit(node : StringLiteral) #

[View source]
def visit(node : RegexLiteral) #

[View source]
def visit(node : ArrayLiteral) #

[View source]
def visit(node : HashLiteral) #

[View source]
def visit(node : And) #

[View source]
def visit(node : Or) #

[View source]
def visit(node : RangeLiteral) #

[View source]
def visit(node : StringInterpolation) #

[View source]
def visit(node : Case) #

[View source]
def visit(node : Select) #

[View source]
def visit(node : MultiAssign) #

[View source]
def visit(node : Not) #

[View source]
def visit(node : VisibilityModifier) #

[View source]
def visit(node : Arg) #

[View source]
def visit(node : AssignWithRestriction) #

[View source]
def visit(node : Unreachable) #

[View source]
def visit(node : When | Unless | Until | MacroLiteral | OpAssign) #

[View source]
def visit(node : ImplicitObj) #

[View source]
def visit_allocate(node) #

[View source]
def visit_any(node) #

[View source]
def visit_class_var(node) #

[View source]
def visit_pointer_malloc(node) #

[View source]
def visit_pointer_new(node) #

[View source]
def visit_pointer_set(node) #

[View source]
def visit_pre_initialize(node) #

[View source]
def visit_read_instance_var(node) #

[View source]
def visit_struct_or_union_set(node) #

[View source]
def visit_va_arg(node) #

[View source]
def with_block_kind(kind : BlockKind, &) #

[View source]
def with_scope : Type | Nil #

[View source]
def with_scope=(with_scope : Type | Nil) #

[View source]
def yield_vars : Array(Var) | Nil #

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.


[View source]
def yield_vars=(yield_vars : Array(Var) | Nil) #

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.


[View source]