Unclear semantics of def's inside a module or class

Consider this class:

class Test1
  puts self.t # => 2

  def self.t
    2
  end
end

It does print 2, even though the function is defined after the call. Since methods are not executed immediately, I see how it’d make sense if they were declared before any code in the class/module is run.

So, my question originally was what would happen if I tried to use any of the non-method code inside one of the methods. I came up with the following to completely confuse myself:

class Test
  @@val = f
  @@val = 4

  def self.f
    @@val
  end

  def self.val
    @@val
  end
end

puts Test.val # => 4

This example does print 4. But I don’t understand how it does that given that the code is not organised in the following order:

  1. Initialise @@val to 4
  2. Declare self.f which returns @@val
  3. Call f and assign the result to @@val

How does the compiler distinguish between @@val=4 and @@val=f? Are methods really declared before any code is run, or does the compiler rely on the semantic stage to make sure the methods are present? Is there anything else I’m missing?

Thanks

Class (and instance) variable initialization is done in order. In your example f is assigned first, then 4 is assigned later, so 4 is the value that stays. Method definition is done in a pass previous to class (and instance) variable initialization (this is called “hoisting”).

Oh, to be a bit more clear: the compiler only stores the last assignment to a class variable. The line @@val = f is never reached (or even compiled) by the compiler. Check this:

class Test
  @@val = f
  @@val = 4

  def self.f
    this is never compiled
    @@val
  end

  def self.val
    @@val
  end
end

puts Test.val # => 4

https://play.crystal-lang.org/#/r/623c

2 Likes

Oh, that makes a lot of sense. Does this transformation happen at the semantic AST normalisation stage?

Also, does this only apply to class and instance variables or is it the general semantics for any code written in a class/module but outside of any methods?

The logic only applies to class and instance variables. That said, it’s the way it’s currently implemented, it’s not documented and it could change in the future.