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

Visibility modifies for types #2950

Closed
asterite opened this issue Jul 3, 2016 · 15 comments
Closed

Visibility modifies for types #2950

asterite opened this issue Jul 3, 2016 · 15 comments

Comments

@asterite
Copy link
Member

asterite commented Jul 3, 2016

Right now private and protected can't be applied to types, only to methods, but it would be nice if we could do so.

file-private types

Similar to file-private methods and macros:

# This class can only be used in this file
private class Foo
end

# This will find the Foo above
Foo.new

# This will find the public Foo, not the private one
::Foo.new

private types

A private type can only be found from within the type where it's contained, and without "prefixes" (::):

class Foo
  private class Bar
  end

  def foo
    Bar.new  # OK
    Foo::Bar # Error, can only be accessed with plain `Bar`
  end

  class Baz
    Bar # OK
    Foo::Bar # Error
  end
end

Foo::Bar # Error

protected types

A protected type can be found from within the type where it's contained, and allows "prefixes" (::):

class Foo
  private class Bar
  end

  def foo
    Bar.new  # OK
    Foo::Bar # OK
  end

  class Baz
    Bar # OK
    Foo::Bar # OK
  end
end

Foo::Bar # Error

This is an RFC so we can discuss the behavior.

I'm not sure about the difference between private and protected types, but I feel the same about private and protected methods (I think "protected" is enough for all cases). Maybe we can remove "protected" from the language and make "private" have the current meaning of "protected".

@rmosolgo
Copy link
Contributor

rmosolgo commented Jul 4, 2016

So much 👍 for the idea in general! private_constant in Ruby is an awkward approach to this language feature.

Personally, my favorite usage is what you described in "protected constant" where the constant may be accessed "bare" (Bar) or with :: (Foo::Bar).

@bcardiff
Copy link
Member

bcardiff commented Jul 4, 2016

I think types should always be accessible by a fully qualified name. For private types, the restrictions should be that only inside the container type the private one is accessible (without :: or with the full name). I think there might be chances of ambiguity that could be resolved with fully qualified name and we might get stuck if we don't allow them.

@bcardiff
Copy link
Member

bcardiff commented Jul 4, 2016

Between private/protected contained type, maybe private should be visible only in the same file.

@ysbaddaden
Copy link
Contributor

Types as project internal implementation details is great! I regularly create types with :nodoc: and it would make things cleaner and better for public APIs.

But what if the types above grow a little big, or we add more types that need Bar? we'd like to extract them to their own file, but we can't if we don't want to lose the visibility, or we'd have to reintroduce the :nodoc: trick...

@jhass
Copy link
Member

jhass commented Jul 4, 2016

Keep in mind we need simple rules that cover a wide variety of cases in a consistent and obvious manner, I tried to start a list of possible cases, yet that doesn't even include reopening private or protected types (within or in a new file), nor adding private or protected types to a reopened type from a new file, which adds about another 60 or so cases easily.

private class A; end
protected class B; end

module Mod
  private class C; end
  protected class D; end
end

class Parent
  include Mod
  private class E; end
  protected class F; end
end

module Mod2
  private class G; end
  protected class H; end
end

module Mod3
  include Mod2
end

A.new         # Case 1
B.new         # Case 2
Mod::C.new    # Case 3
Mod::D.new    # Case 4
Parent::C.new # Case 5
Parent::D.new # Case 6
Parent::E.new # Case 7
Parent::F.new # Case 8
Mod3::G.new   # Case 9
Mod3::H.new   # Case 10

class Child < Parent
  include Mod2

  def initialize
    A.new         # Case 11
    B.new         # Case 12
    C.new         # Case 13
    D.new         # Case 14
    Mod::C.new    # Case 15
    Mod::D.new    # Case 16
    E.new         # Case 17
    F.new         # Case 18
    Parent::E.new # Case 19
    Parent::F.new # Case 20
    G.new         # Case 21
    H.new         # Case 22
    Mod2::G.new   # Case 23
    Mod2::H.new   # Case 24
    Mod3::G.new   # Case 25
    Mod3::H.new   # Case 26
  end
end

class Child2 < Parent
  include Mod3

  def initialize
    G.new # Case 27
    H.new # Case 28
  end
end

# New file
require "first_file"

