mirror of
https://github.com/luau-lang/luau.git
synced 2024-12-13 05:20:38 +00:00
Merge remote-tracking branch 'origin/master' into merge
This commit is contained in:
commit
d5ff348e4e
4 changed files with 140 additions and 17 deletions
|
@ -1219,6 +1219,31 @@ static std::optional<const ClassTypeVar*> getMethodContainingClass(const ModuleP
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool stringPartOfInterpString(const AstNode* node, Position position)
|
||||||
|
{
|
||||||
|
const AstExprInterpString* interpString = node->as<AstExprInterpString>();
|
||||||
|
if (!interpString)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const AstExpr* expression : interpString->expressions)
|
||||||
|
{
|
||||||
|
if (expression->location.containsClosed(position))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isSimpleInterpolatedString(const AstNode* node)
|
||||||
|
{
|
||||||
|
const AstExprInterpString* interpString = node->as<AstExprInterpString>();
|
||||||
|
return interpString != nullptr && interpString->expressions.size == 0;
|
||||||
|
}
|
||||||
|
|
||||||
static std::optional<AutocompleteEntryMap> autocompleteStringParams(const SourceModule& sourceModule, const ModulePtr& module,
|
static std::optional<AutocompleteEntryMap> autocompleteStringParams(const SourceModule& sourceModule, const ModulePtr& module,
|
||||||
const std::vector<AstNode*>& nodes, Position position, StringCompletionCallback callback)
|
const std::vector<AstNode*>& nodes, Position position, StringCompletionCallback callback)
|
||||||
{
|
{
|
||||||
|
@ -1227,7 +1252,7 @@ static std::optional<AutocompleteEntryMap> autocompleteStringParams(const Source
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!nodes.back()->is<AstExprConstantString>() && !nodes.back()->is<AstExprError>())
|
if (!nodes.back()->is<AstExprConstantString>() && !isSimpleInterpolatedString(nodes.back()) && !nodes.back()->is<AstExprError>())
|
||||||
{
|
{
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
@ -1432,7 +1457,7 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
|
||||||
return autocompleteExpression(sourceModule, *module, singletonTypes, &typeArena, ancestry, position);
|
return autocompleteExpression(sourceModule, *module, singletonTypes, &typeArena, ancestry, position);
|
||||||
else if (AstStatRepeat* statRepeat = extractStat<AstStatRepeat>(ancestry); statRepeat)
|
else if (AstStatRepeat* statRepeat = extractStat<AstStatRepeat>(ancestry); statRepeat)
|
||||||
return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry, AutocompleteContext::Statement};
|
return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry, AutocompleteContext::Statement};
|
||||||
else if (AstExprTable* exprTable = parent->as<AstExprTable>(); exprTable && (node->is<AstExprGlobal>() || node->is<AstExprConstantString>()))
|
else if (AstExprTable* exprTable = parent->as<AstExprTable>(); exprTable && (node->is<AstExprGlobal>() || node->is<AstExprConstantString>() || node->is<AstExprInterpString>()))
|
||||||
{
|
{
|
||||||
for (const auto& [kind, key, value] : exprTable->items)
|
for (const auto& [kind, key, value] : exprTable->items)
|
||||||
{
|
{
|
||||||
|
@ -1471,7 +1496,7 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
|
||||||
{
|
{
|
||||||
return {*ret, ancestry, AutocompleteContext::String};
|
return {*ret, ancestry, AutocompleteContext::String};
|
||||||
}
|
}
|
||||||
else if (node->is<AstExprConstantString>())
|
else if (node->is<AstExprConstantString>() || isSimpleInterpolatedString(node))
|
||||||
{
|
{
|
||||||
AutocompleteEntryMap result;
|
AutocompleteEntryMap result;
|
||||||
|
|
||||||
|
@ -1497,6 +1522,13 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
|
||||||
|
|
||||||
return {result, ancestry, AutocompleteContext::String};
|
return {result, ancestry, AutocompleteContext::String};
|
||||||
}
|
}
|
||||||
|
else if (stringPartOfInterpString(node, position))
|
||||||
|
{
|
||||||
|
// We're not a simple interpolated string, we're something like `a{"b"}@1`, and we
|
||||||
|
// can't know what to format to
|
||||||
|
AutocompleteEntryMap map;
|
||||||
|
return {map, ancestry, AutocompleteContext::String};
|
||||||
|
}
|
||||||
|
|
||||||
if (node->is<AstExprConstantNumber>())
|
if (node->is<AstExprConstantNumber>())
|
||||||
return {};
|
return {};
|
||||||
|
|
|
@ -2661,6 +2661,7 @@ AstExpr* Parser::parseInterpString()
|
||||||
TempVector<AstExpr*> expressions(scratchExpr);
|
TempVector<AstExpr*> expressions(scratchExpr);
|
||||||
|
|
||||||
Location startLocation = lexer.current().location;
|
Location startLocation = lexer.current().location;
|
||||||
|
Location endLocation;
|
||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
|
@ -2668,16 +2669,16 @@ AstExpr* Parser::parseInterpString()
|
||||||
LUAU_ASSERT(currentLexeme.type == Lexeme::InterpStringBegin || currentLexeme.type == Lexeme::InterpStringMid ||
|
LUAU_ASSERT(currentLexeme.type == Lexeme::InterpStringBegin || currentLexeme.type == Lexeme::InterpStringMid ||
|
||||||
currentLexeme.type == Lexeme::InterpStringEnd || currentLexeme.type == Lexeme::InterpStringSimple);
|
currentLexeme.type == Lexeme::InterpStringEnd || currentLexeme.type == Lexeme::InterpStringSimple);
|
||||||
|
|
||||||
Location location = currentLexeme.location;
|
endLocation = currentLexeme.location;
|
||||||
|
|
||||||
Location startOfBrace = Location(location.end, 1);
|
Location startOfBrace = Location(endLocation.end, 1);
|
||||||
|
|
||||||
scratchData.assign(currentLexeme.data, currentLexeme.length);
|
scratchData.assign(currentLexeme.data, currentLexeme.length);
|
||||||
|
|
||||||
if (!Lexer::fixupQuotedString(scratchData))
|
if (!Lexer::fixupQuotedString(scratchData))
|
||||||
{
|
{
|
||||||
nextLexeme();
|
nextLexeme();
|
||||||
return reportExprError(startLocation, {}, "Interpolated string literal contains malformed escape sequence");
|
return reportExprError(Location{startLocation, endLocation}, {}, "Interpolated string literal contains malformed escape sequence");
|
||||||
}
|
}
|
||||||
|
|
||||||
AstArray<char> chars = copy(scratchData);
|
AstArray<char> chars = copy(scratchData);
|
||||||
|
@ -2688,15 +2689,36 @@ AstExpr* Parser::parseInterpString()
|
||||||
|
|
||||||
if (currentLexeme.type == Lexeme::InterpStringEnd || currentLexeme.type == Lexeme::InterpStringSimple)
|
if (currentLexeme.type == Lexeme::InterpStringEnd || currentLexeme.type == Lexeme::InterpStringSimple)
|
||||||
{
|
{
|
||||||
AstArray<AstArray<char>> stringsArray = copy(strings);
|
break;
|
||||||
AstArray<AstExpr*> expressionsArray = copy(expressions);
|
|
||||||
|
|
||||||
return allocator.alloc<AstExprInterpString>(startLocation, stringsArray, expressionsArray);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AstExpr* expression = parseExpr();
|
bool errorWhileChecking = false;
|
||||||
|
|
||||||
expressions.push_back(expression);
|
switch (lexer.current().type)
|
||||||
|
{
|
||||||
|
case Lexeme::InterpStringMid:
|
||||||
|
case Lexeme::InterpStringEnd:
|
||||||
|
{
|
||||||
|
errorWhileChecking = true;
|
||||||
|
nextLexeme();
|
||||||
|
expressions.push_back(reportExprError(endLocation, {}, "Malformed interpolated string, expected expression inside '{}'"));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Lexeme::BrokenString:
|
||||||
|
{
|
||||||
|
errorWhileChecking = true;
|
||||||
|
nextLexeme();
|
||||||
|
expressions.push_back(reportExprError(endLocation, {}, "Malformed interpolated string, did you forget to add a '`'?"));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
expressions.push_back(parseExpr());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errorWhileChecking)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
switch (lexer.current().type)
|
switch (lexer.current().type)
|
||||||
{
|
{
|
||||||
|
@ -2706,14 +2728,18 @@ AstExpr* Parser::parseInterpString()
|
||||||
break;
|
break;
|
||||||
case Lexeme::BrokenInterpDoubleBrace:
|
case Lexeme::BrokenInterpDoubleBrace:
|
||||||
nextLexeme();
|
nextLexeme();
|
||||||
return reportExprError(location, {}, ERROR_INVALID_INTERP_DOUBLE_BRACE);
|
return reportExprError(endLocation, {}, ERROR_INVALID_INTERP_DOUBLE_BRACE);
|
||||||
case Lexeme::BrokenString:
|
case Lexeme::BrokenString:
|
||||||
nextLexeme();
|
nextLexeme();
|
||||||
return reportExprError(location, {}, "Malformed interpolated string, did you forget to add a '}'?");
|
return reportExprError(endLocation, {}, "Malformed interpolated string, did you forget to add a '}'?");
|
||||||
default:
|
default:
|
||||||
return reportExprError(location, {}, "Malformed interpolated string, got %s", lexer.current().toString().c_str());
|
return reportExprError(endLocation, {}, "Malformed interpolated string, got %s", lexer.current().toString().c_str());
|
||||||
}
|
}
|
||||||
} while (true);
|
} while (true);
|
||||||
|
|
||||||
|
AstArray<AstArray<char>> stringsArray = copy(strings);
|
||||||
|
AstArray<AstExpr*> expressionsArray = copy(expressions);
|
||||||
|
return allocator.alloc<AstExprInterpString>(Location{startLocation, endLocation}, stringsArray, expressionsArray);
|
||||||
}
|
}
|
||||||
|
|
||||||
AstExpr* Parser::parseNumber()
|
AstExpr* Parser::parseNumber()
|
||||||
|
|
|
@ -183,7 +183,7 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstExprInterpString")
|
||||||
AstStat* statement = expectParseStatement("local a = `var = {x}`");
|
AstStat* statement = expectParseStatement("local a = `var = {x}`");
|
||||||
|
|
||||||
std::string_view expected =
|
std::string_view expected =
|
||||||
R"({"type":"AstStatLocal","location":"0,0 - 0,18","vars":[{"luauType":null,"name":"a","type":"AstLocal","location":"0,6 - 0,7"}],"values":[{"type":"AstExprInterpString","location":"0,10 - 0,18","strings":["var = ",""],"expressions":[{"type":"AstExprGlobal","location":"0,18 - 0,19","global":"x"}]}]})";
|
R"({"type":"AstStatLocal","location":"0,0 - 0,21","vars":[{"luauType":null,"name":"a","type":"AstLocal","location":"0,6 - 0,7"}],"values":[{"type":"AstExprInterpString","location":"0,10 - 0,21","strings":["var = ",""],"expressions":[{"type":"AstExprGlobal","location":"0,18 - 0,19","global":"x"}]}]})";
|
||||||
|
|
||||||
CHECK(toJson(statement) == expected);
|
CHECK(toJson(statement) == expected);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include "Luau/StringUtils.h"
|
#include "Luau/StringUtils.h"
|
||||||
|
|
||||||
#include "Fixture.h"
|
#include "Fixture.h"
|
||||||
|
#include "ScopedFlags.h"
|
||||||
|
|
||||||
#include "doctest.h"
|
#include "doctest.h"
|
||||||
|
|
||||||
|
@ -2708,13 +2709,77 @@ a = if temp then even else abc@3
|
||||||
CHECK(ac.entryMap.count("abcdef"));
|
CHECK(ac.entryMap.count("abcdef"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(ACFixture, "autocomplete_interpolated_string")
|
TEST_CASE_FIXTURE(ACFixture, "autocomplete_interpolated_string_constant")
|
||||||
{
|
{
|
||||||
|
ScopedFastFlag sff{"LuauInterpolatedStringBaseSupport", true};
|
||||||
|
|
||||||
|
check(R"(f(`@1`))");
|
||||||
|
auto ac = autocomplete('1');
|
||||||
|
CHECK(ac.entryMap.empty());
|
||||||
|
CHECK_EQ(ac.context, AutocompleteContext::String);
|
||||||
|
|
||||||
|
check(R"(f(`@1 {"a"}`))");
|
||||||
|
ac = autocomplete('1');
|
||||||
|
CHECK(ac.entryMap.empty());
|
||||||
|
CHECK_EQ(ac.context, AutocompleteContext::String);
|
||||||
|
|
||||||
|
check(R"(f(`{"a"} @1`))");
|
||||||
|
ac = autocomplete('1');
|
||||||
|
CHECK(ac.entryMap.empty());
|
||||||
|
CHECK_EQ(ac.context, AutocompleteContext::String);
|
||||||
|
|
||||||
|
check(R"(f(`{"a"} @1 {"b"}`))");
|
||||||
|
ac = autocomplete('1');
|
||||||
|
CHECK(ac.entryMap.empty());
|
||||||
|
CHECK_EQ(ac.context, AutocompleteContext::String);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(ACFixture, "autocomplete_interpolated_string_expression")
|
||||||
|
{
|
||||||
|
ScopedFastFlag sff{"LuauInterpolatedStringBaseSupport", true};
|
||||||
|
|
||||||
check(R"(f(`expression = {@1}`))");
|
check(R"(f(`expression = {@1}`))");
|
||||||
|
auto ac = autocomplete('1');
|
||||||
|
CHECK(ac.entryMap.count("table"));
|
||||||
|
CHECK_EQ(ac.context, AutocompleteContext::Expression);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(ACFixture, "autocomplete_interpolated_string_expression_with_comments")
|
||||||
|
{
|
||||||
|
ScopedFastFlag sff{"LuauInterpolatedStringBaseSupport", true};
|
||||||
|
|
||||||
|
check(R"(f(`expression = {--[[ bla bla bla ]]@1`))");
|
||||||
|
|
||||||
auto ac = autocomplete('1');
|
auto ac = autocomplete('1');
|
||||||
CHECK(ac.entryMap.count("table"));
|
CHECK(ac.entryMap.count("table"));
|
||||||
CHECK_EQ(ac.context, AutocompleteContext::Expression);
|
CHECK_EQ(ac.context, AutocompleteContext::Expression);
|
||||||
|
|
||||||
|
check(R"(f(`expression = {@1 --[[ bla bla bla ]]`))");
|
||||||
|
ac = autocomplete('1');
|
||||||
|
CHECK(!ac.entryMap.empty());
|
||||||
|
CHECK(ac.entryMap.count("table"));
|
||||||
|
CHECK_EQ(ac.context, AutocompleteContext::Expression);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(ACFixture, "autocomplete_interpolated_string_as_singleton")
|
||||||
|
{
|
||||||
|
ScopedFastFlag sff{"LuauInterpolatedStringBaseSupport", true};
|
||||||
|
|
||||||
|
check(R"(
|
||||||
|
--!strict
|
||||||
|
local function f(a: "cat" | "dog") end
|
||||||
|
|
||||||
|
f(`@1`)
|
||||||
|
f(`uhhh{'try'}@2`)
|
||||||
|
)");
|
||||||
|
|
||||||
|
auto ac = autocomplete('1');
|
||||||
|
CHECK(ac.entryMap.count("cat"));
|
||||||
|
CHECK_EQ(ac.context, AutocompleteContext::String);
|
||||||
|
|
||||||
|
ac = autocomplete('2');
|
||||||
|
CHECK(ac.entryMap.empty());
|
||||||
|
CHECK_EQ(ac.context, AutocompleteContext::String);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(ACFixture, "autocomplete_explicit_type_pack")
|
TEST_CASE_FIXTURE(ACFixture, "autocomplete_explicit_type_pack")
|
||||||
|
|
Loading…
Reference in a new issue