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/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"(