A.new         # Case 29
B.new         # Case 30
Mod::C.new    # Case 31
Mod::D.new    # Case 32
Parent::C.new # Case 33
Parent::D.new # Case 34
Parent::E.new # Case 35
Parent::F.new # Case 36
Mod3::G.new   # Case 37
Mod3::H.new   # Case 38

class Child3 < Parent
  include Mod2

  def initialize
    A.new         # Case 39
    B.new         # Case 40
    C.new         # Case 41
    D.new         # Case 42
    Mod::C.new    # Case 43
    Mod::D.new    # Case 44
    E.new         # Case 45
    F.new         # Case 46
    Parent::E.new # Case 47
    Parent::F.new # Case 48
    G.new         # Case 49
    H.new         # Case 50
    Mod2::G.new   # Case 51
    Mod2::H.new   # Case 52
    Mod3::G.new   # Case 53
    Mod3::H.new   # Case 54
  end
end

class Child4 < Parent
  include Mod3

  def initialize
    G.new # Case 55
    H.new # Case 56
  end
end


# New file
require "first_file"
include Mod
C.new      # Case 57
D.new      # Case 58
Mod::C.new # Case 59
Mod::D.new # Case 60


# New file
require "first_file"
include Mod3
G.new       # Case 61
H.new       # Case 62
Mod3::G.new # Case 63
Mod3::H.new # Case 64

@refi64
Copy link
Contributor

refi64 commented Jul 4, 2016

What if the types are private on the module level, not the file level? e.g.

module X
  private class Y
  end
end

X::Y # error

module X
  class Z < Y # OK
  end
end

It would be more in line with how class private variables work.

@asterite
Copy link
Member Author

asterite commented Jul 4, 2016

I don't think that many cases need to be considered. Only the target type and the location where the type is requested need to be analyzed. For protected it means "inside the same hierarchy or namespace", similar to protected methods. It's for private (non-top-level) types I'm not sure about the semantics.

I think the best thing to do here is for me to prepare a PR with specs and we can discuss on real cases, and try new cases and see how they behave, and if they should behave like that.

@david50407
Copy link
Contributor

Are private types in class also a file-private types? (I wish no, that we can continue modifying these classes in other files like what we do in Ruby or Crystal)

I think private/protected types are useful that C# and Java also have this feature.

@asterite
Copy link
Member Author

They would work similar to methods: a private top-level type is private to the file. A private/protected type inside another type is visible inside that namespace, but not outside it. I'm still having issues thinking what would the difference between private and protected be, and whether we need two visibility modifiers.

@david50407
Copy link
Contributor

In Java, protected inner class can only be visible in the same package, and private is only for the current class (outer class). (ref)

So maybe we can restrict protected types can only be seen in the same namespace (class, module) and the sub-namespaces, and private only be seen in the same namespace.

For example, the table below will show what can we see under other classes if A::B has different modifier:

global A (A::B) A::B::C A::Z X
default O O O O O O
protected X O O O O X
private X O O O X X

@david50407
Copy link
Contributor

And what if I do #class on a private type or call .class.new ?

Like this:

class Foo
  private class Bar
  end

  def self.foo
    Bar.new
  end
end

Foo.foo #=> instance of Foo::Bar or cannot access?
Foo.foo.class #=> what will happend?
Foo.foo.class.new #=> what will happend?

@oprypin
Copy link
Member

oprypin commented Sep 9, 2016

Now it is possible to do what I've always wanted: private macros that work not just for one file.

module My
  private module Macros
    macro mac(a)
      7 + {{a}}
    end
  end
end
module My
  def self.test
    Macros.mac(5)
  end
end

p My.test

But it is possible only indirectly, macros still remain a black sheep, because private macro is invalid. Why not allow private macro in modules?

@asterite
Copy link
Member Author

asterite commented Sep 9, 2016

@BlaXpirit You mean something like this? (doesn't work, just to understand):

class Foo
  private macro foo
  end

  foo # OK
end

Foo.foo # Error

@oprypin
Copy link
Member

oprypin commented Sep 9, 2016

@asterite, I thought my example would explain it. Something like this:

module My
  private macro mac(a)
    7 + {{a}}
  end
end
require "./the_file_above"

module My
  def self.test
    mac(5) # OK
  end
end

p My.test

My.mac(1) # Error

Though your example is not necessarily different from what I want (in fact, it would make a lot of sense and be consistent). I just have a specific use case with modules.

@makenowjust
Copy link
Contributor

@BlaXpirit I implemented this in #3747.

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

9 participants