luau/tests/Lexer.test.cpp

433 lines
13 KiB
C++
Raw Normal View History

2022-07-28 21:24:07 -07:00
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Lexer.h"
#include "Fixture.h"
#include "ScopedFlags.h"
#include "doctest.h"
using namespace Luau;
LUAU_FASTFLAG(LexerFixInterpStringStart)
2022-07-28 21:24:07 -07:00
TEST_SUITE_BEGIN("LexerTests");
TEST_CASE("broken_string_works")
{
const std::string testInput = "[[";
Luau::Allocator alloc;
AstNameTable table(alloc);
Lexer lexer(testInput.c_str(), testInput.size(), table);
Lexeme lexeme = lexer.next();
CHECK_EQ(lexeme.type, Lexeme::Type::BrokenString);
CHECK_EQ(lexeme.location, Luau::Location(Luau::Position(0, 0), Luau::Position(0, 2)));
}
TEST_CASE("broken_comment")
{
const std::string testInput = "--[[ ";
Luau::Allocator alloc;
AstNameTable table(alloc);
Lexer lexer(testInput.c_str(), testInput.size(), table);
Lexeme lexeme = lexer.next();
CHECK_EQ(lexeme.type, Lexeme::Type::BrokenComment);
CHECK_EQ(lexeme.location, Luau::Location(Luau::Position(0, 0), Luau::Position(0, 6)));
}
TEST_CASE("broken_comment_kept")
{
const std::string testInput = "--[[ ";
Luau::Allocator alloc;
AstNameTable table(alloc);
Lexer lexer(testInput.c_str(), testInput.size(), table);
lexer.setSkipComments(true);
CHECK_EQ(lexer.next().type, Lexeme::Type::BrokenComment);
}
TEST_CASE("comment_skipped")
{
const std::string testInput = "-- ";
Luau::Allocator alloc;
AstNameTable table(alloc);
Lexer lexer(testInput.c_str(), testInput.size(), table);
lexer.setSkipComments(true);
CHECK_EQ(lexer.next().type, Lexeme::Type::Eof);
}
TEST_CASE("multilineCommentWithLexemeInAndAfter")
{
const std::string testInput = "--[[ function \n"
"]] end";
Luau::Allocator alloc;
AstNameTable table(alloc);
Lexer lexer(testInput.c_str(), testInput.size(), table);
Lexeme comment = lexer.next();
Lexeme end = lexer.next();
CHECK_EQ(comment.type, Lexeme::Type::BlockComment);
CHECK_EQ(comment.location, Luau::Location(Luau::Position(0, 0), Luau::Position(1, 2)));
CHECK_EQ(end.type, Lexeme::Type::ReservedEnd);
CHECK_EQ(end.location, Luau::Location(Luau::Position(1, 3), Luau::Position(1, 6)));
}
TEST_CASE("testBrokenEscapeTolerant")
{
const std::string testInput = "'\\3729472897292378'";
Luau::Allocator alloc;
AstNameTable table(alloc);
Lexer lexer(testInput.c_str(), testInput.size(), table);
Lexeme item = lexer.next();
CHECK_EQ(item.type, Lexeme::QuotedString);
CHECK_EQ(item.location, Luau::Location(Luau::Position(0, 0), Luau::Position(0, int(testInput.size()))));
}
TEST_CASE("testBigDelimiters")
{
const std::string testInput = "--[===[\n"
"\n"
"\n"
"\n"
"]===]";
Luau::Allocator alloc;
AstNameTable table(alloc);
Lexer lexer(testInput.c_str(), testInput.size(), table);
Lexeme item = lexer.next();
CHECK_EQ(item.type, Lexeme::Type::BlockComment);
CHECK_EQ(item.location, Luau::Location(Luau::Position(0, 0), Luau::Position(4, 5)));
}
TEST_CASE("lookahead")
{
const std::string testInput = "foo --[[ comment ]] bar : nil end";
Luau::Allocator alloc;
AstNameTable table(alloc);
Lexer lexer(testInput.c_str(), testInput.size(), table);
lexer.setSkipComments(true);
lexer.next(); // must call next() before reading data from lexer at least once
CHECK_EQ(lexer.current().type, Lexeme::Name);
CHECK_EQ(lexer.current().name, std::string("foo"));
CHECK_EQ(lexer.lookahead().type, Lexeme::Name);
CHECK_EQ(lexer.lookahead().name, std::string("bar"));
lexer.next();
CHECK_EQ(lexer.current().type, Lexeme::Name);
CHECK_EQ(lexer.current().name, std::string("bar"));
CHECK_EQ(lexer.lookahead().type, ':');
lexer.next();
CHECK_EQ(lexer.current().type, ':');
CHECK_EQ(lexer.lookahead().type, Lexeme::ReservedNil);
lexer.next();
CHECK_EQ(lexer.current().type, Lexeme::ReservedNil);
CHECK_EQ(lexer.lookahead().type, Lexeme::ReservedEnd);
lexer.next();
CHECK_EQ(lexer.current().type, Lexeme::ReservedEnd);
CHECK_EQ(lexer.lookahead().type, Lexeme::Eof);
lexer.next();
CHECK_EQ(lexer.current().type, Lexeme::Eof);
CHECK_EQ(lexer.lookahead().type, Lexeme::Eof);
}
TEST_CASE("string_interpolation_basic")
{
const std::string testInput = R"(`foo {"bar"}`)";
Luau::Allocator alloc;
AstNameTable table(alloc);
Lexer lexer(testInput.c_str(), testInput.size(), table);
Lexeme interpBegin = lexer.next();
CHECK_EQ(interpBegin.type, Lexeme::InterpStringBegin);
Lexeme quote = lexer.next();
CHECK_EQ(quote.type, Lexeme::QuotedString);
Lexeme interpEnd = lexer.next();
CHECK_EQ(interpEnd.type, Lexeme::InterpStringEnd);
// The InterpStringEnd should start with }, not `.
CHECK_EQ(interpEnd.location.begin.column, FFlag::LexerFixInterpStringStart ? 11 : 12);
}
Sync to upstream/release/566 (#853) * Fixed incorrect lexeme generated for string parts in the middle of an interpolated string (Fixes https://github.com/Roblox/luau/issues/744) * DeprecatedApi lint can report some issues without type inference information * Fixed performance of autocomplete requests when suggestions have large intersection types (Solves https://github.com/Roblox/luau/discussions/847) * Marked `table.getn`/`foreach`/`foreachi` as deprecated ([RFC: Deprecate table.getn/foreach/foreachi](https://github.com/Roblox/luau/blob/master/rfcs/deprecate-table-getn-foreach.md)) * With -O2 optimization level, we now optimize builtin calls based on known argument/return count. Note that this change can be observable if `getfenv/setfenv` is used to substitute a builtin, especially if arity is different. Fastcall heavy tests show a 1-2% improvement. * Luau can now be built with clang-cl (Fixes https://github.com/Roblox/luau/issues/736) We also made many improvements to our experimental components. For our new type solver: * Overhauled data flow analysis system, fixed issues with 'repeat' loops, global variables and type annotations * Type refinements now work on generic table indexing with a string literal * Type refinements will properly track potentially 'nil' values (like t[x] for a missing key) and their further refinements * Internal top table type is now isomorphic to `{}` which fixes issues when `typeof(v) == 'table'` type refinement is handled * References to non-existent types in type annotations no longer resolve to 'error' type like in old solver * Improved handling of class unions in property access expressions * Fixed default type packs * Unsealed tables can now have metatables * Restored expected types for function arguments And for native code generation: * Added min and max IR instructions mapping to vminsd/vmaxsd on x64 * We now speculatively extract direct execution fast-paths based on expected types of expressions which provides better optimization opportunities inside a single basic block * Translated existing math fastcalls to IR form to improve tag guard removal and constant propagation
2023-03-03 22:21:14 +02:00
TEST_CASE("string_interpolation_full")
{
const std::string testInput = R"(`foo {"bar"} {"baz"} end`)";
Luau::Allocator alloc;
AstNameTable table(alloc);
Lexer lexer(testInput.c_str(), testInput.size(), table);
Lexeme interpBegin = lexer.next();
CHECK_EQ(interpBegin.type, Lexeme::InterpStringBegin);
CHECK_EQ(interpBegin.toString(), "`foo {");
Lexeme quote1 = lexer.next();
CHECK_EQ(quote1.type, Lexeme::QuotedString);
CHECK_EQ(quote1.toString(), "\"bar\"");
Lexeme interpMid = lexer.next();
CHECK_EQ(interpMid.type, Lexeme::InterpStringMid);
CHECK_EQ(interpMid.toString(), "} {");
CHECK_EQ(interpMid.location.begin.column, FFlag::LexerFixInterpStringStart ? 11 : 12);
Sync to upstream/release/566 (#853) * Fixed incorrect lexeme generated for string parts in the middle of an interpolated string (Fixes https://github.com/Roblox/luau/issues/744) * DeprecatedApi lint can report some issues without type inference information * Fixed performance of autocomplete requests when suggestions have large intersection types (Solves https://github.com/Roblox/luau/discussions/847) * Marked `table.getn`/`foreach`/`foreachi` as deprecated ([RFC: Deprecate table.getn/foreach/foreachi](https://github.com/Roblox/luau/blob/master/rfcs/deprecate-table-getn-foreach.md)) * With -O2 optimization level, we now optimize builtin calls based on known argument/return count. Note that this change can be observable if `getfenv/setfenv` is used to substitute a builtin, especially if arity is different. Fastcall heavy tests show a 1-2% improvement. * Luau can now be built with clang-cl (Fixes https://github.com/Roblox/luau/issues/736) We also made many improvements to our experimental components. For our new type solver: * Overhauled data flow analysis system, fixed issues with 'repeat' loops, global variables and type annotations * Type refinements now work on generic table indexing with a string literal * Type refinements will properly track potentially 'nil' values (like t[x] for a missing key) and their further refinements * Internal top table type is now isomorphic to `{}` which fixes issues when `typeof(v) == 'table'` type refinement is handled * References to non-existent types in type annotations no longer resolve to 'error' type like in old solver * Improved handling of class unions in property access expressions * Fixed default type packs * Unsealed tables can now have metatables * Restored expected types for function arguments And for native code generation: * Added min and max IR instructions mapping to vminsd/vmaxsd on x64 * We now speculatively extract direct execution fast-paths based on expected types of expressions which provides better optimization opportunities inside a single basic block * Translated existing math fastcalls to IR form to improve tag guard removal and constant propagation
2023-03-03 22:21:14 +02:00
Lexeme quote2 = lexer.next();
CHECK_EQ(quote2.type, Lexeme::QuotedString);
CHECK_EQ(quote2.toString(), "\"baz\"");
Lexeme interpEnd = lexer.next();
CHECK_EQ(interpEnd.type, Lexeme::InterpStringEnd);
CHECK_EQ(interpEnd.toString(), "} end`");
CHECK_EQ(interpEnd.location.begin.column, FFlag::LexerFixInterpStringStart ? 19 : 20);
Sync to upstream/release/566 (#853) * Fixed incorrect lexeme generated for string parts in the middle of an interpolated string (Fixes https://github.com/Roblox/luau/issues/744) * DeprecatedApi lint can report some issues without type inference information * Fixed performance of autocomplete requests when suggestions have large intersection types (Solves https://github.com/Roblox/luau/discussions/847) * Marked `table.getn`/`foreach`/`foreachi` as deprecated ([RFC: Deprecate table.getn/foreach/foreachi](https://github.com/Roblox/luau/blob/master/rfcs/deprecate-table-getn-foreach.md)) * With -O2 optimization level, we now optimize builtin calls based on known argument/return count. Note that this change can be observable if `getfenv/setfenv` is used to substitute a builtin, especially if arity is different. Fastcall heavy tests show a 1-2% improvement. * Luau can now be built with clang-cl (Fixes https://github.com/Roblox/luau/issues/736) We also made many improvements to our experimental components. For our new type solver: * Overhauled data flow analysis system, fixed issues with 'repeat' loops, global variables and type annotations * Type refinements now work on generic table indexing with a string literal * Type refinements will properly track potentially 'nil' values (like t[x] for a missing key) and their further refinements * Internal top table type is now isomorphic to `{}` which fixes issues when `typeof(v) == 'table'` type refinement is handled * References to non-existent types in type annotations no longer resolve to 'error' type like in old solver * Improved handling of class unions in property access expressions * Fixed default type packs * Unsealed tables can now have metatables * Restored expected types for function arguments And for native code generation: * Added min and max IR instructions mapping to vminsd/vmaxsd on x64 * We now speculatively extract direct execution fast-paths based on expected types of expressions which provides better optimization opportunities inside a single basic block * Translated existing math fastcalls to IR form to improve tag guard removal and constant propagation
2023-03-03 22:21:14 +02:00
}
TEST_CASE("string_interpolation_double_brace")
{
const std::string testInput = R"(`foo{{bad}}bar`)";
Luau::Allocator alloc;
AstNameTable table(alloc);
Lexer lexer(testInput.c_str(), testInput.size(), table);
auto brokenInterpBegin = lexer.next();
CHECK_EQ(brokenInterpBegin.type, Lexeme::BrokenInterpDoubleBrace);
CHECK_EQ(std::string(brokenInterpBegin.data, brokenInterpBegin.getLength()), std::string("foo"));
CHECK_EQ(lexer.next().type, Lexeme::Name);
auto interpEnd = lexer.next();
CHECK_EQ(interpEnd.type, Lexeme::InterpStringEnd);
CHECK_EQ(std::string(interpEnd.data, interpEnd.getLength()), std::string("}bar"));
}
TEST_CASE("string_interpolation_double_but_unmatched_brace")
{
const std::string testInput = R"(`{{oops}`, 1)";
Luau::Allocator alloc;
AstNameTable table(alloc);
Lexer lexer(testInput.c_str(), testInput.size(), table);
CHECK_EQ(lexer.next().type, Lexeme::BrokenInterpDoubleBrace);
CHECK_EQ(lexer.next().type, Lexeme::Name);
CHECK_EQ(lexer.next().type, Lexeme::InterpStringEnd);
CHECK_EQ(lexer.next().type, ',');
CHECK_EQ(lexer.next().type, Lexeme::Number);
}
TEST_CASE("string_interpolation_unmatched_brace")
{
const std::string testInput = R"({
`hello {"world"}
} -- this might be incorrectly parsed as a string)";
Luau::Allocator alloc;
AstNameTable table(alloc);
Lexer lexer(testInput.c_str(), testInput.size(), table);
CHECK_EQ(lexer.next().type, '{');
CHECK_EQ(lexer.next().type, Lexeme::InterpStringBegin);
CHECK_EQ(lexer.next().type, Lexeme::QuotedString);
CHECK_EQ(lexer.next().type, Lexeme::BrokenString);
CHECK_EQ(lexer.next().type, '}');
}
TEST_CASE("string_interpolation_with_unicode_escape")
{
const std::string testInput = R"(`\u{1F41B}`)";
Luau::Allocator alloc;
AstNameTable table(alloc);
Lexer lexer(testInput.c_str(), testInput.size(), table);
CHECK_EQ(lexer.next().type, Lexeme::InterpStringSimple);
CHECK_EQ(lexer.next().type, Lexeme::Eof);
}
Sync to upstream/release/660 (#1643) # General This release introduces initial work on a Roundtrippable AST for Luau, and numerous fixes to the new type solver, runtime, and fragment autocomplete. ## Roundtrippable AST To support tooling around source code transformations, we are extending the parser to retain source information so that we can re-emit the initial source code exactly as the author wrote it. We have made numerous changes to the Transpiler, added new AST types such as `AstTypeGroup`, and added source information to AST nodes such as `AstExprInterpString`, `AstExprIfElse`, `AstTypeTable`, `AstTypeReference`, `AstTypeSingletonString`, and `AstTypeTypeof`. ## New Type Solver * Implement `setmetatable` and `getmetatable` type functions. * Fix handling of nested and recursive union type functions to prevent the solver from getting stuck. * Free types in both old and new solver now have an upper and lower bound to resolve mixed mode usage of the solvers in fragment autocomplete. * Fix infinite recursion during normalization of cyclic tables. * Add normalization support for intersections of subclasses with negated superclasses. ## Runtime * Fix compilation error in Luau buffer bit operations for big-endian machines. ## Miscellaneous * Add test and bugfixes to fragment autocomplete. * Fixed `clang-tidy` warnings in `Simplify.cpp`. **Full Changelog**: https://github.com/luau-lang/luau/compare/0.659...0.660 --- Co-authored-by: Ariel Weiss <aaronweiss@roblox.com> Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com> Co-authored-by: Talha Pathan <tpathan@roblox.com> Co-authored-by: Varun Saini <vsaini@roblox.com> Co-authored-by: Vighnesh Vijay <vvijay@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com> --------- Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com> Co-authored-by: Varun Saini <61795485+vrn-sn@users.noreply.github.com> Co-authored-by: Alexander Youngblood <ayoungblood@roblox.com> Co-authored-by: Menarul Alam <malam@roblox.com>
2025-02-07 16:17:11 -08:00
TEST_CASE("single_quoted_string")
{
const std::string testInput = "'test'";
Luau::Allocator alloc;
AstNameTable table(alloc);
Lexer lexer(testInput.c_str(), testInput.size(), table);
Lexeme lexeme = lexer.next();
CHECK_EQ(lexeme.type, Lexeme::QuotedString);
CHECK_EQ(lexeme.getQuoteStyle(), Lexeme::QuoteStyle::Single);
}
TEST_CASE("double_quoted_string")
{
const std::string testInput = R"("test")";
Luau::Allocator alloc;
AstNameTable table(alloc);
Lexer lexer(testInput.c_str(), testInput.size(), table);
Lexeme lexeme = lexer.next();
CHECK_EQ(lexeme.type, Lexeme::QuotedString);
CHECK_EQ(lexeme.getQuoteStyle(), Lexeme::QuoteStyle::Double);
}
TEST_CASE("lexer_determines_string_block_depth_0")
{
const std::string testInput = "[[ test ]]";
Luau::Allocator alloc;
AstNameTable table(alloc);
Lexer lexer(testInput.c_str(), testInput.size(), table);
Lexeme lexeme = lexer.next();
REQUIRE_EQ(lexeme.type, Lexeme::RawString);
CHECK_EQ(lexeme.getBlockDepth(), 0);
}
TEST_CASE("lexer_determines_string_block_depth_0_multiline_1")
{
const std::string testInput = R"([[ test
]])";
Luau::Allocator alloc;
AstNameTable table(alloc);
Lexer lexer(testInput.c_str(), testInput.size(), table);
Lexeme lexeme = lexer.next();
REQUIRE_EQ(lexeme.type, Lexeme::RawString);
CHECK_EQ(lexeme.getBlockDepth(), 0);
}
TEST_CASE("lexer_determines_string_block_depth_0_multiline_2")
{
const std::string testInput = R"([[
test
]])";
Luau::Allocator alloc;
AstNameTable table(alloc);
Lexer lexer(testInput.c_str(), testInput.size(), table);
Lexeme lexeme = lexer.next();
REQUIRE_EQ(lexeme.type, Lexeme::RawString);
CHECK_EQ(lexeme.getBlockDepth(), 0);
}
TEST_CASE("lexer_determines_string_block_depth_0_multiline_3")
{
const std::string testInput = R"([[
test ]])";
Luau::Allocator alloc;
AstNameTable table(alloc);
Lexer lexer(testInput.c_str(), testInput.size(), table);
Lexeme lexeme = lexer.next();
REQUIRE_EQ(lexeme.type, Lexeme::RawString);
CHECK_EQ(lexeme.getBlockDepth(), 0);
}
TEST_CASE("lexer_determines_string_block_depth_1")
{
const std::string testInput = "[=[[%s]]=]";
Luau::Allocator alloc;
AstNameTable table(alloc);
Lexer lexer(testInput.c_str(), testInput.size(), table);
Lexeme lexeme = lexer.next();
REQUIRE_EQ(lexeme.type, Lexeme::RawString);
CHECK_EQ(lexeme.getBlockDepth(), 1);
}
TEST_CASE("lexer_determines_string_block_depth_2")
{
const std::string testInput = "[==[ test ]==]";
Luau::Allocator alloc;
AstNameTable table(alloc);
Lexer lexer(testInput.c_str(), testInput.size(), table);
Lexeme lexeme = lexer.next();
REQUIRE_EQ(lexeme.type, Lexeme::RawString);
CHECK_EQ(lexeme.getBlockDepth(), 2);
}
TEST_CASE("lexer_determines_string_block_depth_2_multiline_1")
{
const std::string testInput = R"([==[ test
]==])";
Luau::Allocator alloc;
AstNameTable table(alloc);
Lexer lexer(testInput.c_str(), testInput.size(), table);
Lexeme lexeme = lexer.next();
REQUIRE_EQ(lexeme.type, Lexeme::RawString);
CHECK_EQ(lexeme.getBlockDepth(), 2);
}
TEST_CASE("lexer_determines_string_block_depth_2_multiline_2")
{
const std::string testInput = R"([==[
test
]==])";
Luau::Allocator alloc;
AstNameTable table(alloc);
Lexer lexer(testInput.c_str(), testInput.size(), table);
Lexeme lexeme = lexer.next();
REQUIRE_EQ(lexeme.type, Lexeme::RawString);
CHECK_EQ(lexeme.getBlockDepth(), 2);
}
TEST_CASE("lexer_determines_string_block_depth_2_multiline_3")
{
const std::string testInput = R"([==[
test ]==])";
Luau::Allocator alloc;
AstNameTable table(alloc);
Lexer lexer(testInput.c_str(), testInput.size(), table);
Lexeme lexeme = lexer.next();
REQUIRE_EQ(lexeme.type, Lexeme::RawString);
CHECK_EQ(lexeme.getBlockDepth(), 2);
}
TEST_CASE("lexer_determines_comment_block_depth_0")
{
const std::string testInput = "--[[ test ]]";
Luau::Allocator alloc;
AstNameTable table(alloc);
Lexer lexer(testInput.c_str(), testInput.size(), table);
Lexeme lexeme = lexer.next();
REQUIRE_EQ(lexeme.type, Lexeme::BlockComment);
CHECK_EQ(lexeme.getBlockDepth(), 0);
}
TEST_CASE("lexer_determines_string_block_depth_1")
{
const std::string testInput = "--[=[ μέλλον ]=]";
Luau::Allocator alloc;
AstNameTable table(alloc);
Lexer lexer(testInput.c_str(), testInput.size(), table);
Lexeme lexeme = lexer.next();
REQUIRE_EQ(lexeme.type, Lexeme::BlockComment);
CHECK_EQ(lexeme.getBlockDepth(), 1);
}
TEST_CASE("lexer_determines_string_block_depth_2")
{
const std::string testInput = "--[==[ test ]==]";
Luau::Allocator alloc;
AstNameTable table(alloc);
Lexer lexer(testInput.c_str(), testInput.size(), table);
Lexeme lexeme = lexer.next();
REQUIRE_EQ(lexeme.type, Lexeme::BlockComment);
CHECK_EQ(lexeme.getBlockDepth(), 2);
}
2022-07-28 21:24:07 -07:00
TEST_SUITE_END();