Proposal: wildcard method member `...` for classes with undeclared methods
Los mantenedores suelen responder en 1 día
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 5/5
- Tiempo estimado
- Más de una semana
- Aptitud para principiantes
- 15/100
Línea de trabajo
This is an open design proposal with no comments or decision yet, so agree on the semantics before writing code. The issue names DefinitionBuilder (keep the :"..." method in Definition#methods and add a fallback accessor), rbs validate and DefinitionBuilder (reject ... inside interface), rbs test (skip it), and docs/syntax.md. Done means those semantics are settled by the maintainers and each listed change is covered by tests.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
Motivation
RBS requires every method of a class to be listed. For some classes that is impossible, and for others it is not worth doing yet, even though the class itself is well known:
- Classes whose methods are defined by metaprogramming (
define_methodover a schema, attribute DSLs,ActiveSupport::Concern). The methods exist at runtime, but RBS has no way to say "this class has more methods than the ones written here", so every call is aNoMethoderror unless all of them are enumerated by hand or by a generator. - Inherently dynamic classes such as
OpenStructandHashie::Mash, whose member set is runtime data. - Classes whose methods could be enumerated, but where doing so is not worth it yet. With
def `...`: (?) -> untypeda class can be typed gradually: declare the methods that matter, leave the rest untyped, and narrow later by addingdefs. Today the only options are "list everything" or "nothing".
In the first two cases we usually know the type of the unknown members (untyped, Hashie::Mash, String, ...) but not their names; in the third we know the names and choose not to write them yet. Opening the class does not help with either: it is global, and it still needs the names.
This differs from #422, which asks for forwarding to another object whose method names are known.
Proposal
Give a special meaning to a method named ...: it is the type of every method that is not declared.
class Config
def initialize: (Hash[Symbol, untyped]) -> void
def `...`: () -> String # any other method returns String
end
class Hashie::Mash
def `...`: () -> Hashie::Mash # mash.foo
| (untyped) -> untyped # mash.foo = 1
end
class OpenStruct
def `...`: (?) -> untyped # arguments and result unknown
end
class Model
def self.`...`: (*untyped) -> Array[Model] # singleton side
end
Semantics:
- Looking up an undeclared method
mon the instance (or singleton) definition resolves to the method types of.... Arguments and blocks are checked against them as usual. - A declared method always wins. Adding a
defnarrows the type; the wildcard covers only what is not written. No compatibility between declared methods and the wildcard is required, since RBS lookups are always by a static name. ...propagates like any method: to subclasses, throughinclude/prepend(instance side) andextend(singleton side), and can be overridden....does not participate in subtyping. A class withdef `...`: () -> Stringdoes not satisfy an interface requiringdef name: () -> String; otherwise such a class would be a subtype of almost everything....is not allowed ininterface. An interface describes which names an object responds to; "and unknown others" has no meaning there. This is permanent, not a v1 limitation.
No new syntax is required
Backtick-quoted method names already accept .... On current master (4.3.0.dev):
| Input | Result |
|---|---|
def `...`: () -> String |
parses, name == :"...", kind == :instance |
def self.`...`: () -> String |
parses, kind == :singleton |
def `...`: () -> A | (?) -> untyped |
parses, overloads work; round-trips through RBS::Writer |
def ...: () -> String (bare) |
syntax error (pDOT3) |
So the change is purely semantic: parser, writer, C and Rust are untouched. :"..." becomes a reserved method name, in the same way DefinitionBuilder already treats initialize specially to synthesize new. Ruby cannot def a method named ... (syntax error), so nothing real collides. Because the bare form is currently an error, it can be added as sugar later without compatibility concerns.
Changes in rbs
DefinitionBuilder: keep:"..."inDefinition#methodsso inheritance, mixins and overriding work unchanged; add an accessor such asDefinition#fallback_methodso consumers need not know the magic name.- Reject
def `...`insideinterfaceinrbs validateandDefinitionBuilder(it currently parses). rbs test: skip:"..."instead of trying to hook it.- Document the member in
docs/syntax.md.
For type checkers (informative, Steep as an example): on lookup failure, use ... instead of reporting NoMethod; hide it from completion and from MethodDefinitionMissing.
Non-goals
- Forwarding to another object (#422): needs the target's names, separate feature.
- Name-dependent types (
:name→String,:age→Integer):...discards the name. If wanted later, typingmethod_missingis the natural place, and it composes with this proposal. - Closing the wildcard in a subclass: RBS cannot remove methods either.
Alternatives considered
- A class-level annotation like
%a{incomplete}("unknown methods are(?) -> untyped"). Needs the sameDefinitionBuilderwork plus new propagation rules, and cannot express a result type.def `...`: (?) -> untypedcovers it. - Using the declared type of
method_missing. For metaprogrammed classesmethod_missingis never called, andBasicObject#method_missingexists everywhere, so an opt-in would still be needed. - Code generation (
rbs prototype runtime, rbs_rails, orthoses). Complementary; this is for when names cannot be computed or generation is not worth it.
Prior art with the same "unknown name, known type" shape: TypeScript index signatures (which likewise do not satisfy named property requirements), Haxe Dynamic<T>, Python __getattr__ -> T, Swift @dynamicMemberLookup.
- Lenguaje dominante
- Ruby
- Estrellas
- 2.2k
- Forks
- 260
- Merge medio
- 22 h 3 min
- PR fusionados (30 d)
- 54
Preparar el entorno
- Sin Dockerfile ni archivo de Docker Compose
- Sin plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de ruby/rbs
-
InlineParser: `@rbs @ivar` annotation is reported as unused unless it is the first thing in the class bodyPosiblemente ocupada @ksss la tomó hace 35 días. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 82/100
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 85/100
Los mantenedores suelen responder en 1 día
-
Dificultad 1/5 Menos de una hora Aptitud para principiantes 78/100
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
Los mantenedores suelen responder en 1 día
-
Dificultad 5/5 Más de una semana Aptitud para principiantes 35/100
Los mantenedores suelen responder en 1 día
Issues similares
-
area/web interface
Dificultad 2/5 1-3 horas Aptitud para principiantes 65/100
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 85/100
rubys/roundhouse#1757 ·
-
Dificultad 1/5 Menos de una hora Aptitud para principiantes 88/100
akitaonrails/ai-book-2026#12 ·
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 76/100
openfoodfoundation/openfoodnetwork#14970 ·
Los mantenedores suelen responder en 1 día
-
JWT::EncodedToken raises TypeError for a non-object headerPosiblemente ocupada @anakinj la tomó hoy. Abiertobug
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
jwt/ruby-jwt#780 · 1 reacción ·
Los mantenedores suelen responder en 1 día