Merge branch 'master' into merge

This commit is contained in:
Arseny Kapoulkine 2021-11-11 18:21:14 -08:00
commit ce0bbdda59
28 changed files with 483 additions and 79 deletions

View File

@ -1,5 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Help and support
- name: Questions
url: https://github.com/Roblox/luau/discussions
about: Please use GitHub Discussions if you have questions or need help.

1
.gitignore vendored
View File

@ -5,3 +5,4 @@
^default.prof*
^fuzz-*
^luau$
/.vs

View File

@ -496,7 +496,7 @@ static bool canSuggestInferredType(ScopePtr scope, TypeId ty)
return false;
// No syntax for unnamed tables with a metatable
if (const MetatableTypeVar* mtv = get<MetatableTypeVar>(ty))
if (get<MetatableTypeVar>(ty))
return false;
if (const TableTypeVar* ttv = get<TableTypeVar>(ty))
@ -688,7 +688,7 @@ static std::optional<bool> functionIsExpectedAt(const Module& module, AstNode* n
TypeId expectedType = follow(*it);
if (const FunctionTypeVar* ftv = get<FunctionTypeVar>(expectedType))
if (get<FunctionTypeVar>(expectedType))
return true;
if (const IntersectionTypeVar* itv = get<IntersectionTypeVar>(expectedType))

View File

@ -2129,7 +2129,7 @@ TypeId TypeChecker::checkRelationalOperation(
if (!isNonstrictMode() && !isOrOp)
return ty;
if (auto i = get<UnionTypeVar>(ty))
if (get<UnionTypeVar>(ty))
{
std::optional<TypeId> cleaned = tryStripUnionFromNil(ty);

View File

@ -216,7 +216,7 @@ bool finite(TypePackId tp)
if (auto pack = get<TypePack>(tp))
return pack->tail ? finite(*pack->tail) : true;
if (auto pack = get<VariadicTypePack>(tp))
if (get<VariadicTypePack>(tp))
return false;
return true;

View File

@ -749,9 +749,9 @@ void StateDot::visitChild(TypeId ty, int parentIndex, const char* linkName)
if (opts.duplicatePrimitives && canDuplicatePrimitive(ty))
{
if (const PrimitiveTypeVar* ptv = get<PrimitiveTypeVar>(ty))
if (get<PrimitiveTypeVar>(ty))
formatAppend(result, "n%d [label=\"%s\"];\n", index, toStringDetailed(ty, {}).name.c_str());
else if (const AnyTypeVar* atv = get<AnyTypeVar>(ty))
else if (get<AnyTypeVar>(ty))
formatAppend(result, "n%d [label=\"any\"];\n", index);
}
else
@ -902,19 +902,19 @@ void StateDot::visitChildren(TypeId ty, int index)
finishNodeLabel(ty);
finishNode();
}
else if (const AnyTypeVar* atv = get<AnyTypeVar>(ty))
else if (get<AnyTypeVar>(ty))
{
formatAppend(result, "AnyTypeVar %d", index);
finishNodeLabel(ty);
finishNode();
}
else if (const PrimitiveTypeVar* ptv = get<PrimitiveTypeVar>(ty))
else if (get<PrimitiveTypeVar>(ty))
{
formatAppend(result, "PrimitiveTypeVar %s", toStringDetailed(ty, {}).name.c_str());
finishNodeLabel(ty);
finishNode();
}
else if (const ErrorTypeVar* etv = get<ErrorTypeVar>(ty))
else if (get<ErrorTypeVar>(ty))
{
formatAppend(result, "ErrorTypeVar %d", index);
finishNodeLabel(ty);
@ -994,7 +994,7 @@ void StateDot::visitChildren(TypePackId tp, int index)
finishNodeLabel(tp);
finishNode();
}
else if (const Unifiable::Error* etp = get<Unifiable::Error>(tp))
else if (get<Unifiable::Error>(tp))
{
formatAppend(result, "ErrorTypePack %d", index);
finishNodeLabel(tp);

View File

@ -2379,15 +2379,12 @@ AstArray<AstTypeOrPack> Parser::parseTypeParams()
Lexeme begin = lexer.current();
nextLexeme();
bool seenPack = false;
while (true)
{
if (FFlag::LuauParseTypePackTypeParameters)
{
if (shouldParseTypePackAnnotation(lexer))
{
seenPack = true;
auto typePack = parseTypePackAnnotation();
if (FFlag::LuauTypeAliasPacks) // Type packs are recorded only is we can handle them
@ -2399,8 +2396,6 @@ AstArray<AstTypeOrPack> Parser::parseTypeParams()
if (typePack)
{
seenPack = true;
if (FFlag::LuauTypeAliasPacks) // Type packs are recorded only is we can handle them
parameters.push_back({{}, typePack});
}

View File

@ -34,8 +34,10 @@ static void report(ReportFormat format, const char* name, const Luau::Location&
}
}
static void reportError(ReportFormat format, const char* name, const Luau::TypeError& error)
static void reportError(ReportFormat format, const Luau::TypeError& error)
{
const char* name = error.moduleName.c_str();
if (const Luau::SyntaxError* syntaxError = Luau::get_if<Luau::SyntaxError>(&error.data))
report(format, name, error.location, "SyntaxError", syntaxError->message.c_str());
else
@ -49,7 +51,10 @@ static void reportWarning(ReportFormat format, const char* name, const Luau::Lin
static bool analyzeFile(Luau::Frontend& frontend, const char* name, ReportFormat format, bool annotate)
{
Luau::CheckResult cr = frontend.check(name);
Luau::CheckResult cr;
if (frontend.isDirty(name))
cr = frontend.check(name);
if (!frontend.getSourceModule(name))
{
@ -58,7 +63,7 @@ static bool analyzeFile(Luau::Frontend& frontend, const char* name, ReportFormat
}
for (auto& error : cr.errors)
reportError(format, name, error);
reportError(format, error);
Luau::LintResult lr = frontend.lint(name);
@ -115,7 +120,12 @@ struct CliFileResolver : Luau::FileResolver
{
if (Luau::AstExprConstantString* expr = node->as<Luau::AstExprConstantString>())
{
Luau::ModuleName name = std::string(expr->value.data, expr->value.size) + ".lua";
Luau::ModuleName name = std::string(expr->value.data, expr->value.size) + ".luau";
if (!moduleExists(name))
{
// fall back to .lua if a module with .luau doesn't exist
name = std::string(expr->value.data, expr->value.size) + ".lua";
}
return {{name}};
}
@ -236,8 +246,15 @@ int main(int argc, char** argv)
if (isDirectory(argv[i]))
{
traverseDirectory(argv[i], [&](const std::string& name) {
if (name.length() > 4 && name.rfind(".lua") == name.length() - 4)
// Look for .luau first and if absent, fall back to .lua
if (name.length() > 5 && name.rfind(".luau") == name.length() - 5)
{
failed += !analyzeFile(frontend, name.c_str(), format, annotate);
}
else if (name.length() > 4 && name.rfind(".lua") == name.length() - 4)
{
failed += !analyzeFile(frontend, name.c_str(), format, annotate);
}
});
}
else
@ -256,5 +273,3 @@ int main(int argc, char** argv)
return (format == ReportFormat::Luacheck) ? 0 : failed;
}

View File

@ -13,6 +13,17 @@
#include <memory>
#ifdef _WIN32
#include <io.h>
#include <fcntl.h>
#endif
enum class CompileFormat
{
Default,
Binary
};
static int lua_loadstring(lua_State* L)
{
size_t l = 0;
@ -51,9 +62,13 @@ static int lua_require(lua_State* L)
return finishrequire(L);
lua_pop(L, 1);
std::optional<std::string> source = readFile(name + ".lua");
std::optional<std::string> source = readFile(name + ".luau");
if (!source)
luaL_argerrorL(L, 1, ("error loading " + name).c_str());
{
source = readFile(name + ".lua"); // try .lua if .luau doesn't exist
if (!source)
luaL_argerrorL(L, 1, ("error loading " + name).c_str()); // if neither .luau nor .lua exist, we have an error
}
// module needs to run in a new thread, isolated from the rest
lua_State* GL = lua_mainthread(L);
@ -183,6 +198,11 @@ static std::string runCode(lua_State* L, const std::string& source)
error += "\nstack backtrace:\n";
error += lua_debugtrace(T);
#ifdef __EMSCRIPTEN__
// nicer formatting for errors in web repl
error = "Error:" + error;
#endif
fprintf(stdout, "%s", error.c_str());
}
@ -190,6 +210,44 @@ static std::string runCode(lua_State* L, const std::string& source)
return std::string();
}
#ifdef __EMSCRIPTEN__
extern "C"
{
const char* executeScript(const char* source)
{
// static string for caching result (prevents dangling ptr on function exit)
static std::string result;
// setup flags
for (Luau::FValue<bool>* flag = Luau::FValue<bool>::list; flag; flag = flag->next)
if (strncmp(flag->name, "Luau", 4) == 0)
flag->value = true;
// create new state
std::unique_ptr<lua_State, void (*)(lua_State*)> globalState(luaL_newstate(), lua_close);
lua_State* L = globalState.get();
// setup state
setupState(L);
// sandbox thread
luaL_sandboxthread(L);
// run code + collect error
std::string error = runCode(L, source);
result = error;
if (error.length())
{
return result.c_str();
}
return NULL;
}
}
#endif
// Excluded from emscripten compilation to avoid -Wunused-function errors.
#ifndef __EMSCRIPTEN__
static void completeIndexer(lua_State* L, const char* editBuffer, size_t start, std::vector<std::string>& completions)
{
std::string_view lookup = editBuffer + start;
@ -366,7 +424,7 @@ static void reportError(const char* name, const Luau::CompileError& error)
report(name, error.getLocation(), "CompileError", error.what());
}
static bool compileFile(const char* name)
static bool compileFile(const char* name, CompileFormat format)
{
std::optional<std::string> source = readFile(name);
if (!source)
@ -383,7 +441,15 @@ static bool compileFile(const char* name)
Luau::compileOrThrow(bcb, *source);
switch (format)
{
case CompileFormat::Default:
printf("%s", bcb.dumpEverything().c_str());
break;
case CompileFormat::Binary:
fwrite(bcb.getBytecode().data(), 1, bcb.getBytecode().size(), stdout);
break;
}
return true;
}
@ -408,7 +474,7 @@ static void displayHelp(const char* argv0)
printf("\n");
printf("Available modes:\n");
printf(" omitted: compile and run input files one by one\n");
printf(" --compile: compile input files and output resulting bytecode\n");
printf(" --compile[=format]: compile input files and output resulting formatted bytecode (binary or text)\n");
printf("\n");
printf("Available options:\n");
printf(" --profile[=N]: profile the code using N Hz sampling (default 10000) and output results to profile.out\n");
@ -440,8 +506,19 @@ int main(int argc, char** argv)
return 0;
}
if (argc >= 2 && strcmp(argv[1], "--compile") == 0)
if (argc >= 2 && strncmp(argv[1], "--compile", strlen("--compile")) == 0)
{
CompileFormat format = CompileFormat::Default;
if (strcmp(argv[1], "--compile=binary") == 0)
format = CompileFormat::Binary;
#ifdef _WIN32
if (format == CompileFormat::Binary)
_setmode(_fileno(stdout), _O_BINARY);
#endif
int failed = 0;
for (int i = 2; i < argc; ++i)
@ -452,13 +529,15 @@ int main(int argc, char** argv)
if (isDirectory(argv[i]))
{
traverseDirectory(argv[i], [&](const std::string& name) {
if (name.length() > 4 && name.rfind(".lua") == name.length() - 4)
failed += !compileFile(name.c_str());
if (name.length() > 5 && name.rfind(".luau") == name.length() - 5)
failed += !compileFile(name.c_str(), format);
else if (name.length() > 4 && name.rfind(".lua") == name.length() - 4)
failed += !compileFile(name.c_str(), format);
});
}
else
{
failed += !compileFile(argv[i]);
failed += !compileFile(argv[i], format);
}
}
@ -511,5 +590,4 @@ int main(int argc, char** argv)
return failed;
}
}
#endif

View File

@ -17,17 +17,26 @@ add_library(Luau.VM STATIC)
if(LUAU_BUILD_CLI)
add_executable(Luau.Repl.CLI)
if(NOT EMSCRIPTEN)
add_executable(Luau.Analyze.CLI)
else()
# add -fexceptions for emscripten to allow exceptions to be caught in C++
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fexceptions")
endif()
# This also adds target `name` on Linux/macOS and `name.exe` on Windows
set_target_properties(Luau.Repl.CLI PROPERTIES OUTPUT_NAME luau)
if(NOT EMSCRIPTEN)
set_target_properties(Luau.Analyze.CLI PROPERTIES OUTPUT_NAME luau-analyze)
endif()
endif()
if(LUAU_BUILD_TESTS)
if(LUAU_BUILD_TESTS AND NOT EMSCRIPTEN)
add_executable(Luau.UnitTest)
add_executable(Luau.Conformance)
endif()
include(Sources.cmake)
target_compile_features(Luau.Ast PUBLIC cxx_std_17)
@ -53,10 +62,6 @@ if(MSVC)
else()
list(APPEND LUAU_OPTIONS -Wall) # All warnings
list(APPEND LUAU_OPTIONS -Werror) # Warnings are errors
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
list(APPEND LUAU_OPTIONS -Wno-unused) # GCC considers variables declared/checked in if() as unused
endif()
endif()
target_compile_options(Luau.Ast PRIVATE ${LUAU_OPTIONS})
@ -65,7 +70,10 @@ target_compile_options(Luau.VM PRIVATE ${LUAU_OPTIONS})
if(LUAU_BUILD_CLI)
target_compile_options(Luau.Repl.CLI PRIVATE ${LUAU_OPTIONS})
if(NOT EMSCRIPTEN)
target_compile_options(Luau.Analyze.CLI PRIVATE ${LUAU_OPTIONS})
endif()
target_include_directories(Luau.Repl.CLI PRIVATE extern)
target_link_libraries(Luau.Repl.CLI PRIVATE Luau.Compiler Luau.VM)
@ -74,10 +82,20 @@ if(LUAU_BUILD_CLI)
target_link_libraries(Luau.Repl.CLI PRIVATE pthread)
endif()
if(NOT EMSCRIPTEN)
target_link_libraries(Luau.Analyze.CLI PRIVATE Luau.Analysis)
endif()
if(EMSCRIPTEN)
# declare exported functions to emscripten
target_link_options(Luau.Repl.CLI PRIVATE -sEXPORTED_FUNCTIONS=['_executeScript'] -sEXPORTED_RUNTIME_METHODS=['ccall','cwrap'] -fexceptions)
# custom output directory for wasm + js file
set_target_properties(Luau.Repl.CLI PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/docs/assets/luau)
endif()
endif()
if(LUAU_BUILD_TESTS)
if(LUAU_BUILD_TESTS AND NOT EMSCRIPTEN)
target_compile_options(Luau.UnitTest PRIVATE ${LUAU_OPTIONS})
target_include_directories(Luau.UnitTest PRIVATE extern)
target_link_libraries(Luau.UnitTest PRIVATE Luau.Analysis Luau.Compiler)

View File

@ -36,6 +36,10 @@ struct CompileOptions
// global builtin to construct vectors; disabled by default
const char* vectorLib = nullptr;
const char* vectorCtor = nullptr;
// array of globals that are mutable; disables the import optimization for fields accessed through them
// use NULL to end the array
const char** mutableGlobals = nullptr;
};
class CompileError : public std::exception

View File

@ -13,6 +13,7 @@
LUAU_FASTFLAGVARIABLE(LuauPreloadClosures, false)
LUAU_FASTFLAGVARIABLE(LuauPreloadClosuresFenv, false)
LUAU_FASTFLAGVARIABLE(LuauPreloadClosuresUpval, false)
LUAU_FASTFLAGVARIABLE(LuauGenericSpecialGlobals, false)
LUAU_FASTFLAG(LuauIfElseExpressionBaseSupport)
namespace Luau
@ -1277,7 +1278,7 @@ struct Compiler
{
const Global* global = globals.find(expr->name);
return options.optimizationLevel >= 1 && (!global || (!global->written && !global->special));
return options.optimizationLevel >= 1 && (!global || (!global->written && !global->writable));
}
void compileExprIndexName(AstExprIndexName* expr, uint8_t target)
@ -2465,9 +2466,10 @@ struct Compiler
}
else if (node->is<AstStatBreak>())
{
LUAU_ASSERT(!loops.empty());
// before exiting out of the loop, we need to close all local variables that were captured in closures since loop start
// normally they are closed by the enclosing blocks, including the loop block, but we're skipping that here
LUAU_ASSERT(!loops.empty());
closeLocals(loops.back().localOffset);
size_t label = bytecode.emitLabel();
@ -2478,12 +2480,13 @@ struct Compiler
}
else if (AstStatContinue* stat = node->as<AstStatContinue>())
{
LUAU_ASSERT(!loops.empty());
if (loops.back().untilCondition)
validateContinueUntil(stat, loops.back().untilCondition);
// before continuing, we need to close all local variables that were captured in closures since loop start
// normally they are closed by the enclosing blocks, including the loop block, but we're skipping that here
LUAU_ASSERT(!loops.empty());
closeLocals(loops.back().localOffset);
size_t label = bytecode.emitLabel();
@ -2900,6 +2903,11 @@ struct Compiler
break;
case AstExprUnary::Len:
if (arg.type == Constant::Type_String)
{
result.type = Constant::Type_Number;
result.valueNumber = double(arg.valueString.size);
}
break;
default:
@ -3440,7 +3448,7 @@ struct Compiler
struct Global
{
bool special = false;
bool writable = false;
bool written = false;
};
@ -3498,7 +3506,7 @@ struct Compiler
{
Global* g = globals.find(object->name);
return !g || (!g->special && !g->written) ? Builtin{object->name, expr->index} : Builtin();
return !g || (!g->writable && !g->written) ? Builtin{object->name, expr->index} : Builtin();
}
else
{
@ -3696,13 +3704,26 @@ void compileOrThrow(BytecodeBuilder& bytecode, AstStatBlock* root, const AstName
Compiler compiler(bytecode, options);
// since access to some global objects may result in values that change over time, we block table imports
// since access to some global objects may result in values that change over time, we block imports from non-readonly tables
if (FFlag::LuauGenericSpecialGlobals)
{
if (AstName name = names.get("_G"); name.value)
compiler.globals[name].writable = true;
if (options.mutableGlobals)
for (const char** ptr = options.mutableGlobals; *ptr != NULL; ++ptr)
{
if (AstName name = names.get(*ptr); name.value)
compiler.globals[name].writable = true;
}
}
else
{
for (const char* global : kSpecialGlobals)
{
AstName name = names.get(global);
if (name.value)
compiler.globals[name].special = true;
if (AstName name = names.get(global); name.value)
compiler.globals[name].writable = true;
}
}
// this visitor traverses the AST to analyze mutability of locals/globals, filling Local::written and Global::written

View File

@ -49,7 +49,10 @@ OBJECTS=$(AST_OBJECTS) $(COMPILER_OBJECTS) $(ANALYSIS_OBJECTS) $(VM_OBJECTS) $(T
CXXFLAGS=-g -Wall -Werror
LDFLAGS=
CXXFLAGS+=-Wno-unused # temporary, for older gcc versions
# temporary, for older gcc versions as they treat var in `if (type var = val)` as unused
ifeq ($(findstring g++,$(shell $(CXX) --version)),g++)
CXXFLAGS+=-Wno-unused
endif
# configuration-specific flags
ifeq ($(config),release)

View File

@ -22,7 +22,7 @@ You can download the binaries from [a recent release](https://github.com/Roblox/
# Building
To build Luau tools or tests yourself, you can use CMake on all platforms, or alternatively make (on Linux/macOS). For example:
To build Luau tools or tests yourself, you can use CMake on all platforms:
```sh
mkdir cmake && cd cmake
@ -31,6 +31,12 @@ cmake --build . --target Luau.Repl.CLI --config RelWithDebInfo
cmake --build . --target Luau.Analyze.CLI --config RelWithDebInfo
```
Alternatively, on Linux/macOS you can use make:
```sh
make config=release luau luau-analyze
```
To integrate Luau into your CMake application projects, at the minimum you'll need to depend on `Luau.Compiler` and `Luau.VM` projects. From there you need to create a new Luau state (using Lua 5.x API such as `lua_newstate`), compile source to bytecode and load it into the VM like this:
```cpp

View File

@ -346,6 +346,8 @@ struct lua_Debug
* can only be changed when the VM is not running any code */
struct lua_Callbacks
{
void* userdata; /* arbitrary userdata pointer that is never overwritten by Luau */
void (*interrupt)(lua_State* L, int gc); /* gets called at safepoints (loop back edges, call/ret, gc) if set */
void (*panic)(lua_State* L, int errcode); /* gets called when an unprotected error is raised (if longjmp is used) */

View File

@ -703,6 +703,7 @@ void lua_setreadonly(lua_State* L, int objindex, bool value)
const TValue* o = index2adr(L, objindex);
api_check(L, ttistable(o));
Table* t = hvalue(o);
api_check(L, t != hvalue(registry(L)));
t->readonly = value;
return;
}

View File

@ -20,8 +20,9 @@
// If types of the arguments mismatch, luauF_* needs to return -1 and the execution will fall back to the usual call path
// If luauF_* succeeds, it needs to return *all* requested arguments, filling results with nil as appropriate.
// On input, nparams refers to the actual number of arguments (0+), whereas nresults contains LUA_MULTRET for arbitrary returns or 0+ for a
// fixed-length return Because of this, and the fact that "extra" returned values will be ignored, implementations below typically check that nresults
// is <= expected number, which covers the LUA_MULTRET case.
// fixed-length return
// Because of this, and the fact that "extra" returned values will be ignored, implementations below typically check that nresults is <= expected
// number, which covers the LUA_MULTRET case.
static int luauF_assert(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams)
{

View File

@ -30,6 +30,7 @@ LUAU_FASTFLAGVARIABLE(LuauArrayBoundary, false)
#define MAXBITS 26
#define MAXSIZE (1 << MAXBITS)
static_assert(offsetof(LuaNode, val) == 0, "Unexpected Node memory layout, pointer cast in gval2slot is incorrect");
// TKey is bitpacked for memory efficiency so we need to validate bit counts for worst case
static_assert(TKey{{NULL}, 0, LUA_TDEADKEY, 0}.tt == LUA_TDEADKEY, "not enough bits for tt");
static_assert(TKey{{NULL}, 0, LUA_TNIL, MAXSIZE - 1}.next == MAXSIZE - 1, "not enough bits for next");

View File

@ -9,7 +9,6 @@
#define gval(n) (&(n)->val)
#define gnext(n) ((n)->key.next)
static_assert(offsetof(LuaNode, val) == 0, "Unexpected Node memory layout, pointer cast below is incorrect");
#define gval2slot(t, v) int(cast_to(LuaNode*, static_cast<const TValue*>(v)) - t->node)
LUAI_FUNC const TValue* luaH_getnum(Table* t, int key);

View File

@ -3,6 +3,8 @@ main:
url: /news
- title: Getting Started
url: /getting-started
- title: GitHub
url: https://github.com/Roblox/luau
pages:
- title: Getting Started
@ -25,3 +27,7 @@ pages:
url: /profile
- title: Library
url: /library
# Remove demo pages until solution is found
# - title: Demo
# url: /demo

50
docs/_includes/repl.html Normal file
View File

@ -0,0 +1,50 @@
<form>
<div>
<label>Script:</label>
<br>
<textarea rows="10" cols="70" id="script">print("Hello World!")</textarea>
<br><br>
<button onclick="clearInput(); return false;">
Clear Input
</button>
<button onclick="executeScript(); return false;">
Run
</button>
</div>
<br><br>
<div>
<label>Output:</label>
<br>
<textarea readonly rows="10" cols="70" id="output"></textarea>
<br><br>
<button onclick="clearOutput(); return false;">
Clear Output
</button>
</div>
</form>
<script>
function output(text) {
document.getElementById("output").value += "[" + new Date().toLocaleTimeString() + "] " + text.replace('stdin:', '') + "\n";
}
var Module = {
'print': function (msg) { output(msg) }
};
function clearInput() {
document.getElementById("script").value = "";
}
function clearOutput() {
document.getElementById("output").value = "";
}
function executeScript() {
var err = Module.ccall('executeScript', 'string', ['string'], [document.getElementById("script").value]);
if (err) {
output('Error:' + err.replace('stdin:', ''));
}
}
</script>
<script async src="assets/luau/luau.js"></script>

6
docs/_pages/demo.md Normal file
View File

@ -0,0 +1,6 @@
---
permalink: /demo
title: Demo
---
{% include repl.html %}

View File

@ -8,7 +8,7 @@ To get started with Luau you need to use `luau` command line binary to run your
## Creating a script
To create your own testing script, create a new file with `.lua` as the extension:
To create your own testing script, create a new file with `.luau` as the extension:
```lua
function ispositive(x)
@ -26,7 +26,7 @@ print(isfoo("bar"))
print(isfoo(1))
```
You can now run the file using `luau test.lua` and analyze it using `luau-analyze test.lua`.
You can now run the file using `luau test.luau` and analyze it using `luau-analyze test.luau`.
Note that there are no warnings about calling ``ispositive()`` with a string, or calling ``isfoo()`` a number. This is because the type checking uses non-strict mode by default, which is lenient in how it infers types used by the program.
@ -52,8 +52,8 @@ In this case, Luau will use the ``return x > 0`` statement to infer that ``ispos
Based on Luau's type inference, the analysis tool will now flag the incorrect call to ``ispositive()``:
```
$ luau-analyze test.lua
test.lua(7,18): TypeError: Type 'string' could not be converted into 'number'
$ luau-analyze test.luau
test.luau(7,18): TypeError: Type 'string' could not be converted into 'number'
```
## Annotations
@ -92,9 +92,9 @@ result = ispositive(1)
Oops -- we're returning string values, but we forgot to update the function return type. Since we've told Luau that ``ispositive()`` returns a boolean (and that's how we're using it), the call site isn't flagged as an error. But because the annotation doesn't match our code, we get a warning in the function body itself:
```
$ luau-analyze test.lua
test.lua(5,9): TypeError: Type 'string' could not be converted into 'boolean'
test.lua(7,9): TypeError: Type 'string' could not be converted into 'boolean'
$ luau-analyze test.luau
test.luau(5,9): TypeError: Type 'string' could not be converted into 'boolean'
test.luau(7,9): TypeError: Type 'string' could not be converted into 'boolean'
```
The fix is simple; just change the annotation to declare the return type as a string:
@ -117,8 +117,8 @@ result = ispositive(1)
Well, almost - since we declared ``result`` as a boolean, the call site is now flagged:
```
$ luau-analyze test.lua
test.lua(12,10): TypeError: Type 'string' could not be converted into 'boolean'
$ luau-analyze test.luau
test.luau(12,10): TypeError: Type 'string' could not be converted into 'boolean'
```
If we update the type of the local variable, everything is good. Note that we could also just let Luau infer the type of ``result`` by changing it to the single line version ``local result = ispositive(1)``.

View File

@ -56,4 +56,4 @@ This profiler doesn't track leaf C functions and instead attributes the time spe
slow, consider not just the work it does immediately but also the library functions it calls.
This profiler tracks time consumed by Luau thread stacks; when a thread calls another thread via `coroutine.resume`, the time spent is not attributed to the parent thread that's
waiting for resume results. This limitation will be removed in the future in the future.
waiting for resume results. This limitation will be removed in the future.

View File

@ -46,16 +46,6 @@ This is using the VM feature that is not accessible from scripts, that prevents
By itself this would mean that code that runs in Luau can't use globals at all, since assigning globals would fail. While this is feasible, in Roblox we solve this by creating a new global table for each script, that uses `__index` to point to the builtin global table. This safely sandboxes the builtin globals while still allowing writing globals from each script. This also means that short of exposing special shared globals from the host, all scripts are isolated from each other.
## Thread identity
Environment-level sandboxing is sufficient to implement separation between trusted code and untrusted code, assuming that `getfenv`/`setfenv` are either unavailable (removed from the globals), or that trusted code never interfaces with untrusted code (which prevents untrusted code from ever getting access to trusted functions). When running trusted code, it's possible to inject extra globals from the host into that global table, providing access to special APIs.
However, in some cases it's desirable to restrict access to functions that are exposed both to trusted and untrusted code. For example, both may have access to `game` global, but `game` may expose methods that should only work from trusted code.
To achieve this, each thread in Luau has a security identity, which can only be set by the host. Newly created threads inherit identities from the parent thread, and functions exposed from the host can validate the identity of the calling thread. This makes it possible to provide APIs to trusted code while limiting the access from untrusted code.
> Note: to achieve an even stronger guarantee of isolation between trusted and untrusted code, it's possible to run it in different Luau VMs, which is what Roblox does for extra safety.
## `__gc`
Lua 5.1 exposes a `__gc` metamethod for userdata, which can be used on proxies (`newproxy`) to hook into garbage collector. Later versions of Lua extend this mechanism to work on tables.

View File

@ -351,6 +351,30 @@ local onlyString: string = stringOrNumber -- ok
local onlyNumber: number = stringOrNumber -- not ok
```
## Typecasts
Expressions may be typecast using `::`. Typecasting is useful for specifying the type of an expression when the automatically inferred type is too generic.
For example, consider the following table constructor where the intent is to store a table of names:
```lua
local myTable = {names = {}}
table.insert(myTable.names, 42) -- Inserting a number ought to cause a type error, but doesn't
```
In order to specify the type of the `names` table a typecast may be used:
```lua
local myTable = {names = {} :: {string}}
table.insert(myTable.names, 42) -- not ok, invalid 'number' to 'string' conversion
```
A typecast itself is also type checked to ensure the conversion is made to a subtype of the expression's type or `any`:
```lua
local numericValue = 1
local value = numericValue :: any -- ok, all expressions may be cast to 'any'
local flag = numericValue :: boolean -- not ok, invalid 'number' to 'boolean' conversion
```
## Roblox types
Roblox supports a rich set of classes and data types, [documented here](https://developer.roblox.com/en-us/api-reference). All of them are readily available for the type checker to use by their name (e.g. `Part` or `RaycastResult`).
@ -397,3 +421,10 @@ return module
```
There are some caveats here though. For instance, the require path must be resolvable statically, otherwise Luau cannot accurately type check it.
### Cyclic module dependencies
Cyclic module dependencies can cause problems for the type checker. In order to break a module dependency cycle a typecast of the module to `any` may be used:
```lua
local myModule = require(MyModule) :: any
```

View File

@ -0,0 +1,50 @@
# bit32.countlz/countrz
## Summary
Add bit32.countlz (count left zeroes) and bit32.countrz (count right zeroes) to accelerate bit scanning
## Motivation
All CPUs have instructions to determine the position of first/last set bit in an integer. These instructions have a variety of uses, the popular ones being:
- Fast implementation of integer logarithm (essentially allowing to compute `floor(log2(value))` quickly)
- Scanning set bits in an integer, which allows efficient traversal of compact representation of bitmaps
- Allocating bits out of a bitmap quickly
Today it's possible to approximate `countlz` using `floor` and `log` but this approximation is relatively slow; approximating `countrz` is difficult without iterating through each bit.
## Design
`bit32` library will gain two new functions, `countlz` and `countrz`:
```
function bit32.countlz(n: number): number
function bit32.countrz(n: number): number
```
`countlz` takes an integer number (converting the input number to a 32-bit unsigned integer as all other `bit32` functions do), and returns the number of consecutive left-most zero bits - that is, the number of most significant zero bits in a 32-bit number until the first 1. The result is in `[0, 32]` range.
For example, when the input number is `0`, it's `32`. When the input number is `2^k`, the result is `31-k`.
`countrz` takes an integer number (converting the input number to a 32-bit unsigned integer as all other `bit32` functions do), and returns the number of consecutive right-most zero bits - that is,
the number of least significant zero bits in a 32-bit number until the first 1. The result is in `[0, 32]` range.
For example, when the input number is `0`, it's `32`. When the input number is `2^k`, the result is `k`.
> Non-normative: a proof of concept implementation shows that a polyfill for `countlz` takes ~34 ns per loop iteration when computing `countlz` for an increasing number sequence, whereas
> a builtin implementation takes ~4 ns.
## Drawbacks
None known.
## Alternatives
These functions can be alternatively specified as "find the position of the most/least significant bit set" (e.g. "ffs"/"fls" for "find first set"/"find last set"). This formulation
can be more immediately useful since the bit position is usually more important than the number of bits. However, the bit position is undefined when the input number is zero,
returning a sentinel such as -1 seems non-idiomatic, and returning `nil` seems awkward for calling code. Counting functions don't have this problem.
An early version of this proposal suggested `clz`/`ctz` (leading/trailing) as names; however, using a full verb is more consistent with other operations like shift/rotate, and left/right may be easier to understand intuitively compared to leading/trailing. left/right are used by C++20.
Of the two functions, `countlz` is vastly more useful than `countrz`; we could implement just `countlz`, but having both is nice for symmetry.

View File

@ -13,6 +13,7 @@
LUAU_FASTFLAG(LuauPreloadClosures)
LUAU_FASTFLAG(LuauPreloadClosuresFenv)
LUAU_FASTFLAG(LuauPreloadClosuresUpval)
LUAU_FASTFLAG(LuauGenericSpecialGlobals)
using namespace Luau;
@ -1168,6 +1169,17 @@ RETURN R0 1
)");
}
TEST_CASE("ConstantFoldStringLen")
{
CHECK_EQ("\n" + compileFunction0("return #'string', #'', #'a', #('b')"), R"(
LOADN R0 6
LOADN R1 0
LOADN R2 1
LOADN R3 1
RETURN R0 4
)");
}
TEST_CASE("ConstantFoldCompare")
{
// ordered comparisons
@ -3659,4 +3671,118 @@ RETURN R0 0
)");
}
TEST_CASE("LuauGenericSpecialGlobals")
{
const char* source = R"(
print()
Game.print()
Workspace.print()
_G.print()
game.print()
plugin.print()
script.print()
shared.print()
workspace.print()
)";
{
ScopedFastFlag genericSpecialGlobals{"LuauGenericSpecialGlobals", false};
// Check Roblox globals are here
CHECK_EQ("\n" + compileFunction0(source), R"(
GETIMPORT R0 1
CALL R0 0 0
GETIMPORT R1 3
GETTABLEKS R0 R1 K0
CALL R0 0 0
GETIMPORT R1 5
GETTABLEKS R0 R1 K0
CALL R0 0 0
GETIMPORT R1 7
GETTABLEKS R0 R1 K0
CALL R0 0 0
GETIMPORT R1 9
GETTABLEKS R0 R1 K0
CALL R0 0 0
GETIMPORT R1 11
GETTABLEKS R0 R1 K0
CALL R0 0 0
GETIMPORT R1 13
GETTABLEKS R0 R1 K0
CALL R0 0 0
GETIMPORT R1 15
GETTABLEKS R0 R1 K0
CALL R0 0 0
GETIMPORT R1 17
GETTABLEKS R0 R1 K0
CALL R0 0 0
RETURN R0 0
)");
}
ScopedFastFlag genericSpecialGlobals{"LuauGenericSpecialGlobals", true};
// Check Roblox globals are no longer here
CHECK_EQ("\n" + compileFunction0(source), R"(
GETIMPORT R0 1
CALL R0 0 0
GETIMPORT R0 3
CALL R0 0 0
GETIMPORT R0 5
CALL R0 0 0
GETIMPORT R1 7
GETTABLEKS R0 R1 K0
CALL R0 0 0
GETIMPORT R0 9
CALL R0 0 0
GETIMPORT R0 11
CALL R0 0 0
GETIMPORT R0 13
CALL R0 0 0
GETIMPORT R0 15
CALL R0 0 0
GETIMPORT R0 17
CALL R0 0 0
RETURN R0 0
)");
// Check we can add them back
Luau::BytecodeBuilder bcb;
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code);
Luau::CompileOptions options;
const char* mutableGlobals[] = {"Game", "Workspace", "game", "plugin", "script", "shared", "workspace", NULL};
options.mutableGlobals = &mutableGlobals[0];
Luau::compileOrThrow(bcb, source, options);
CHECK_EQ("\n" + bcb.dumpFunction(0), R"(
GETIMPORT R0 1
CALL R0 0 0
GETIMPORT R1 3
GETTABLEKS R0 R1 K0
CALL R0 0 0
GETIMPORT R1 5
GETTABLEKS R0 R1 K0
CALL R0 0 0
GETIMPORT R1 7
GETTABLEKS R0 R1 K0
CALL R0 0 0
GETIMPORT R1 9
GETTABLEKS R0 R1 K0
CALL R0 0 0
GETIMPORT R1 11
GETTABLEKS R0 R1 K0
CALL R0 0 0
GETIMPORT R1 13
GETTABLEKS R0 R1 K0
CALL R0 0 0
GETIMPORT R1 15
GETTABLEKS R0 R1 K0
CALL R0 0 0
GETIMPORT R1 17
GETTABLEKS R0 R1 K0
CALL R0 0 0
RETURN R0 0
)");
}
TEST_SUITE_END();