luau/VM/src/lcorolib.cpp
menarulalam a8d14596e7
Sync to upstream/release/669 (#1770)
We have lots of new changes for you! 

# What's Changed
## General

- We updated Luau's license year to 2025! 
- We fixed a bug where large amounts of errors were being printed when
deep intersections of unions error.


## Require-by-String
This release introduces the `Luau.Require` library, which exposes the
runtime semantics of require-by-string, including support for the new
`@self` alias described in [this
RFC](https://github.com/luau-lang/rfcs/pull/109).

The library operates on a virtualized filesystem, allowing consumers to
specify navigation rules without assuming a filesystem context.
Documentation in `Require.h` explains how to enable the library, and the
`setupState` function in Repl.cpp demonstrates how we've integrated it
into the luau CLI tool. Note that the interface in `Require.h` is
written in C, which enables any application written in a language with a
C foreign-function interface to link against this library and enable
require-by-string. This makes it straightforward for any application
embedding Luau to support require-by-string, provided that it defines or
operates within an environment resembling a virtual filesystem.

The core navigation semantics of require-by-string have additionally
been pulled into the `Luau.RequireNavigator` library. While
`Luau.Require` internally depends on `Luau.RequireNavigator`, the latter
does not depend on the Luau VM. This library provides an interface for
inspecting require-by-string's navigation behavior and therefore serves
as a useful dependency for static tooling. Documentation for
`Luau.RequireNavigator` is available in `RequireNavigator.h`.
## Autocomplete
- We fixed a memory leak in fragment autocomplete!
## New Solver And Old Solver
- We've found a infinite iteration error over a type pack. We added a
way to detect this error and throw an `InternalCompileError` instead.
- We fix `table.freeze` not accounting for the first argument not
getting type stated. We fall back to regular inference instead.
- We fix a crash in the old solver with `length_error`.
- We fix a crash in the new solver stemming from generalization
reentrancy. Now we correctly generalize interior free types that do not
appear in a function signature.
- We fix a nil refinement. (Fixes
https://github.com/luau-lang/luau/issues/1687 and
https://github.com/luau-lang/luau/issues/1451)



### Internal Contributors
Co-authored-by: Andy Friesen <afriesen@roblox.com>
Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com>
Co-authored-by: Talha Pathan <tpathan@roblox.com>
Co-authored-by: Varun Saini <vsaini@roblox.com>
Co-authored-by: Vighnesh Vijay <vvijay@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>

Full Changelog: https://github.com/luau-lang/luau/compare/0.668...0.669

---------

Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com>
Co-authored-by: Varun Saini <61795485+vrn-sn@users.noreply.github.com>
Co-authored-by: Alexander Youngblood <ayoungblood@roblox.com>
Co-authored-by: Aviral Goel <agoel@roblox.com>
Co-authored-by: Vighnesh <vvijay@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
Co-authored-by: Ariel Weiss <aaronweiss@roblox.com>
2025-04-11 17:44:21 -07:00

269 lines
6.5 KiB
C++

// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
#include "lualib.h"
#include "ldebug.h"
#include "lstate.h"
#include "lvm.h"
#define CO_STATUS_ERROR -1
#define CO_STATUS_BREAK -2
static const char* const statnames[] = {"running", "suspended", "normal", "dead", "dead"}; // dead appears twice for LUA_COERR and LUA_COFIN
static int costatus(lua_State* L)
{
lua_State* co = lua_tothread(L, 1);
luaL_argexpected(L, co, 1, "thread");
lua_pushstring(L, statnames[lua_costatus(L, co)]);
return 1;
}
static int auxresume(lua_State* L, lua_State* co, int narg)
{
// error handling for edge cases
if (co->status != LUA_YIELD)
{
int status = lua_costatus(L, co);
if (status != LUA_COSUS)
{
lua_pushfstring(L, "cannot resume %s coroutine", statnames[status]);
return CO_STATUS_ERROR;
}
}
if (narg)
{
if (!lua_checkstack(co, narg))
luaL_error(L, "too many arguments to resume");
lua_xmove(L, co, narg);
}
else
{
// coroutine might be completely full already
if ((co->top - co->base) > LUAI_MAXCSTACK)
luaL_error(L, "too many arguments to resume");
}
co->singlestep = L->singlestep;
int status = lua_resume(co, L, narg);
if (status == 0 || status == LUA_YIELD)
{
int nres = cast_int(co->top - co->base);
if (nres)
{
// +1 accounts for true/false status in resumefinish
if (nres + 1 > LUA_MINSTACK && !lua_checkstack(L, nres + 1))
luaL_error(L, "too many results to resume");
lua_xmove(co, L, nres); // move yielded values
}
return nres;
}
else if (status == LUA_BREAK)
{
return CO_STATUS_BREAK;
}
else
{
lua_xmove(co, L, 1); // move error message
return CO_STATUS_ERROR;
}
}
static int interruptThread(lua_State* L, lua_State* co)
{
// notify the debugger that the thread was suspended
if (L->global->cb.debuginterrupt)
luau_callhook(L, L->global->cb.debuginterrupt, co);
return lua_break(L);
}
static int auxresumecont(lua_State* L, lua_State* co)
{
if (co->status == 0 || co->status == LUA_YIELD)
{
int nres = cast_int(co->top - co->base);
if (!lua_checkstack(L, nres + 1))
luaL_error(L, "too many results to resume");
lua_xmove(co, L, nres); // move yielded values
return nres;
}
else
{
lua_rawcheckstack(L, 2);
lua_xmove(co, L, 1); // move error message
return CO_STATUS_ERROR;
}
}
static int coresumefinish(lua_State* L, int r)
{
if (r < 0)
{
lua_pushboolean(L, 0);
lua_insert(L, -2);
return 2; // return false + error message
}
else
{
lua_pushboolean(L, 1);
lua_insert(L, -(r + 1));
return r + 1; // return true + `resume' returns
}
}
static int coresumey(lua_State* L)
{
lua_State* co = lua_tothread(L, 1);
luaL_argexpected(L, co, 1, "thread");
int narg = cast_int(L->top - L->base) - 1;
int r = auxresume(L, co, narg);
if (r == CO_STATUS_BREAK)
return interruptThread(L, co);
return coresumefinish(L, r);
}
static int coresumecont(lua_State* L, int status)
{
lua_State* co = lua_tothread(L, 1);
luaL_argexpected(L, co, 1, "thread");
// if coroutine still hasn't yielded after the break, break current thread again
if (co->status == LUA_BREAK)
return interruptThread(L, co);
int r = auxresumecont(L, co);
return coresumefinish(L, r);
}
static int auxwrapfinish(lua_State* L, int r)
{
if (r < 0)
{
if (lua_isstring(L, -1))
{ // error object is a string?
luaL_where(L, 1); // add extra info
lua_insert(L, -2);
lua_concat(L, 2);
}
lua_error(L); // propagate error
}
return r;
}
static int auxwrapy(lua_State* L)
{
lua_State* co = lua_tothread(L, lua_upvalueindex(1));
int narg = cast_int(L->top - L->base);
int r = auxresume(L, co, narg);
if (r == CO_STATUS_BREAK)
return interruptThread(L, co);
return auxwrapfinish(L, r);
}
static int auxwrapcont(lua_State* L, int status)
{
lua_State* co = lua_tothread(L, lua_upvalueindex(1));
// if coroutine still hasn't yielded after the break, break current thread again
if (co->status == LUA_BREAK)
return interruptThread(L, co);
int r = auxresumecont(L, co);
return auxwrapfinish(L, r);
}
static int cocreate(lua_State* L)
{
luaL_checktype(L, 1, LUA_TFUNCTION);
lua_State* NL = lua_newthread(L);
lua_xpush(L, NL, 1); // push function on top of NL
return 1;
}
static int cowrap(lua_State* L)
{
cocreate(L);
lua_pushcclosurek(L, auxwrapy, NULL, 1, auxwrapcont);
return 1;
}
static int coyield(lua_State* L)
{
int nres = cast_int(L->top - L->base);
return lua_yield(L, nres);
}
static int corunning(lua_State* L)
{
if (lua_pushthread(L))
lua_pushnil(L); // main thread is not a coroutine
return 1;
}
static int coyieldable(lua_State* L)
{
lua_pushboolean(L, lua_isyieldable(L));
return 1;
}
static int coclose(lua_State* L)
{
lua_State* co = lua_tothread(L, 1);
luaL_argexpected(L, co, 1, "thread");
int status = lua_costatus(L, co);
if (status != LUA_COFIN && status != LUA_COERR && status != LUA_COSUS)
luaL_error(L, "cannot close %s coroutine", statnames[status]);
if (co->status == LUA_OK || co->status == LUA_YIELD)
{
lua_pushboolean(L, true);
lua_resetthread(co);
return 1;
}
else
{
lua_pushboolean(L, false);
if (co->status == LUA_ERRMEM)
lua_pushstring(L, LUA_MEMERRMSG);
else if (co->status == LUA_ERRERR)
lua_pushstring(L, LUA_ERRERRMSG);
else if (lua_gettop(co))
lua_xmove(co, L, 1); // move error message
lua_resetthread(co);
return 2;
}
}
static const luaL_Reg co_funcs[] = {
{"create", cocreate},
{"running", corunning},
{"status", costatus},
{"wrap", cowrap},
{"yield", coyield},
{"isyieldable", coyieldable},
{"close", coclose},
{NULL, NULL},
};
int luaopen_coroutine(lua_State* L)
{
luaL_register(L, LUA_COLIBNAME, co_funcs);
lua_pushcclosurek(L, coresumey, "resume", 0, coresumecont);
lua_setfield(L, -2, "resume");
return 1;
}