Proposal: wildcard method member `...` for classes with undeclared methods
I maintainer di solito rispondono entro 1 giorno
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 5/5
- Tempo stimato
- Più di una settimana
- Idoneità per principianti
- 15/100
Direzione di ricerca
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.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
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.
- Lingua principale
- Ruby
- Stelle
- 2.2k
- Fork
- 260
- Merge medio
- 22h 3m
- PR unite (30g)
- 54
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Nessun modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di ruby/rbs
-
InlineParser: `@rbs @ivar` annotation is reported as unused unless it is the first thing in the class bodyForse già presa @ksss l’ha presa 34 giorni fa. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 85/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 78/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 5/5 Più di una settimana Idoneità per principianti 35/100
I maintainer di solito rispondono entro 1 giorno
Issue simili
-
JWT::EncodedToken raises TypeError for a non-object headerForse già presa @anakinj l’ha presa oggi. Apertabug
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
jwt/ruby-jwt#780 · 1 reazione ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 Meno di un'ora Idoneità per principianti 70/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 82/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
zold-io/zold-ruby-sdk#85 ·