RFC: Class hierarchies in WebAPI, and developing WebAPI, React and the compiler together
Les mainteneurs répondent en général sous 1 jour
Personne n'a encore pris cette issue.
Évaluation
- Difficulté
- 5/5
- Temps estimé
- Plus d'une semaine
- Accessibilité débutants
- 25/100
- Type d'issue
- Fonctionnalité
- Clarté
- Clairement spécifiée
- Activité
- Active
- Stack technique
- javascript, ocaml
- Domaine
- backend-api-design
Piste de recherche
Start by reading the RFC’s sections on the compiler proposal, especially A–C, then inspect the compiler’s type attributes and untagged variant handling; the issue does not name specific source files or tests. The work spans compiler semantics, WebAPI bindings and React bindings, and includes an unresolved decision about mutable fields on private records. Done means the team has settled the proposals and implemented and validated the compiler and binding changes across the stated development sequence.
Rédigé par le modèle d'indexation à partir du texte de l'issue.
Description
Summary
ReScript v13 lets a type say which JavaScript class its values are instances of, and an untagged variant then tells its cases apart with instanceof, subclasses first. WebAPI is the obvious user of this: it could replace its hand-written isInstanceOf / classify / decode helpers with plain untagged variants, and its %identity upcasts with :>.
Two small compiler features are missing for WebAPI's style of bindings (classes as records), and a third needs a decision. This RFC proposes them. It also proposes how to develop WebAPI, the React bindings and the compiler together while these features settle, and asks the WebAPI team for feedback on both.
What the compiler does today (v13)
A type declares its runtime class with @instanceOf, or its typeof with @typeOf:
@instanceOf("Animal") type animal
@instanceOf("Dog") type dog = private animal // Dog is a subclass of Animal
- A subclass is a private abbreviation of its superclass:
(dog :> animal)upcasts, but ananimalis never taken for adog. - An untagged variant tests its cases in a safe order: literals first, then
typeofprimitives, then classes, subclasses before their superclasses, then functions and objects:
@unboxed type pet = Dog(dog) | Animal(animal) | Name(string)
if (typeof pet === "string") { ... }
else if (pet instanceof Dog) { ... }
else { ... } // Animal: the last case of a complete match is not checked
- A class introduced on an abbreviation must be
private, so two names for one type can't claim two different classes. That is how the stdlib's typed arrays became distinct types in v13 (#9050):Int8Array.tisprivate TypedArray.t<int>.
Why this matters for WebAPI
With class annotations, WebAPI could:
- drop the hand-written classification helpers (
isInstanceOfvia%raw,Element.classify,FillStyle.decode) in favour of untagged variants that the compiler checks and generates; - write sound unions such as
blobPart = String | Blob | ArrayBuffer | TypedArray. TheArrayBufferand typed-array cases are commented out today; they work now that typed arrays are distinct types; - upcast with
:>instead of%identity. This already works today for private records (see below).
What doesn't work yet
WebAPI models an interface as a record of its fields, inherited fields included, with methods coming from Impl functors:
type blob = {size: float, @as("type") type_: string}
type file = {...blob, name: string, lastModified: float}
Three things stand between that and class annotations:
@instanceOfcan't go on a record definition.@instanceOf("Blob") type blob = private {...}is rejected ("only abstract types and type abbreviations"). The workaround of a fields record plus@instanceOf("Blob") type blob = private blobFieldsdoesn't help either: fields can't be read through a private abbreviation, sob.sizestops compiling.- Record inheritance isn't visible to the compiler.
filecontainsblob's fields but isn't an abbreviation ofblob, so nothing says File is a Blob. InBlob(blob) | File(file)theinstanceof Blobcheck would come first and take every File. - Records carrying a class must be private, or
{size: 1.}would type as abloband be dispatched as one. WebAPI records have mutable fields (element.id,innerHTML, …). Writing a mutable field of a private record is warning 111 since ReScript 13.0.0-alpha.5 (#8366), so every DOM property write would warn. This is probably why the WebAPI sources still say "TODO: mark as private once mutating fields of private records is allowed".
What already works: record coercion between private records. With type blob = private {size: float} and type file = private {...blob, name: string}, (file :> blob) compiles to plain file, so no %identity is needed.
Proposal: compiler side
A. @instanceOf on private record definitions. The record type becomes a class case, dispatched with instanceof, and its fields stay readable. It must be private, by the same rule as above.
B. An explicit superclass: @extends.
@instanceOf("Blob")
type blob = private {
size: float,
@as("type") type_: string,
}
@instanceOf("File") @extends(: blob)
type file = private {
...blob,
name: string,
lastModified: float,
}
let describe = (f: file) => `${f.name}: ${Float.toString(f.size)} bytes` // fields readable
let asBlob = (f: file) => (f :> blob) // plain upcast
@unboxed
type blobPart =
| Text(string)
| Blob(blob)
| File(file)
| Buffer(ArrayBuffer.t)
| Bytes(Uint8Array.t)
if (typeof part === "string") { ... }
else if (part instanceof File) { ... } // before Blob, because of @extends
else if (part instanceof Blob) { ... }
else if (part instanceof ArrayBuffer) { ... }
else { ... }
@extends(: blob)names a type, so it works across modules (@extends(: Blob.t)) and inside recursive type groups. That matters for the DOM, which is largely one recursive group where record spreads can't be used. There, the generator writes the inherited fields out and adds@extends.- The compiler would check that the named type is a class, and that the record has all its fields with the same types, so that
:>is valid. It would report a missing@instanceOf, a non-class supertype, missing fields and cycles as errors. - Inferring the superclass from a record spread (
...blob) would need no new syntax, but it fails in recursive groups, and WebAPI's generator knows the IDL inheritance anyway. So the explicit form is proposed, perhaps with inference as a convenience later.
C. Writable mutable fields on private records. This is still to decide. The options:
- C1. No warning 111 for fields declared
mutablein a private record:privatethen means "can't be constructed or upcast into", not "can't be written". - C2. An opt-in attribute on the type, keeping today's warning by default.
- C3. Keep the warning, and leave WebAPI's records public, without classes.
Recommendation: C1. Making a record private is about who may construct it, and WebAPI's mutable fields mirror writable DOM properties, so writing them should not warn. The WebAPI team writes the most code affected by this, so its view decides.
Proposal: WebAPI side (once A, B and C exist)
- The generator emits
@instanceOf("X")and@extends(: parent)for every IDL interface with a global constructor. Mixins (ParentNode,GlobalEventHandlers) and dictionaries get no class, since dictionaries are plain objects. - Interface records become
private. %identityupcasts (asNode,asBlob, …) become:>. This is possible today, before A, B and C.isInstanceOf,classifyanddecodegive way to untagged variants.- Unions like
blobPartget theirArrayBufferand typed-array cases back.
Proposal: developing WebAPI, React and the compiler together
- The React bindings should build on WebAPI rather than on the stdlib's
Dommodule. React useselement,eventTarget,event,window,history,location,touchList,dataTransferandeventPointerIdfrom there. - The stdlib's web types go away.
Dom, and File, Blob and similar, are leftovers in the stdlib. The proposal is to deprecate them in 12.x and remove them in v13, with WebAPI as the replacement. - The compiler repo vendors a WebAPI base package, as it vendors the React bindings today (
tests/dependencies/rescript-react). The tests, examples and playground need React, and React then needs the base package. The vendored subset is what React needs: EventTarget, Node, Element and HTMLElement, the event types, Window, History, Location, DataTransfer, TouchList and the style object. - While the features above are in flux, one compiler PR changes the compiler, the vendored WebAPI and the vendored React together. That way every compiler feature is tried on real bindings before it's final. During that time the vendored copy is authoritative for the base subset. A
VENDORED.mdrecords the upstream commit and lists local deviations, and the changes are ported back into the WebAPI generator in batches. - Long term, the base package comes from the WebAPI repo, as the React bindings come from rescript-react, and the compiler repo only vendors a copy. Once A, B and C are released, upstream is authoritative again and the copy is re-synced from the generator.
DOM values on the server
Server runtimes (Node, Deno, Bun) have no DOM: there is no global Element, Node or Window. On the server, a class check like x instanceof Element therefore throws ReferenceError: Element is not defined. The compiler will not guard these checks, because DOM values should not show up in server code at all, and the error names the missing class. Primitives and literals never reach a class check.
Proposed order
- Vendor the WebAPI base subset unchanged, and switch the vendored React from the stdlib's
Domto it. - Deprecate the stdlib's web types in 12.x; remove them in v13.
- A and B in one compiler PR, updating the vendored WebAPI and React.
- C in its own compiler PR, once decided.
- Port the changes to the WebAPI generator and re-sync.
Questions for the WebAPI team
- Does
@instanceOf("X") @extends(: parent)on private records fit the generator? Is there a better spelling? - C: which of C1, C2 and C3 would you choose?
- Which modules belong in the base package that React depends on, and should it be published as its own npm package from the WebAPI repo?
- Are you fine with the vendored copy leading during the transition, with batched ports back to the generator?
- Langage dominant
- OCaml
- Étoiles
- 7.5k
- Forks
- 483
- Merge moyen
- 20 h 7 min
- PR mergées (30 j)
- 227
Préparer son environnement
Lance le conteneur de développement du projet dans votre navigateur, avec votre propre compte GitHub.
- Aucun Dockerfile ni fichier Docker Compose
- Propose un modèle de pull request
- Lire le guide de contribution
Par où commencer
- Lisez l'issue en entier, puis le guide de contribution du projet.
- Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
- Forkez le dépôt et travaillez sur une branche.
- Ouvrez une pull request qui référence le numéro de l'issue.
Autres issues de rescript-lang/rescript
-
Record coercion can turn an immutable field into a mutable onePeut-être pris Une pull request liée à cette issue est ouverte ou déjà fusionnée. Ouverte
Difficulté 4/5 3-5 jours Accessibilité débutants 57/100
rescript-lang/rescript#9063 ·
Les mainteneurs répondent en général sous 1 jour
-
Formatter moves a line comment in a switch/if/loop operand into the blockPeut-être pris @cknitt l’a pris aujourd’hui. Ouverte
Difficulté 4/5 3-5 jours Accessibilité débutants 35/100
rescript-lang/rescript#9060 ·
Les mainteneurs répondent en général sous 1 jour
-
RFC: Temporal API bindingsPeut-être pris Une pull request liée à cette issue est ouverte ou déjà fusionnée. Ouverte
Difficulté 5/5 Plus d'une semaine Accessibilité débutants 12/100
rescript-lang/rescript#9044 · 1 commentaire · 3 réactions ·
Les mainteneurs répondent en général sous 1 jour
-
Difficulté 5/5 Plus d'une semaine Accessibilité débutants 35/100
rescript-lang/rescript#9006 · 1 commentaire · 1 réaction ·
Les mainteneurs répondent en général sous 1 jour
-
Have formatter insert newlines around multi-line expressionsPeut-être pris @rolandpeelen l’a pris il y a 5 jours. Ouverte
Difficulté 4/5 3-5 jours Accessibilité débutants 25/100
rescript-lang/rescript#8960 ·
Les mainteneurs répondent en général sous 1 jour
Toutes les issues de rescript-lang/rescript
Issues similaires
-
Difficulté 2/5 1-3 heures Accessibilité débutants 78/100
Fei2411/DeepSeek-Bot#634 ·
-
:Search Relevance/Vectors >test-failure needs:risk Team:Search Relevance
Difficulté 2/5 1-3 heures Accessibilité débutants 61/100
elastic/elasticsearch#163668 · 3 commentaires · 1 réaction ·
Les mainteneurs répondent en général sous 1 jour
-
Handler accepts zero/negative NNCP_MAX_RETRIES and NNCP backoff values, stalling policies or hot-loopingPeut-être pris @NopeCharon l’a pris aujourd’hui. Ouverte
Difficulté 2/5 1-3 heures Accessibilité débutants 78/100
nmstate/kubernetes-nmstate#1604 ·
-
bug
Difficulté 2/5 1-3 heures Accessibilité débutants 70/100
-
chore(worker): five module header comments describe the code from before later issues changed itOuvertearea:dev U2-later
Difficulté 2/5 1-3 heures Accessibilité débutants 78/100
bentleypark/aiwatch#1824 ·
Les mainteneurs répondent en général sous 1 jour