nim-lang / Nim

Nim is a statically typed compiled systems programming language. It combines successful concepts from mature languages like Python, Ada and Modula. Its design focuses on efficiency, expressiveness, and elegance (in that order of priority).
https://nim-lang.org
Other
16.58k stars 1.47k forks source link

AsyncJsBackend: introduce better type checking for imports #23464

Closed Alogani closed 7 months ago

Alogani commented 7 months ago

Summary

Hello,

I truly believe that the use of Nim as a language for JavaScript is hindered by the fact that strong typing becomes complicated when importing native JavaScript functions. Dynamic typing is really a problem to write good, evolutive and simple code. Dynamic typing also don't allow code documenting and types bugs.

The other solution is to create objects for each javascript function arguments, which is really cumbersome and with other drawbacks.

Description

Here is an example of what we can do now :

## With dynamic typing :
proc getDocument*(PdfJs: PdfJsLib, options: JsObject): Future[Pdf] {.importcpp: "#.getDocument(#).promise", varargs, nodecl, async.}

PdfJs.getDocument(JsObject{data: ...})
# What is the expected argument ? How do I prevent typos ?
## With strong typing :
type getDocumentArguments* = object
   data: ArrayBuffer = nil
   url: NilableString = nil
   httpHeaders: HttpHeaders = nil
   withCredentials: NilableBool
   ...

proc getDocumentImpl(PdfJs: PdfJsLib, options: JsObject): Future[Pdf] {.importcpp: "#.getDocument(#).promise", varargs, nodecl, async.}
proc getDocument*(PdfJs: PdfJsLib, options: getDocumentArguments): Future[Pdf] = PdfJs.getDocumentImpl(options.toJs())

# Very cumbersome. Can be difficult to handle fields that have a significance by there absence (and can't just be present with a nil or empty value

Alternatives

No response

Examples

I don't think there is a perfect solution.

But one solution could be to make a special type that could be checked against at compile time. For example :

proc getDocument(PdfJs: PdfJsLib, options: JsType{ data: ArrayBuffer, url: string, httpHeaders: HttpHeaders, withCredentials: bool }): Future[Pdf] {.importcpp: "#.getDocument(#).promise", varargs, nodecl, async.}

# Usage:
var page = await PdfJs.getDocument(JsObject{ data: ArrayBuffer })

Missing values will not be generated on asyncjsbackend, but only fields defined with JsType (and their corresponding type) will be used.

Backwards Compatibility

No response

Links

No response

metagn commented 7 months ago

Doesn't

PdfJs.getDocument(js{ data: ... })

already do this? In general you can cast to JsObject for dynamic typing

Alogani commented 7 months ago

There is no compile time type checking in your example, or I miss something.

It's not about creating js objects. But to define types for proc arguments and to check the arguments passed resemble the type at compile time. It is about introducing concise type checking for js objects.

It would rather be :

proc getDocument(pdfjs: PdfJs, js: JsType{data: string}) = discard

pdfjs.getDocument(js = js{data: ...})

I have gathered some example usage for this kind of type checking :


proc createWindow(arg: JsTypeOf{x: int, y: int, size: JsType{ x = 20, y = 30 }}) {.jstype, importjs: "#.createWindow(#)".}

var myWindow = JsType{x = 10, y = 30, size = JsType{ x = 50 }}
createWindow(myWindow)
# or directly
createWindow(JsType{x = 10, y = 30, size = { x = 50 }})

# And another example :
proc test(a: JsTypeOf{ name: string, value: int | float, size = JsType{ x: int, y: int }})

I'm working on a solution using macros, but that is quite hell of a challenge. But my solution won't modify compiler/jsgen (beyond my competence), so I think it would be more a hack than a true proposition.

Another solution could be to leverage tuple (still inside compiler/jsgen), because there are of not utility on Jsbackend, and the code actually generated on js is quite deceptive #23459

Araq commented 7 months ago

Sorry but rejected. JsObject is good enough and JS libraries that use dynamic typing creatively should all be replaced by TypeScript libraries that don't.