Skip to content

Compiler Internals: Instance Variables

Johannes Müller edited this page Jan 26, 2022 · 1 revision

From https://github.com/crystal-lang/crystal/issues/11673#issuecomment-1020368147

  • instance variables of modules are just used to compute what instance variables classes and structs will have
  • in a hierarchy, an instance variable belongs to the top-most type that defines it

So for instance if we have:

class A 
  @a = 1
end

class B < A
  @b = 1
end

We have that A holds @a : Int32 and B holds @b : Int32. Also B holds @a : Int32 through A, but that information is kept in A, not in B.

Let's say we have this:

class A 
  @a = 1
end

class B < A
  @a = 3
  @b = 1
end

In this case there's also @a : Int32, but the end result is the same as before: @a : Int32 belongs to A, B knows nothing about it directly (it knows about the initializer, though)

To be able to do this the compiler will analyze type hierarchies starting from root types and going downwards.

This... kind of falls apart with modules!

What Crystal does right now is to analyze modules first, before classes and structs. If a module mentions an instance variable, that instance variable is propagated to inherited types.

So in a code like this:

module Moo
  @a = 1
end

class Foo
  @b = 2
end
  • We first process Moo, and then we know that Foo needs to have @a : Int32
  • Then we process Foo and learn that it also has @b : Int32

The end result is that Foo will have:

  • @a : Int32
  • @b : Int32

So far, so good.

But with this example

module M
  @a = 1
end

abstract class A
  @a = 1
end

class B < A
  include M
  @b = 1
end

B.new
  1. We first process M and we learn that B needs to have @a : Int32
  2. We then process A and learn that it needs to have @a : Int32
  3. We then process B and learn that it needs to have @b : Int32