Skip to content
This repository was archived by the owner on Dec 27, 2025. It is now read-only.

Conversation

@cheesycod
Copy link

No description provided.

vrn-sn and others added 30 commits October 11, 2025 08:09
…et_version.cmake` output (#419)

Our release workflow depends on the output of `get_version.cmake` being
the raw version. We added some extra text in #396 that is currently
causing errors during the release tag generation step, so this PR
reverts that change.

Someone reached out about recent changes not being "released" yet, which
was surprising because we're on a nightly release schedule—this was the
root cause.
This PR comes with a small change to tools/luthier.luau to pick a
different delimiter string.
tin
makes net.request work with https on windows. should also work on linux
according to the
[docs](https://curl.se/libcurl/c/CURLOPT_SSL_OPTIONS.html)
### Problem

The issue can be replicated with a simple script that calls
`process.run` twice, like this:
```luau
local process = require("@lute/process")
process.run({"echo", "hello!"})
process.run({"echo", "goodbye!"})
```

Very rarely, some runs throw unexpected errors, crash, or corrupt Lute
output with random bytes. The problem is a subtle UAF bug in
`process.cpp`. Despite the small PR diff, this was a very tricky bug to
nail down. Had to go digging pretty deep into the libuv source to
understand what was going on.

Essentially, it's not correct to check if the `stdout` and `stderr`
pipes are active before closing them. Since these pipes are used as
read-only data streams, they're automatically made inactive by libuv
upon reaching EOF. In this situation, we don't need to call
`uv_read_stop` on these streams because they're already stopped
(although it is fine to do it; `uv_read_stop` is
[idempotent](https://docs.libuv.org/en/v1.x/stream.html#c.uv_read_stop)),
but we do still need to close the underlying pipe handle.

We're currently in a situation where calling `process.run` might
immediately close either the `stdout` or `stderr` streams (at EOF),
causing the `uv_is_active` check to fail, preventing us from calling
`uv_close` on the handle and from incrementing `pendingCloses`.

Failing to close these handles means that libuv retains data internally
associated with them, and by not incrementing `pendingCloses`
(essentially a refcount), we end up destroying the owning
`ProcessHandle` of these handles before libuv has been able to clean
them up internally.

Then, when we go to initialize new handles for the next `process.run`
call, we have a UAF manifest deep inside of libuv's circular doubly
linked queue code: adding a new handle to the data structure requires
adjusting pointers on the last-added handle, which happens to be a
handle created from the previous `process.run` call. At this point, it
is freed memory that was supposed to be removed from libuv's internals
before being freed (using `uv_close`).

### Solution

Check instead if the handles still need closing with `!uv_is_closing`
since `uv_is_active` is not relevant to whether `uv_close` needs to be
called in this case.
### Problem

There was no std lib for `system` related functionalities. 

### Solution

We introduce a `std` library analogue for `system`, where we can now
check the environment with boolean flags.
Closes #167

To test (with huge help from @vrn-sn), we wanted to write a C++ file
that compares the "os" captured by the C++ executable and compares to
the `@std/system` OS. To write that (and other similar output comparison
test cases), we exposed the `CliRuntimeFixture` and a `runCode()`
function that was used by `require.test.cpp` before but we added a
`capture()` function based on the `Luau/ReplFixture` class.

Then, we changed some function signatures to ensure the program args
(`argc` and `argv`) are not statically passed to avoid some tests
failing.

### Example:

```lua
local system = require("@std/system")

if system.linux then
    print("Running on Linux")
