diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 86284d74..e6a01255 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,7 +20,7 @@ jobs: unix: strategy: matrix: - os: [{name: ubuntu, version: ubuntu-latest}, {name: macos, version: macos-latest}] + os: [{name: ubuntu, version: ubuntu-latest}, {name: macos, version: macos-latest}, {name: macos-arm, version: macos-14}] name: ${{matrix.os.name}} runs-on: ${{matrix.os.version}} steps: diff --git a/Analysis/src/Autocomplete.cpp b/Analysis/src/Autocomplete.cpp index 75fd9f58..87a593bd 100644 --- a/Analysis/src/Autocomplete.cpp +++ b/Analysis/src/Autocomplete.cpp @@ -16,6 +16,7 @@ LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution); LUAU_FASTFLAG(DebugLuauReadWriteProperties); LUAU_FASTFLAGVARIABLE(LuauAutocompleteStringLiteralBounds, false); +LUAU_FASTFLAGVARIABLE(LuauAutocompleteTableKeysNoInitialCharacter, false); static const std::unordered_set kStatementStartingKeywords = { "while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"}; @@ -1742,6 +1743,37 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M } } } + else if (AstExprTable* exprTable = node->as(); exprTable && FFlag::LuauAutocompleteTableKeysNoInitialCharacter) + { + AutocompleteEntryMap result; + + if (auto it = module->astExpectedTypes.find(exprTable)) + { + result = autocompleteProps(*module, typeArena, builtinTypes, *it, PropIndexType::Key, ancestry); + + // If the key type is a union of singleton strings, + // suggest those too. + if (auto ttv = get(follow(*it)); ttv && ttv->indexer) + { + autocompleteStringSingleton(ttv->indexer->indexType, false, node, position, result); + } + + // Remove keys that are already completed + for (const auto& item : exprTable->items) + { + if (!item.key) + continue; + + if (auto stringKey = item.key->as()) + result.erase(std::string(stringKey->value.data, stringKey->value.size)); + } + } + + // Also offer general expression suggestions + autocompleteExpression(sourceModule, *module, builtinTypes, typeArena, ancestry, position, result); + + return {result, ancestry, AutocompleteContext::Property}; + } else if (isIdentifier(node) && (parent->is() || parent->is())) return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry, AutocompleteContext::Statement}; diff --git a/VM/src/linit.cpp b/VM/src/linit.cpp index b7491952..d2176a97 100644 --- a/VM/src/linit.cpp +++ b/VM/src/linit.cpp @@ -43,9 +43,13 @@ void luaL_sandbox(lua_State* L) // set all builtin metatables to read-only lua_pushliteral(L, ""); - lua_getmetatable(L, -1); - lua_setreadonly(L, -1, true); - lua_pop(L, 2); + if (lua_getmetatable(L, -1)) + { + lua_setreadonly(L, -1, true); + lua_pop(L, 2); + } + else + lua_pop(L, 1); // set globals to readonly and activate safeenv since the env is immutable lua_setreadonly(L, LUA_GLOBALSINDEX, true); diff --git a/tests/Autocomplete.test.cpp b/tests/Autocomplete.test.cpp index 8bed89cd..5b031e79 100644 --- a/tests/Autocomplete.test.cpp +++ b/tests/Autocomplete.test.cpp @@ -16,6 +16,7 @@ LUAU_FASTFLAG(LuauTraceTypesInNonstrictMode2) LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel) LUAU_FASTFLAG(LuauAutocompleteStringLiteralBounds); +LUAU_FASTFLAG(LuauAutocompleteTableKeysNoInitialCharacter) using namespace Luau; @@ -2693,6 +2694,43 @@ local t = { CHECK_EQ(ac.context, AutocompleteContext::Property); } +TEST_CASE_FIXTURE(ACFixture, "suggest_table_keys_no_initial_character") +{ + ScopedFastFlag sff{FFlag::LuauAutocompleteTableKeysNoInitialCharacter, true}; + + check(R"( +type Test = { first: number, second: number } +local t: Test = { @1 } + )"); + + auto ac = autocomplete('1'); + CHECK(ac.entryMap.count("first")); + CHECK(ac.entryMap.count("second")); + CHECK_EQ(ac.context, AutocompleteContext::Property); + + check(R"( +type Test = { first: number, second: number } +local t: Test = { first = 1, @1 } + )"); + + ac = autocomplete('1'); + CHECK_EQ(ac.entryMap.count("first"), 0); + CHECK(ac.entryMap.count("second")); + CHECK_EQ(ac.context, AutocompleteContext::Property); + + check(R"( +type Properties = { TextScaled: boolean, Text: string } +local function create(props: Properties) end + +create({ @1 }) + )"); + + ac = autocomplete('1'); + CHECK(ac.entryMap.count("TextScaled")); + CHECK(ac.entryMap.count("Text")); + CHECK_EQ(ac.context, AutocompleteContext::Property); +} + TEST_CASE_FIXTURE(ACFixture, "autocomplete_documentation_symbols") { loadDefinition(R"( diff --git a/tests/Conformance.test.cpp b/tests/Conformance.test.cpp index 2d6a5ac7..298c5322 100644 --- a/tests/Conformance.test.cpp +++ b/tests/Conformance.test.cpp @@ -884,6 +884,17 @@ TEST_CASE("NewUserdataOverflow") CHECK(strcmp(lua_tostring(L, -1), "memory allocation error: block too big") == 0); } +TEST_CASE("SandboxWithoutLibs") +{ + StateRef globalState(luaL_newstate(), lua_close); + lua_State* L = globalState.get(); + + luaopen_base(L); // Load only base library + luaL_sandbox(L); + + CHECK(lua_getreadonly(L, LUA_GLOBALSINDEX)); +} + TEST_CASE("ApiTables") { StateRef globalState(luaL_newstate(), lua_close);