Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix access of class vars from generic metaclass #6348

Merged

Conversation

asterite
Copy link
Member

@asterite asterite commented Jul 6, 2018

Fixes #3719

Every type has a "class var owner": the type where a class var is stored, or which "owns" the class var. Metaclasses usually forward this responsibility to their instance type. Same goes with generic type instantiations. The problem happened with a metaclass of a generic instantiation: it should double forward the responsibility. So I just made sure to also ask the class var owner of the type we are forwarding to.

@ysbaddaden
Copy link
Contributor

Aparte: you should put the explanation right into the commit message, not just on GitHub PR; Git log or blame, for example, would be very enriched :-)

@asterite
Copy link
Member Author

asterite commented Jul 7, 2018

I'll do it, please don't merge yet 👍

@asterite asterite force-pushed the bug/3719-class_var_from_generic_metaclass branch from 8f837e5 to 9630c81 Compare July 7, 2018 13:57
@asterite
Copy link
Member Author

asterite commented Jul 7, 2018

Updated!

@asterite asterite force-pushed the bug/3719-class_var_from_generic_metaclass branch from 9630c81 to 1831f4e Compare July 7, 2018 13:58
@RX14 RX14 merged commit a441539 into crystal-lang:master Jul 8, 2018
@RX14 RX14 added this to the Next milestone Jul 8, 2018
@RX14 RX14 modified the milestones: Next, 0.26.0 Jul 30, 2018
@bcardiff
Copy link
Member

bcardiff commented Aug 1, 2018

So, after this change the code class vars are shared among all the instantiations

class Foo(T)
  @@counter = 0

  def self.inc
    @@counter += 1
  end

  def self.counter
    @@counter
  end
end

pp!({Foo.counter, Foo(Int32).counter, Foo(String).counter}) # => {0, 0, 0}
Foo.inc
pp!({Foo.counter, Foo(Int32).counter, Foo(String).counter}) # => {1, 1, 1}
Foo(Int32).inc
pp!({Foo.counter, Foo(Int32).counter, Foo(String).counter}) # => {2, 2, 2}
Foo(String).inc
pp!({Foo.counter, Foo(Int32).counter, Foo(String).counter}) # => {3, 3, 3}

I came late to the discussion (holidays \o/) but I think a more proper behaviour would have been to let Foo(Int32) and Foo(String) have their own class variables and disable Foo without instantiation here. Foo could be used as a namespace component without instantiation as it can be used today.

With the above proposed behavior user can share state among metaclasses instance with a dummy type and isolate state by declaring class vars directly inside the generic type.

I found that would be even more consistent with respect non generic class hierarchy.

class Bar
  @@counter = 0

  def self.inc
    @@counter += 1
  end

  def self.counter
    @@counter
  end
end

class Baz < Bar
end

class Qux < Bar
end

pp!({Bar.counter, Baz.counter, Qux.counter}) # => {0, 0, 0}
Bar.inc
pp!({Bar.counter, Baz.counter, Qux.counter}) # => {1, 0, 0}
Baz.inc
pp!({Bar.counter, Baz.counter, Qux.counter}) # => {1, 1, 0}
Qux.inc
pp!({Bar.counter, Baz.counter, Qux.counter}) # => {1, 1, 1}

@bcardiff
Copy link
Member

bcardiff commented Aug 4, 2018

@ony
Copy link

ony commented Dec 17, 2018

@bcardiff , I agree that this a good question to refer to language ideas. Which are a bit unclear for me after point of diverging from Ruby. With this PR it looks to be Java-like approach (any "class" regardless of it being generic or specialized represent single entity) and refer to Foo(Int32) vs Foo(string) as a single class though instances of it being annotated with different types (like phantom types) to carry additional information.
It still can be justified. Similar as in Java you can say that as soon as you create a specialized class (class FooInt32 < Foo(Int32)) you'll get unique treatment. Though I somehow prefer "templates" from C++ over "generics". And I was a bit surprised to find absence of "templates" in Crystal while having compile-time macros and implicit union types.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants