Hacktoberfest 2026: die Issues, die Maintainer für den Oktober markiert haben – offen und einsteigerfreundlich. Hacktoberfest-Issues durchsuchen

RFC: New syntax for fixed external arguments (replacing `@as(...) _`)

Offen
#8,878 0 Kommentare 0 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen

Maintainer antworten meist innerhalb von 1 Tag

Dieses Issue hat noch niemand übernommen.

Bewertung

Schwierigkeit
5/5
Geschätzter Aufwand
Über eine Woche
Anfängerfreundlichkeit
25/100
Issue-Typ
Feature
Klarheit
Klar beschrieben
Aktivitätsstatus
Aktiv
Bereich
compilers

Rechercherichtung

Read the RFC’s four syntax proposals and compare their tradeoffs; the payload also points to parser and printer changes and compiler work in #8875 and #8876. Start by reviewing the existing fixed argument parser and printer behavior. Done means a syntax is chosen and its parser and printer changes are specified; this RFC is seeking design feedback, so implementation work is not yet defined.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Beschreibung

Summary

External bindings sometimes need an argument that is always the same value and that callers never pass, e.g. document.createElement("img") or Object.assign({}, ...). Today such fixed arguments are written as an @as(...) attribute on a _ type:

@send external createImg: (document, @as("img") _) => element = "createElement"
@val external assign: (@as(json`{}`) _, t, {..}) => t = "Object.assign"

This RFC proposes a dedicated syntax for them and presents four options:

  1. %raw("..."): the value as JavaScript source.
  2. =value / ~label=value: the value as a ReScript literal, in "default argument" style.
  3. _ = value / ~label: _ = value: the value as a ReScript literal, after the _ that marks the argument as not passed by callers.
  4. fixed value / ~label: fixed value: the value as a ReScript literal, after a contextual fixed keyword.

These are starting points, not a closed list: better proposals are very welcome.

Motivation

The current syntax has several problems:

  • The meaning is hidden in an attribute. @as(...) on _ turns a type into a value, which is hard to discover and unlike anything else in the language. @as also means something quite different on record fields and variant constructors.
  • Anything beyond strings and integers needs the special json tag, e.g. @as(json`{}`) for an empty object, @as(json`null`) or @as(json`false`). This made json template literals a special case in the compiler.
  • The value is easy to misread as a type, and the formatter and editor tooling see an attribute rather than a value.

The compiler side is being prepared independently of the syntax: #8875 gives fixed arguments their own parsetree node and removes the special json constant. #8876 adds option 1 as a provisional syntax. Whichever option this RFC settles on, it is a parser and printer change on top of that node.

Options

The examples bind document.createElement("img"), Object.assign({}, ...), history.replaceState(null, "", url) and an @obj option that is always false.

Option 1: %raw(...)
@send external createImg: (document, %raw(`"img"`)) => element = "createElement"
@val external assign: (%raw("{}"), t, {..}) => t = "Object.assign"
@val external replaceState: (%raw("null"), %raw(`""`), string) => unit = "history.replaceState"
@obj external makeOptions: (~objectMode: %raw("false"), ~name: string, unit) => options = ""

The payload is JavaScript source and must be exactly one JavaScript literal.

  • Pros
    • Reuses an existing construct; no new grammar.
    • Clearly an escape hatch, consistent with %raw elsewhere.
    • Can express any JavaScript literal, including regexps and bigints.
  • Cons
    • Strings need two levels of quoting (%raw(`"img"`)).
    • The value is a string of JavaScript, so it gets no highlighting or formatting, and mistakes are reported late.
    • Encourages raw JavaScript for what are almost always plain constants.
Option 2: =value and ~label=value
@send external createImg: (document, ="img") => element = "createElement"
@val external assign: (={}, t, {..}) => t = "Object.assign"
@val external replaceState: (=null, ="", string) => unit = "history.replaceState"
@obj external makeOptions: (~objectMode=false, ~name: string, unit) => options = ""

