Skip to content

Allow singleton(C) as module self type constraint - #3267

Open
ksss wants to merge 2 commits into
ruby:masterfrom
ksss:module-self-type-singleton
Open

ksss wants to merge 2 commits into
ruby:masterfrom
ksss:module-self-type-singleton

Conversation

@ksss

@ksss ksss commented Nov 2, 2026

Copy link
Copy Markdown
Collaborator

This PR implements proposal 1 of #1334: singleton(C) is allowed as a module self type constraint, so that a module extended to a class (e.g. Rails' ClassMethods pattern) can declare the class methods it depends on without copying them into an interface.

class Model
  def self.find: (Integer) -> instance
end

module ClassMethods : singleton(Model)
  def find_or_raise: (Integer) -> Model   # can call `find` of `singleton(Model)`
end

class User < Model
  extend ClassMethods
end

Changes

  • Syntax: module Foo : singleton(Bar) and singleton(Bar)[T] (type arguments are optional, as in the class singleton type). Documented in docs/syntax.md.
  • AST: new node RBS::AST::Declarations::Module::SingletonSelf (name, args, location). Module::Self is unchanged, so existing consumers keep working. Module#self_types is now Array[Module::Self | Module::SingletonSelf]. Both classes get #update.
  • Ancestors: singleton self types become Definition::Ancestor::Singleton (with a new optional source attribute) in OneAncestors#self_types. The recursive ancestor check goes through singleton_ancestors, consistent with the existing check for class instance self types (module M : Foo + class Foo; include M is an error today, and so is module M : singleton(C) + class C; extend M).
  • Definitions: build_instance(Foo) merges build_singleton(Bar) (methods and class instance variables). Methods defined in Foo take precedence. Method types are taken as written: instance/class/self are not rewritten, consistent with class instance self types, and resolving them is left to the type checker.
  • Validation: singleton(_Interface) is rejected by the parser and by NoSelfTypeFoundError.check!.

Not included

Proposal 2 of #1334 (singleton(M) < singleton(C) when module M : singleton(C)) is intentionally not included. The self inside the body of M is a Module object at runtime and does not respond to the class methods of C, so treating singleton(M) as a subtype of singleton(C) would be unsound.

Also out of scope: the inline # @rbs module-self: annotation, and the Steep side (type_construction.rb converts module_entry.self_types to types by name.interface?/name.class?, so it needs a branch for SingletonSelf → Types::ClassSingleton).

🤖 Generated with Claude Code

ksss and others added 2 commits October 28, 2026 13:43
Allow `module Foo : singleton(Bar)` (optionally with type arguments,
`singleton(Bar)[T]`) in addition to class instance and interface self
types. The new syntax is represented by a new AST node,
`RBS::AST::Declarations::Module::SingletonSelf`, so that existing
consumers of `Module::Self` keep working unchanged.

- parser: accept `singleton` `(` class_name `)` type_args? in
  `parse_module_self_types`
- AST: add `Module::SingletonSelf` (C node generated from config.yml) and
  `#update` to `Module::Self`/`SingletonSelf`
- environment: resolve type names and align type parameters of singleton
  self types through `#update`
- docs: describe the syntax in docs/syntax.md

Part of ruby#1334 (proposal 1).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`module Foo : singleton(Bar)` now contributes the singleton methods (and
class instance variables) of `Bar` to the instance definition of `Foo`,
the same way class instance self types contribute instance methods.

- ancestor_builder: push `Definition::Ancestor::Singleton` (with the new
  `source` attribute) for singleton self types, and check for recursive
  ancestors through `singleton_ancestors`, consistent with the existing
  check for class instance self types
- definition_builder: merge `build_singleton(Bar)` into the module
  definition; methods defined in the module take precedence
- errors: `NoSelfTypeFoundError.check!` rejects `singleton(_Interface)`

Method types are taken as written: `instance`/`class`/`self` in the
singleton methods are not rewritten, consistent with class instance self
types. Resolving them is left to the type checker.

Part of ruby#1334 (proposal 1).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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.

1 participant