Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Add type arguments support to singleton types #2502

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

allcre
Copy link
Contributor

@allcre allcre commented May 20, 2025

Add support for parameterized singleton types

This PR adds support for type parameters on singleton types to match the functionality available in Sorbet's T.class_of(X)[Y] syntax. With this change, RBS now supports the equivalent syntax: singleton(X)[Y].

Changes

  • Updated the RBS grammar to allow type arguments for singleton types
  • Modified the C parser implementation to accept type parameters for singleton types

Examples

Previously, only this was supported:

singleton(Array)   # Class singleton type with no type parameters

Now this is also supported:

singleton(Array)[String]   # Class singleton type with String type parameter

Questions

  1. Should the Application module be included in the ClassSingleton class, similar to how it's included in ClassInstance and Interface? Currently, I've implemented the necessary methods directly.

  2. Are there additional methods such as map_type and each_type that should be added to the ClassSingleton class?

@ParadoxV5
Copy link
Contributor

@soutaro Did singleton(Array)[String] (or singleton(Array[String]), whatever) make sense in the first place?

@allcre allcre force-pushed the Add_type_arguments_support_to_singleton_types branch 5 times, most recently from 06d9ce0 to 46c9aa2 Compare May 21, 2025 15:48
Previously, singleton type arguments could not be supported by RBS. This
adds support for them, enabling syntax like
`singleton(Array)[String, Integer]`.
@allcre allcre force-pushed the Add_type_arguments_support_to_singleton_types branch from 46c9aa2 to 91eafe8 Compare May 21, 2025 15:53
@allcre allcre marked this pull request as ready for review May 21, 2025 16:00
@soutaro
Copy link
Member

soutaro commented May 27, 2025

@allcre Can you give me some examples why we need that type? I'm assuming singleton(Array) is a singleton type -- ::Array is the only one value of the type -- and we don't need generic type for it.

@soutaro soutaro self-assigned this May 27, 2025
@Morriar
Copy link
Contributor

Morriar commented Jun 3, 2025

@soutaro we use it in Sorbet to represent the type of the attached class to a singleton.

It's useful around factories, here's a simple example:

class Box
  extend T::Sig
  extend T::Generic
  
  E = type_member

  sig { params(e: E).void }
  def initialize(e)
    @e = e
  end

  sig { returns(E) }
  def e
    @e
  end
end

extend T::Sig

sig { returns(T.class_of(Box)[Box[Integer]]) }
def example
  Box
end

x = example
T.reveal_type(x) # => T.class_of(Box)[Box[Integer]]
x.new("str") # error: Expected `Integer` but found `String` for argument `e`

More involved examples can be found in the documentation: https://sorbet.org/docs/class-of#tclass_of-applying-type-arguments-to-a-singleton-class-type.

@ParadoxV5
Copy link
Contributor

RBS expects us to genericize class methods because it only recognizes type parameters at the instance level.

class Set[E]
  def self.[]: [E] (*E elements) -> Set[E] # `E` is totally not duplicated
end

Honestly, that might be a flawed design:

class Set[E]
  def self.[]: (*untyped elements) -> instance # `instance` is `Set[untyped]`!
end

@soutaro
Copy link
Member

soutaro commented Jun 4, 2025

Thanks @Morriar,

I got the use case and agree that it cannot be written in RBS now. Using an interface would be a workaround, but not sure if it can cover the existing use cases.

Let me confirm the semantics: the type singleton(T)[S, ...] means that a class object of T but the instance created through the value of the type is T[S, ...]. Looks like it makes some sense, except if it needs a big overhaul in RBS (and Steep).

What should we do for the other singleton methods?

class Box[T]
  def initialize: (T) -> void

  def self.new_array: [T] (T) -> Box[Array[T]]
end

b = Box #: singleton(Box)[String]
b.new("")           # => Box[String]
b.new(1)            # => type error
b.new_array(1)      #=> ???

Looks like it only works for .new method in Sorbet?

@ParadoxV5
Copy link
Contributor

What should we do for the other singleton methods?

How about changing the semantics so that type parameters apply on the class level as well?
It would resolve my #1521.

class Box[T]
  def self.new_array: (T) -> Box[Array[T]] # No need to genericize the method separately
  def self.[]: (T) -> instance # `instance` was `Box[untyped]`, but now `Box[T]`
end

@soutaro
Copy link
Member

soutaro commented Jun 6, 2025

@ParadoxV5 Yeah, it would make sense. But, how can we associate the type parameter T in self.[] and the type parameter given to the singleton class. singleton(Box)[T]? Can we do that by adding another type instance[T]?

@ParadoxV5
Copy link
Contributor

singleton(Box)[T] should probably be singleton(Box[T]) rather, so that singleton(Box[Integer])#[]Box[Integer].[]T in self.[] is Integer.

Allowing instance[T] is an option to, as I requested in #1521 long ago.
Its flexibility would enable def self.new_array: (T) -> instance[Array[T]].

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

Successfully merging this pull request may close these issues.

4 participants