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

Can't infer the type of class variable in extend-ed module #4066

Open
Sija opened this issue Feb 23, 2017 · 9 comments
Open

Can't infer the type of class variable in extend-ed module #4066

Sija opened this issue Feb 23, 2017 · 9 comments

Comments

@Sija
Copy link
Contributor

Sija commented Feb 23, 2017

module Foo
  @@foo : Bool?

  def foo?
    @@foo ||= true
  end
end

class Bar
  extend Foo
end

Bar.foo?

Maybe I don't fully understand how class variables work at module level but it seems to me that above code should work, instead it fails with error:

Error in line 13: instantiating 'Bar:Class#foo?()'

in line 5: Can't infer the type of class variable '@@foo' of Bar

The type of a class variable, if not declared explicitly with
`@@foo : Type`, is inferred from assignments to it across
the whole program.

The assignments must look like this:

  1. `@@foo = 1` (or other literals), inferred to the literal's type
  2. `@@foo = Type.new`, type is inferred to be Type
  3. `@@foo = Type.method`, where `method` has a return type
     annotation, type is inferred from it
  4. `@@foo = arg`, with 'arg' being a method argument with a
     type restriction 'Type', type is inferred to be Type
  5. `@@foo = arg`, with 'arg' being a method argument with a
     default value, type is inferred using rules 1, 2 and 3 from it
  6. `@@foo = uninitialized Type`, type is inferred to be Type
  7. `@@foo = LibSome.func`, and `LibSome` is a `lib`, type
     is inferred from that fun.
  8. `LibSome.func(out @@foo)`, and `LibSome` is a `lib`, type
     is inferred from that fun argument.

Other assignments have no effect on its type.

Can't infer the type of class variable '@@foo' of Bar

Might be related to #4039?

@asterite
Copy link
Member

asterite commented Mar 2, 2017

I think extend is a poor language feature. We should probably drop it and only allow inclusion via include, which works as multiple-inheritance.

@ysbaddaden
Copy link
Contributor

What do mean? Having include behave has both include + extend?

@luislavena
Copy link
Contributor

@asterite removing extend will break some usage for doing interfaces directly with classes (and not instances):

http://stackoverflow.com/a/42053962/117298

(I mean until Interface.class gets implemented)

I would like to know more why extend is poor from your perspective.

Thank you.

@asterite
Copy link
Member

asterite commented Mar 3, 2017

I think include + extend in Ruby aren't very intuitive. For example:

module Moo
  def self.moo
  end

  def moo
  end
end

class Foo
  include Moo
end

p Foo.new.moo # OK
p Foo.moo     # Error

I understand why the second line is an error, but when I use include I generally want to include all functionality from a type inside another type. That includes both instance and class methods.

I personally never use extend in Ruby unless I want to make all methods from a module be prefixed with def self. instead of writing that. But if that's the only reason extend exists then I'd like to remove it. You'll have to write def self. a bit more, but it's less things to know and learn, and a simpler language.

I think the issue mentioned in stack overflow can still be implemented, I'm not sure it's related to include vs. extend.

@luislavena
Copy link
Contributor

Thank you @asterite for your response, indeed include and extend sometimes presents some confusion, specially when coming from a non-Ruby background.

This decision will require solve things like abstract def self.something which are currently not possible (but extend does work for that).

Merging both concepts will definitely reduce that confusion area. Looking forward read the proposal.

Cheers.

@asterite
Copy link
Member

asterite commented Mar 3, 2017

abstract def self.something is impossible to implement, because classes always exist, a user can't explicitly instantiate them. That's why using classes as interfaces will probably never happen. What I mean is this:

module Moo
  abstract def self.moo
end

# Nothing prevents me from doing this:
Moo.moo

So abstract class methods probably don't make sense. In fact in no statically typed language there's such concept, only abstract instance methods.

@luislavena
Copy link
Contributor

abstract def self.something is impossible to implement

I hear you, valid points and something that needs to be solved since can be done right now:

module Interface
  module ClassMethods
    abstract def call
  end

  macro included
    extend ClassMethods
  end
end

class Action
  include Interface

  # uncomment this for compile to succeed
  # def self.call
  # end
end
Error in 22.cr:3: abstract `def Interface::ClassMethods#call()` must be implemented by Action:Class

    abstract def call
                 ^~~~

I have a few other cases where include and abstract don't play nice with each other, but will save them for a later trolling 😺

@straight-shoota
Copy link
Member

For reference: This has come up in another question on Stack Overflow: https://stackoverflow.com/questions/45631130/defining-class-vars-in-modules-in-crystal-causes-error

@lbarasti
Copy link
Contributor

lbarasti commented Nov 9, 2019

I had a similar issue, wanted to be able to call a method referencing a @@ variable from a class / module by including or extending a module.

A workaround that makes the compiler happy: in the parent module, define a method on self, then define an instance method calling it

module Foo
  @@foo : Bool?
 
  def foo?
    Foo.foo?
  end

  def self.foo?
    @@foo ||= true
  end
end
 
class Bar
  extend Foo
end
 
Bar.foo?

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

No branches or pull requests

6 participants