end
```

See `examples/system_environment_check.luau` for example use case
continuation of
#416 (comment) to
avoid duplicating a `static` function each time the header is included

---------

Co-authored-by: Varun Saini <61795485+vrn-sn@users.noreply.github.com>
Some filesystem operations didn't clean up and free all their data. This
PR attempts to fix this.

Some general observations:

- `uv_fs_req_cleanup` has to be called on all requests (sync/async). The
documentation doesn't really mention this in much detail. For some
operations, this might be a noop, but that's not part of the API and
libuv is free to change this afaik.
- When handling errors, the request wasn't always free'd.
- The data associated with requests wasn't always free'd.
- Some code was unreachable due to a Lua error thrown before.

These errors seem hard to detect statically. Maybe there could be some
RAII type for typed requests?
This should now provide exit as an option when invoking process. +
provide better in editor typechecking.
Static casting `void*` to `T*` is always allowed, so it's better to use
the narrower cast when possible.
Refactoring to make lute's API formatting more consistent
When running a script with `LUA_MINSTACK - 1` (19) or more arguments,
Luau fails with `L->top < L->ci->top`, because `getRefForThread` tries
to push the current thread onto a full stack [in
`runBytecode`](https://github.com/luau-lang/lute/blob/a70354de11446cc95b65f00d853b0c3d0b0faf84/lute/cli/src/climain.cpp#L115).
The stack only had `argc` slots, because it was resized in
`setupArguments`.

With this PR, `setupArguments` reserves one more slot (an alternative
would be to reserve space in `getRefForThread`). `setupArguments` should
probably be declared `static` as well, since it's not declared in any
header.
This PR takes a different approach to #434 by saying "yes, the data will
be visible, and we'll communicate intent for it to generally be
undisturbed with an underscore prefix." This is generally accepted as
the idiom for information hiding in Luau today, and I don't feel like
this is worth making a point of difference.
…+ testing assertions (#411)

This PR comes with some changes to the testing api to reduce the amount
of global state we depend on - when making subcases, we now pass in the
suite object on which the user can operate. Additionally, assertions are
passed in with the test case itself, making them more accessible and
reducing an additional line of code needed to start writing tests.

- [x] {before,after}x{all,each} lifecycle methods
- [x] Table assertions 

For example: 
<img width="463" height="79" alt="image"
src="https://github.com/user-attachments/assets/31003849-ae2c-469d-a23b-415c4135865b"
/>
#449)

This is a pre-req for a future PR for Multi-File bytecode compilation.
Doing this lets me use the module path interface more flexibly.
I was trying to get the code generation to run through CMake (which
would run lute in the build). When integrating this into luthier, I
needed a way to identify the current lute executable to pass to CMake.

This adds `execpath` to `@lute/process`, which mirrors Node.js'
[process.execPath](https://nodejs.org/docs/latest/api/process.html#processexecpath)
to get the lute executable path - this is immune to `exec -a foo lute`
having `foo` as `argv[0]`.


[`uv_setup_args`](https://docs.libuv.org/en/v1.x/misc.html#c.uv_setup_args)
can allocate on some platforms. That memory should be free'd with
[`uv_library_shutdown`](https://docs.libuv.org/en/v1.x/misc.html#c.uv_library_shutdown).
I'm not sure where it's appropriate to call this.

Passing around the `argv0` also seems a bit wrong. Maybe this path
shouldn't be stored in `Runtime`?
Small change, but "CI" makes more sense than "Gated Commits" for the
README icon:
<img width="253" height="52" alt="image"
src="https://github.com/user-attachments/assets/82b052c7-c445-47a0-993e-ac9943d2881b"
/>
This is a super common assertion and something I wanted to use in a
project I was working on so figure'd I would add it.

Not sure what the larger vision is for testing API, but hopefully this
fits somewhere.

Can also add an optional `expectedError` param
[Rendered](https://github.com/luau-lang/lute/blob/vrn-sn/contributing-md/CONTRIBUTING.md).

Feel free to commit to this branch directly.

---------

Co-authored-by: ariel <arielweiss@roblox.com>
### Problem
Lute did not support user input yet.

### Solution
We introduce user input support to the Lute and the Lute standard
library!
Users can use the `input()` function found in`@std/io` to process and
store user input from command line, piped input, or file redirection.
See examples below and in `examples/user_input.luau`.
Functionality was implemented using `libuv`'s library and can be found
in `lute/io`

### Examples

#### Scripts

Get user input
```lua
local io = require("@std/io")

local input = io.input()
print(input)
```

Get user input with prompt
```lua
local io = require("@std/io")