The value is a ReScript literal: string, number, boolean, null, undefined, array, object or record.

  • Pros
    • The labelled form mirrors the call site (~objectMode=false).
    • Values are ordinary ReScript literals: no double quoting, and the compiler produces the JavaScript.
    • The formatter and editor tooling treat the value like any other expression.
  • Cons
    • ~x=value is exactly the syntax of a default argument in function definitions, where the caller may still pass a different value. Here the caller cannot pass it at all.
    • The unlabelled form ="img" looks unusual.
    • ~x=value and ~x: t=? side by side can be confusing.
    • Can't express JavaScript-only literals such as regexps.
Option 3: _ = value and ~label: _ = value
@send external createImg: (document, _ = "img") => element = "createElement"
@val external assign: (_ = {}, t, {..}) => t = "Object.assign"
@val external replaceState: (_ = null, _ = "", string) => unit = "history.replaceState"
@obj external makeOptions: (~objectMode: _ = false, ~name: string, unit) => options = ""

The values are ReScript literals, as in option 2.

  • Pros
    • The closest to today's syntax (@as(x) _ becomes _ = x). It keeps the _, which already says "the caller does not pass this".
    • The same shape for unlabelled and labelled arguments.
    • Values are ordinary ReScript literals, with the same benefits as option 2.
  • Cons
    • Slightly more verbose than option 2.
    • ~x: _ = value resembles a typed default argument (~x: int=1) in function definitions.
    • Can't express JavaScript-only literals such as regexps.
Option 4: fixed value and ~label: fixed value
@send external createImg: (document, fixed "img") => element = "createElement"
@val external assign: (fixed {}, t, {..}) => t = "Object.assign"
@val external replaceState: (fixed null, fixed "", string) => unit = "history.replaceState"
@obj external makeOptions: (~objectMode: fixed false, ~name: string, unit) => options = ""

The values are ReScript literals, as in options 2 and 3. fixed is a contextual keyword: it only has this meaning when a value follows it, which is never valid type syntax today, so types, record fields and variables named fixed keep working.

  • Pros
    • Says what it means: fixed {} reads as "this argument is always {}", with no resemblance to default arguments.
    • The same shape for unlabelled and labelled arguments, and reads well in both.
    • Values are ordinary ReScript literals, with the same benefits as option 2.
    • Easy to search for and to document.
  • Cons
    • Adds a (contextual) keyword to the language.
    • Editor syntax highlighting needs to learn it.
    • Can't express JavaScript-only literals such as regexps.

Details common to all options

  • Where they're allowed. Fixed arguments are only allowed as direct arguments of an external, inside the parenthesized argument list, so external f: _ = "f" keeps its current meaning.
  • Optional arguments. A fixed argument can't be optional, because it is always passed.
  • Values in options 2 to 4. The value is read as written and isn't type-checked: {} means an empty JavaScript object, and null / undefined mean the JavaScript values.
  • Migration. rescript format rewrites the existing @as(...) _ forms to the new syntax automatically. Until then they keep working with a deprecation warning, and they will be removed in a later major version. The stdlib and Belt would be migrated in the same release.
  • PPX compatibility. PPXs keep seeing fixed arguments in the existing form, _ [@as ...], so they aren't affected.

Feasibility

All four options have been implemented or prototyped in the parser. Option 1 is in #8876. Options 2 to 4 parse without conflicting with existing syntax, compile to the expected JavaScript, and leave the existing parser and printer test suites unchanged. For option 4, existing code using fixed as a type, field or variable name is unaffected.

Question

Which option do you prefer, and why? Or do you have a better idea?

Vorherrschende Sprache
OCaml
Sterne
7.5k
Forks
483
Ø Merge
21 Std. 8 Min.
Gemergte PRs (30 T.)
183

Entwicklungsumgebung

In Codespaces öffnen

Startet den Dev-Container des Projekts im Browser, mit Ihrem eigenen GitHub-Konto.

Erste Schritte

  1. Lesen Sie das ganze Issue und danach den Beitragsleitfaden des Projekts.
  2. Schreiben Sie ins Issue, dass Sie es übernehmen — das erspart doppelte Arbeit.
  3. Forken Sie das Repository und arbeiten Sie in einem Branch.
  4. Öffnen Sie einen Pull Request, der die Issue-Nummer nennt.

Mehr aus rescript-lang/rescript

Alle Issues in rescript-lang/rescript

Ähnliche Issues

Weitere Issues zu Compilers

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.