Merge remote-tracking branch 'origin/master' into merge

This commit is contained in:
Andy Friesen 2022-11-18 10:52:40 -08:00
commit d5ff348e4e
4 changed files with 140 additions and 17 deletions

View file

@ -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 {};

View file

@ -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()

View file

@ -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);
} }

View file

@ -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")