local name = io.input("Please enter your name: ")
print(name)
```

#### Ways to execute the script

Command line
```bash
$ lute user_input.luau
> [will wait for user input in command line]
```

Piped input
```bash
$ echo hello | lute user_input.luau
```

Input redirected from file
```bash
$ lute user_input.luau < input_text.txt
```

### Future Work
- Tests
- `stdout` and `stderr` functionality and other I/O utilities.
- Currently, providing a prompt to the user with `.input()` will produce
a new line between said prompt and the user input, this will most likely
be resolved once `stdout` is implemented

Thank you @vrn-sn for massive help (esp with `libuv`) 🙏

Resolves #360
Not visible in a monospaced font, but the mighty en dash strikes again.
😉
vrn-sn and others added 30 commits December 12, 2025 12:52
Printing line numbers in `prettydiff` provides a lot of utility/clarity
in properly visualizing differences b/w files. It also helps distinguish
the diff output from other arbitrary text in the command line.

Here is an example on a 100 line diff, to showcase the alignment of
printed side-headers:
<img width="351" height="1313" alt="Screenshot 2025-12-08 at 3 22 31 PM"
src="https://github.com/user-attachments/assets/79780be7-0298-4f61-9483-f2bf233ba152"
/>
We're only going to expose this API under `@lute/net`.
**Luau**: Updated from `0.702` to `0.703`

**Release Notes:**
https://github.com/luau-lang/luau/releases/tag/0.703

---
*This PR was automatically created by the [Update Luau
workflow](https://github.com/luau-lang/lute/actions/workflows/update-prs.yml)*

---------

Co-authored-by: aatxe <744293+aatxe@users.noreply.github.com>
Co-authored-by: Varun Saini <61795485+vrn-sn@users.noreply.github.com>
We use the new `to_alias_override` Luau.Require callback to ensure that
the `@std` and `@lute` aliases always point at the embedded libraries,
not ones on disk that the user may have set up using `.luaurc` or
`.config.luau` files.

@Vighnesh-V, this means that `@std/test` will always point at the
embedded module now.
…uteVfs` (#696)

Resolves #691. As @Nicell points out in #692, we have some libuv memory
safety issues that have been revealed with this change, so they'll need
to be fixed outside of this PR.
When invoking lute, it will attempt to discover a script named with the
provided argument, or within a `.lute` directory. This change supports
the same discovery mechanism, but additionally supports discovering
scripts in hierarchical `.lute` directories.

As a practical example, I provided a luthier shorthand to demonstrate
how this mechanism can be used. Instead of `lute tools/luthier.luau
...args` we can now invoke `lute luthier ...args`, and this can now be
invoked from within any nested directory of lute.
**Lute**: Updated from `0.1.0-nightly.20251205` to
`0.1.0-nightly.20251220`

**Release Notes:**
https://github.com/luau-lang/lute/releases/tag/0.1.0-nightly.20251220

---
*This PR was automatically created by the [Update Luau
workflow](https://github.com/luau-lang/lute/actions/workflows/update-prs.yml)*

Co-authored-by: aatxe <744293+aatxe@users.noreply.github.com>
Thanks to recent changes to how require works, namely that `@std`
requires will always resolve to the single builtin instance in the
runtime (ignoring aliases), `lute test` can now handle transitive and
batteries requires correctly without needing to intercept requires at
all. This was the last blocker remaining before `lute test` could be
used in CI.
This PR:
a) updates CI to call `lute test`
b) removes the require interception that `lute test` did
b) removes the explicit call to test.run() within the test files in our
repo, since test discovery handles this

I've left `test.run()` in for now, in case there is some desire to use
the testing library without `lute test` (and the standard library tests
in `std.test.luau` exercise this behaviour). I'm open to opinions about
what to do with it, and can address those in a followup.

Resolves issue #634
Felt like tableext should have a `toset` util.
The lute runtime currently schedules coroutines in a std::vector, and we
always pop off the first thread in the vector to run. For small numbers
of coroutines, this is okay, but when you get to 10k+ this becomes very
inefficient as we have to move all the other elements one over `O(n)`.
This PR just replaces the `std::vector` with a `Luau::VecDeque`, making
the pop operation `O(1)`.

