kaleidawave / ezno

A fast and correct TypeScript type checker and compiler with additional experiments
https://kaleidawave.github.io/posts/introducing-ezno/
MIT License
2.48k stars 48 forks source link

`npx ezno check` does not check code? #204

Open rotu opened 2 months ago

rotu commented 2 months ago

I'm trying to get started with ezno and npx ezno check file.ts does not print anything and exits with code 0.

$ npx ezno info
ezno@0.0.22 (#10617350335)

A JavaScript type checker and compiler. For use as a library or through the CLI
Repository: https://github.com/kaleidawave/ezno, License: MIT

For help run --help

$ cat file.ts
const k: number = "abc"
$ npx ezno check file.ts
$

I'm using ezno@0.0.22 installed via npm on Mac OS.

rotu commented 2 months ago

Building with cargo and running does produce the expected output, so it seems this is a problem with the npm package itself.

$ /Users/dan/Source/ezno/target/debug/ezno check ./file.ts 
error: 
  ┌─ file.ts:1:19
  │
1 │ const k: number = "abc"
  │          ------   ^^^^^ Type "abc" is not assignable to type number
  │          │         
  │          Variable declared with type number
rotu commented 2 months ago

I don't know how to debug this. I get as far as this call:

run_cli(cliArguments, readFile, writeFile, readFromCLI);

where cliArguments is ["check","file.ts"]

And after that, it steps into wasm code, which I don't know how to debug.

kaleidawave commented 2 months ago

Hmm yes maybe a problem with the bin field in package.json.

I presume npx ezno info does nothing as well.

NPM and related is a nightmare for packaging platform specific code and binaries 😡

rotu commented 2 months ago

I presume npx ezno info does nothing as well.

As show above, npx ezno info does show info about ezno. So I don't think it's the bin field.

Additionally, even running this module with deno exits silently:

deno --allow-all /Users/dan/Source/try-ezno/node_modules/ezno/dist/cli.mjs check ./file.ts

Or via npm imports, also exits silently:

deno --allow-all 'npm:ezno' check file.ts

NPM and related is a nightmare for packaging platform specific code and binaries 😡

I bet! If you know a way to get sourcemaps working so that I can step into the Rust code, that would certainly help!

kaleidawave commented 2 months ago

Sorry misread the last message. Ah that is good npx ezno info works, so it is an issue with the check command for the CLI in the WASM edition.

Looking at the code: I think it is maybe due to the glob support for checking files, which has been added since the last release.

https://github.com/kaleidawave/ezno/blob/cd8e9612ed16271444d94d7f89e522c18d05a328/src/cli.rs#L215

If you add a similar print_to_cli message with the error under that branch then that might be the cause. I forgot to check how the glob library worked under WASM.

I don't know if you can do WASM source maps? https://github.com/rustwasm/wasm-pack/issues/824

rotu commented 2 months ago

Looking at the code: I think it is maybe due to the glob support for checking files, which has been added since the last release.

Indeed the glob support is the cause of failure. https://github.com/kaleidawave/ezno/blob/cd8e9612ed16271444d94d7f89e522c18d05a328/src/cli.rs#L467-L469

  1. Execution enters the files.is_empty branch.
  2. It appears nothing is printed by the line eprintln!("Input {input:?} matched no files"); nor a print_to_cli statement after, so I think that line panics.
  3. While glob() admits relative paths, I suspect filesystem access is broken since env::current_dir() gives me Err(Error { kind: Unsupported, message: "operation not supported on this platform" })
rotu commented 2 months ago

Okay I think I'm starting to understand Rust, WASM, and wasm-pack. In particular, just about any sort of system call is going to fail. If filesystem access and stdio is needed, it seems prudent to use wasi for this.

WASI is a set of APIs defined for the WebAssembly Component Model to help components interact with the outside world. Core WebAssembly has no intrinsic ability to access the host, for example println! don’t work, but WASI defines how to do so with the wasi:cli/stdio package.

kaleidawave commented 1 month ago

Yep WASM by itself just implements standard instructions. WASI adds more functions for handling system things etc.

However with just base WASM you can import functions in two ways.

Either with an extern block which pulls a global function from JS environment. Which is good for global things https://github.com/kaleidawave/ezno/blob/cd8e9612ed16271444d94d7f89e522c18d05a328/src/wasm_bindings.rs#L4-L8

Or using &js_sys::Function where the exposed function takes a function from an argument check((path) => { return readFileSync(path) }) which is good when you have different functions rather than a global https://github.com/kaleidawave/ezno/blob/cd8e9612ed16271444d94d7f89e522c18d05a328/src/wasm_bindings.rs#L84-L85

and this is how the CLI works https://github.com/kaleidawave/ezno/blob/cd8e9612ed16271444d94d7f89e522c18d05a328/src/wasm_bindings.rs#L125-L140


Having another look at the CLI and especially the glob as input support it uses eprintln! rather than the print_to_cli function, which works with WASM.

https://github.com/kaleidawave/ezno/blob/cd8e9612ed16271444d94d7f89e522c18d05a328/src/cli.rs#L457-L479

and unfortunately the rustc WASM build (which wasm-pack wraps) just compiles eprintln! to a no-op rather than producing a warning at compile or runtime :(.

Changing the eprintln! to print_to_cli for the above will help in figuring out this issue!

rotu commented 1 month ago

My impression is that you either inject the effectful code you need as JS callbacks (which is what you’ve done with wasm_bindgen) OR you use a runtime adapter that does those things for you (which is the goal of wasi).

I believe that:

  1. You should be using the Rust-provided wasm32-wasip2 target.
  2. Your run_cli should not exist. WASI already specifies how to give your module access to input and output. Admittedly, the callbacks are more complicated, since they use stream abstractions, but you don’t have to worry too much about that because…
  3. Things like println! are automatically wired up in the wasm32-wasi* targets, with no need to rewrite for special wasi-aware APIs.^1
  4. At the end of the day, the emitted module is still WASM. You may need to inject the missing system calls to actually get it to run in the browser, but that is doable^2.