Skip to content

Add Core standard library support for genType #6019

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Mar 2, 2023
Merged

Conversation

cristianoc
Copy link
Collaborator

@cristianoc cristianoc commented Feb 28, 2023

@@ -201,9 +201,14 @@ let translateConstr ~config ~paramsTranslation ~(path : Path.t) ~typeEnv =
{dependencies = []; type_ = EmitType.typeReactElement}
| (["FB"; "option"] | ["option"]), [paramTranslation] ->
{paramTranslation with type_ = Option paramTranslation.type_}
| (["Js"; "Null"; "t"] | ["Js"; "null"]), [paramTranslation] ->
| ( (["Js"; "Undefined"; "t"] | ["Undefined"; "t"] | ["Js"; "undefined"]),
Copy link
Collaborator Author

@cristianoc cristianoc Feb 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zth what else from paths hardcoded in this file should be supported for Core, in addition to Undefined.t and Null.t and Nullable.t and Dict.t and Promise.t?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Things like Option.t or Int.t or Float.t?
I think we're under the assumption that we're under "-open RescriptCore".

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Undefined.t is getting axed, but the rest of them for sure. Here are a few other candidates (some are perhaps already supported):

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On my phone now can’t verify but I think undefined<…> and null<…> are at the top level of Core for easy usage in code. Don’t need to prefix with a module name if you open Core. Js.Undefined.t is used frequently in my genType bindings - bummed if the Undefined module is being axed.

Copy link
Collaborator Author

@cristianoc cristianoc Feb 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On my phone now can’t verify but I think undefined<…> and null<…> are at the top level of Core for easy usage in code. Don’t need to prefix with a module name if you open Core. Js.Undefined.t is used frequently in my genType bindings - bummed if the Undefined module is being axed.

Why use Js.Undefined.t<typ> when you can use option<typ>? The definition of Js.Undefined predates the runtime representation of None as undefined.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was writing bindings yesterday with gentype and the function I was binding to expected an undefined or T but not a null. I think option, which I used first, compiles to null or undefined or T. I always start with option but sometimes it doesn’t work with gentype.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Undefined.t is getting axed, but the rest of them for sure. Here are a few other candidates (some are perhaps already supported):

All supported now.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was writing bindings yesterday with gentype and the function I was binding to expected an undefined or T but not a null. I think option, which I used first, compiles to null or undefined or T. I always start with option but sometimes it doesn’t work with gentype.

Can you give an example of what function you were binding to? Did you get a TypeScript type error, or is it a worry that things don't match?

@cristianoc cristianoc requested a review from zth February 28, 2023 12:36
@cristianoc cristianoc added this to the v10.1 milestone Feb 28, 2023
@cristianoc
Copy link
Collaborator Author

@zth this should be complete now and ready for review.
There's the question of Undefined.t, but that can also be handled in the stdlib, as genType won't do anything if that's not present in the library. The question to resolve is whether there are certain bindings that require using Undefined.t<_> and not option<_>, at the level of functionality. The typing question follows from that.

@zth
Copy link
Collaborator

zth commented Feb 28, 2023

Fantastic! Yes I'd be interested in hearing more about what use cases option does not handle from @jmagaram .

Copy link
Collaborator

@zth zth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice to read some gentype code.

@jmagaram
Copy link
Contributor

jmagaram commented Feb 28, 2023

I'm writing bindings for Firebase SDK, a very popular library from Google. These functions require types that are undefined | T. Using option with genType won't work. I've only started writing bindings; just for what I need. Probably a lot more use cases. If Undefined is going to be removed from Core library I'm going to have to keep using Js.Undefined. If makes sense to support all flavors of null | T, undefined | T, and null | undefined | T. The first two are probably identical code with all the same methods like toOption. What remove this from the new Core library?

export declare function getAnalytics(app?: FirebaseApp): Analytics;
export declare function getAuth(app?: FirebaseApp): Auth;
export declare function connectAuthEmulator(auth: Auth, url: string, options?: {
    disableWarnings: boolean;
}): void;

My binding for the first. I suppose I could create multiple bindings, one with the optional parameter and one without, but this is a hassle. I'd rather create all my friendly overrides in Rescript.

  @gentype.import("firebase/analytics")
  external _make: Js.undefined<FirebaseApp.t> => t = "getAnalytics"

@zth
Copy link
Collaborator

zth commented Feb 28, 2023

I still don't understand why option doesn't work where T | undefined is expected. Could you examplify that part @jmagaram ?

@jmagaram
Copy link
Contributor

I still don't understand why option doesn't work where T | undefined is expected. Could you examplify that part @jmagaram ?

I'm confused I thought I just did. Those function declarations are straight from the Firebase SDK. I'm trying to write genType import statements for those. If I use option<FirebaseApp.t> it does not work. I get errors in Typescript saying the type is expecting undefined | T not undefined | null | T. See optionals in the genType documentation.

If you want to try it yourself, put this in a .ts file...

/* eslint-disable @typescript-eslint/no-unused-vars */
export const go1 = (a: string | undefined) => "x";
export const go2 = (args: { a: string | undefined }) => "x";

Then try to import it like this and look at the errors in the gen.tsx file.

@gentype.import("./MyFuncs")
external go1: option<string> => string = "go1"

type args2 = {a?: option<string>}

@gentype.import("./MyFuncs")
external go2: args2 => string = "go2"

There is tricky stuff I can't get my head around here. Like if the ReScript args is {a?:string} or a:option<string> there is different behavior and errors. In the TypeScript {a?:string} makes errors while {a:string|undefined} does not.

@zth
Copy link
Collaborator

zth commented Mar 1, 2023

Right, so this particular issue is that gentype treats option as null | undefined?

@cristianoc
Copy link
Collaborator Author

cristianoc commented Mar 1, 2023

Right, so this particular issue is that gentype treats option as null | undefined?

genType takes the most general approach of accepting both null ad undefined, and converting the value to undefined on the way in from JS.
It would be possible to strictly accept only undefined but not null. Though one would need a proper assessment of what are the actual consequences for bindings: which choice covers the largest surface for typical bindings.
CC @cknitt

@cristianoc
Copy link
Collaborator Author

Generally speaking, treating option<T> as (undefined | T) would make importing JS functions taking optional arguments more general.
But, it would make exporting ReScript functions taking optional arguments less general. So that when those functions are consumes from JS, when called, only undefined can be passed but not null.

@cristianoc
Copy link
Collaborator Author

cristianoc commented Mar 1, 2023

Here's a PR that treats option<t> as t | undefined #6022. It's quite attractive in that it produces fewer conversion functions. Unless the payload requires conversion, as in e.g. variants, the typed bindings are zero-cost.
It would be nice to test it on some codebase which used genType, and see if there are surprises.

@cristianoc
Copy link
Collaborator Author

cristianoc commented Mar 1, 2023

There is tricky stuff I can't get my head around here. Like if the ReScript args is {a?:string} or a:option<string> there is different behavior and errors. In the TypeScript {a?:string} makes errors while {a:string|undefined} does not.

In TS these two types are different:

type t1 = {x?: string}
type t2 = {x: string | undefined}

in that t2 is more restrictive: it does not accept {} as a value.

The two ReScript types corresponding to them, as things currently stand, are:

type t1 = {x?: string}
type t2 = {x: Js.undefined<string>}

See #6022 as relevant.

This produces strictly fewer conversion functions, as it does not need to transform `null` to `undefined`.
This is a breaking changes, but aligns more closely with TS.

The two TS types:

```ts
type t1 = { x?: string };

type t2 = { x: (undefined | string) };
```

now correspond to the ReScript types:
```res
@genType
type t1 = {x?: string}

@genType
type t2 = {x: Js.undefined<string>}
```

The special treatment of fields of option type comes from a time where records with optional fields did not exist.
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.

3 participants