I've also included a profiling script that I used to profile lute since
I keep forgetting the `xctrace` incantation - it can be invoked by:
```
./tools/profile.sh /path/to/.luau(defaults to profile.luau)
```
Addresses #710 

Updates `lute check` to use the new solver by default.

Fails the test as expected without the change
```
Failed Tests (1):

❌ lute check.uses new solver
	/Users/rng/dev/lute/tests/cli/check.test.luau:32
		eq: 1 ~= 0
```
**Luau**: Updated from `0.703` to `0.704`

**Release Notes:**
https://github.com/luau-lang/luau/releases/tag/0.704

---
*This PR was automatically created by the [Update Luau
workflow](https://github.com/luau-lang/lute/actions/workflows/update-prs.yml)*

---------

Co-authored-by: aatxe <744293+aatxe@users.noreply.github.com>
Co-authored-by: Nick Winans <nick@winans.io>
**Lute**: Updated from `0.1.0-nightly.20251220` to
`0.1.0-nightly.20260109`

**Release Notes:**
https://github.com/luau-lang/lute/releases/tag/0.1.0-nightly.20260109

---
*This PR was automatically created by the [Update Luau
workflow](https://github.com/luau-lang/lute/actions/workflows/update-prs.yml)*

---------

Co-authored-by: aatxe <744293+aatxe@users.noreply.github.com>
Co-authored-by: Nick Winans <nick@winans.io>
`AstExprTableItem` nodes are not serialized with location right now.

I encountered a bug due to this behavior while using `lute lint` CLI:
```
Error linting file '/Users/wmccarthy/git/roblox/luau-ui-ecosystem-lints/repro_nil_location.luau': @std/syntax/init.luau:14: attempt to index nil with 'beginline'
```

If you follow the trace from std/syntax/init.luau:14, you'll see the
error stems from calls to syntax.span.subsumes in lint's init file ([see
here](https://github.com/luau-lang/lute/blob/primary/lute/cli/commands/lint/init.luau?rgh-link-date=2026-01-07T21%3A07%3A33Z#L150-L160)).
Here, `nodeIsSuppressed` is used as the default visitor function, in
other words, it is called at every visited node as the visitor walks the
tree. It relies on the assumption that every node has location field,
which is not true. `ExprTableItem` nodes, specifically, do not have a
`location` field.

Adding `location` to table item nodes will fix this issue and help avoid
any similar future issues.
The bulk of the changes are refactoring some of the linting logic to
extract the common logic between linting a file and a string. Also,
paths in reported lint violations are now optional since they're not
relevant to linting string input.

The broader context for this PR is that it allows linting to be consumed
by VSCode (and maybe other) extensions, since the source of truth for
file contents is whatever the editor/LSP server has in memory, rather
than what's on disk in the file.
I had a need for `any` when working on something else, and added `all`
along the way
This lays the groundwork for the autofix work, and means that normal
`lute lint` invocations can function as dry runs.
New lint rule! It's also useful for a test case I want to add for the
autofix stuff
Adds support for autofixing reported lint violations (if the lint rules
provide suggested fixes). The core logic for applying the fixes is
pretty simple: split the content by line and do the replacements to the
relevant lines. For now, we don't handle the cases where suggested fixes
overlap (although my approach would probably be to just ignore fixes
that overlap with previously applied fixes). We rerun the lints after
applying fixes and report any violations that are still left. It's
possible that those reported violations have suggested fixes, but we
ignore them to prevent the chance of looping between suggested fixes
infinitely.
Continuation of #661 and one part
of making file system APIs asynchronous
(#709)
Addresses #709's journey of making our file system APIs asynchronous
Co-authored-by: ariel <arielweiss@roblox.com>
#734)

This PR just updates the `run`, `test` and `compile` cli command
reference.
This PR just pulls in the Luau CLI's Profiler and Code Coverage modules
into `lute`. We're doing this instead of exposing them from Luau because
these features are not really products, so much as best effort attempts,
so we don't necessarily want to expose them from Luau yet.

This is just copied from Luau/CLI - no changes have been made.
Adds some more description to the docs for `lute lint` and `lute
transform`
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.