Add support for ClassType indexer in definition files (#949)

This commit is contained in:
JohnnyMorganz 2023-06-12 21:02:54 +01:00 committed by GitHub
parent 7fa69377be
commit bc0722471f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 148 additions and 19 deletions

View File

@ -752,6 +752,7 @@ struct AstJsonEncoder : public AstVisitor
if (node->superName) if (node->superName)
write("superName", *node->superName); write("superName", *node->superName);
PROP(props); PROP(props);
PROP(indexer);
}); });
} }

View File

@ -22,6 +22,7 @@
LUAU_FASTINT(LuauCheckRecursionLimit); LUAU_FASTINT(LuauCheckRecursionLimit);
LUAU_FASTFLAG(DebugLuauMagicTypes); LUAU_FASTFLAG(DebugLuauMagicTypes);
LUAU_FASTFLAG(LuauParseDeclareClassIndexer);
namespace Luau namespace Luau
{ {
@ -1157,6 +1158,23 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatDeclareC
scope->exportedTypeBindings[className] = TypeFun{{}, classTy}; scope->exportedTypeBindings[className] = TypeFun{{}, classTy};
if (FFlag::LuauParseDeclareClassIndexer && declaredClass->indexer)
{
RecursionCounter counter{&recursionCount};
if (recursionCount >= FInt::LuauCheckRecursionLimit)
{
reportCodeTooComplex(declaredClass->indexer->location);
}
else
{
ctv->indexer = TableIndexer{
resolveType(scope, declaredClass->indexer->indexType, /* inTypeArguments */ false),
resolveType(scope, declaredClass->indexer->resultType, /* inTypeArguments */ false),
};
}
}
for (const AstDeclaredClassProp& prop : declaredClass->props) for (const AstDeclaredClassProp& prop : declaredClass->props)
{ {
Name propName(prop.name.value); Name propName(prop.name.value);

View File

@ -13,6 +13,8 @@
#include <string> #include <string>
LUAU_FASTFLAG(LuauParseDeclareClassIndexer);
static char* allocateString(Luau::Allocator& allocator, std::string_view contents) static char* allocateString(Luau::Allocator& allocator, std::string_view contents)
{ {
char* result = (char*)allocator.allocate(contents.size() + 1); char* result = (char*)allocator.allocate(contents.size() + 1);
@ -227,7 +229,17 @@ public:
idx++; idx++;
} }
return allocator->alloc<AstTypeTable>(Location(), props); AstTableIndexer* indexer = nullptr;
if (FFlag::LuauParseDeclareClassIndexer && ctv.indexer)
{
RecursionCounter counter(&count);
indexer = allocator->alloc<AstTableIndexer>();
indexer->indexType = Luau::visit(*this, ctv.indexer->indexType->ty);
indexer->resultType = Luau::visit(*this, ctv.indexer->indexResultType->ty);
}
return allocator->alloc<AstTypeTable>(Location(), props, indexer);
} }
AstType* operator()(const FunctionType& ftv) AstType* operator()(const FunctionType& ftv)

View File

@ -41,6 +41,7 @@ LUAU_FASTFLAGVARIABLE(LuauTypecheckTypeguards, false)
LUAU_FASTFLAGVARIABLE(LuauTinyControlFlowAnalysis, false) LUAU_FASTFLAGVARIABLE(LuauTinyControlFlowAnalysis, false)
LUAU_FASTFLAGVARIABLE(LuauTypecheckClassTypeIndexers, false) LUAU_FASTFLAGVARIABLE(LuauTypecheckClassTypeIndexers, false)
LUAU_FASTFLAGVARIABLE(LuauAlwaysCommitInferencesOfFunctionCalls, false) LUAU_FASTFLAGVARIABLE(LuauAlwaysCommitInferencesOfFunctionCalls, false)
LUAU_FASTFLAG(LuauParseDeclareClassIndexer)
namespace Luau namespace Luau
{ {
@ -1757,6 +1758,9 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatDeclareClass&
if (!ctv->metatable) if (!ctv->metatable)
ice("No metatable for declared class"); ice("No metatable for declared class");
if (const auto& indexer = declaredClass.indexer; FFlag::LuauParseDeclareClassIndexer && indexer)
ctv->indexer = TableIndexer(resolveType(scope, *indexer->indexType), resolveType(scope, *indexer->resultType));
TableType* metatable = getMutable<TableType>(*ctv->metatable); TableType* metatable = getMutable<TableType>(*ctv->metatable);
for (const AstDeclaredClassProp& prop : declaredClass.props) for (const AstDeclaredClassProp& prop : declaredClass.props)
{ {

View File

@ -801,12 +801,20 @@ struct AstDeclaredClassProp
bool isMethod = false; bool isMethod = false;
}; };
struct AstTableIndexer
{
AstType* indexType;
AstType* resultType;
Location location;
};
class AstStatDeclareClass : public AstStat class AstStatDeclareClass : public AstStat
{ {
public: public:
LUAU_RTTI(AstStatDeclareClass) LUAU_RTTI(AstStatDeclareClass)
AstStatDeclareClass(const Location& location, const AstName& name, std::optional<AstName> superName, const AstArray<AstDeclaredClassProp>& props); AstStatDeclareClass(const Location& location, const AstName& name, std::optional<AstName> superName, const AstArray<AstDeclaredClassProp>& props,
AstTableIndexer* indexer = nullptr);
void visit(AstVisitor* visitor) override; void visit(AstVisitor* visitor) override;
@ -814,6 +822,7 @@ public:
std::optional<AstName> superName; std::optional<AstName> superName;
AstArray<AstDeclaredClassProp> props; AstArray<AstDeclaredClassProp> props;
AstTableIndexer* indexer;
}; };
class AstType : public AstNode class AstType : public AstNode
@ -862,13 +871,6 @@ struct AstTableProp
AstType* type; AstType* type;
}; };
struct AstTableIndexer
{
AstType* indexType;
AstType* resultType;
Location location;
};
class AstTypeTable : public AstType class AstTypeTable : public AstType
{ {
public: public:

View File

@ -714,12 +714,13 @@ void AstStatDeclareFunction::visit(AstVisitor* visitor)
} }
} }
AstStatDeclareClass::AstStatDeclareClass( AstStatDeclareClass::AstStatDeclareClass(const Location& location, const AstName& name, std::optional<AstName> superName,
const Location& location, const AstName& name, std::optional<AstName> superName, const AstArray<AstDeclaredClassProp>& props) const AstArray<AstDeclaredClassProp>& props, AstTableIndexer* indexer)
: AstStat(ClassIndex(), location) : AstStat(ClassIndex(), location)
, name(name) , name(name)
, superName(superName) , superName(superName)
, props(props) , props(props)
, indexer(indexer)
{ {
} }

View File

@ -13,6 +13,7 @@
// See docs/SyntaxChanges.md for an explanation. // See docs/SyntaxChanges.md for an explanation.
LUAU_FASTINTVARIABLE(LuauRecursionLimit, 1000) LUAU_FASTINTVARIABLE(LuauRecursionLimit, 1000)
LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100) LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
LUAU_FASTFLAGVARIABLE(LuauParseDeclareClassIndexer, false)
#define ERROR_INVALID_INTERP_DOUBLE_BRACE "Double braces are not permitted within interpolated strings. Did you mean '\\{'?" #define ERROR_INVALID_INTERP_DOUBLE_BRACE "Double braces are not permitted within interpolated strings. Did you mean '\\{'?"
@ -877,6 +878,7 @@ AstStat* Parser::parseDeclaration(const Location& start)
} }
TempVector<AstDeclaredClassProp> props(scratchDeclaredClassProps); TempVector<AstDeclaredClassProp> props(scratchDeclaredClassProps);
AstTableIndexer* indexer = nullptr;
while (lexer.current().type != Lexeme::ReservedEnd) while (lexer.current().type != Lexeme::ReservedEnd)
{ {
@ -885,7 +887,8 @@ AstStat* Parser::parseDeclaration(const Location& start)
{ {
props.push_back(parseDeclaredClassMethod()); props.push_back(parseDeclaredClassMethod());
} }
else if (lexer.current().type == '[') else if (lexer.current().type == '[' && (!FFlag::LuauParseDeclareClassIndexer || lexer.lookahead().type == Lexeme::RawString ||
lexer.lookahead().type == Lexeme::QuotedString))
{ {
const Lexeme begin = lexer.current(); const Lexeme begin = lexer.current();
nextLexeme(); // [ nextLexeme(); // [
@ -904,6 +907,22 @@ AstStat* Parser::parseDeclaration(const Location& start)
else else
report(begin.location, "String literal contains malformed escape sequence"); report(begin.location, "String literal contains malformed escape sequence");
} }
else if (lexer.current().type == '[' && FFlag::LuauParseDeclareClassIndexer)
{
if (indexer)
{
// maybe we don't need to parse the entire badIndexer...
// however, we either have { or [ to lint, not the entire table type or the bad indexer.
AstTableIndexer* badIndexer = parseTableIndexer();
// we lose all additional indexer expressions from the AST after error recovery here
report(badIndexer->location, "Cannot have more than one class indexer");
}
else
{
indexer = parseTableIndexer();
}
}
else else
{ {
Name propName = parseName("property name"); Name propName = parseName("property name");
@ -916,7 +935,7 @@ AstStat* Parser::parseDeclaration(const Location& start)
Location classEnd = lexer.current().location; Location classEnd = lexer.current().location;
nextLexeme(); // skip past `end` nextLexeme(); // skip past `end`
return allocator.alloc<AstStatDeclareClass>(Location(classStart, classEnd), className.name, superName, copy(props)); return allocator.alloc<AstStatDeclareClass>(Location(classStart, classEnd), className.name, superName, copy(props), indexer);
} }
else if (std::optional<Name> globalName = parseNameOpt("global variable name")) else if (std::optional<Name> globalName = parseNameOpt("global variable name"))
{ {

View File

@ -432,11 +432,11 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatDeclareClass")
REQUIRE(2 == root->body.size); REQUIRE(2 == root->body.size);
std::string_view expected1 = std::string_view expected1 =
R"({"type":"AstStatDeclareClass","location":"1,22 - 4,11","name":"Foo","props":[{"name":"prop","type":"AstDeclaredClassProp","luauType":{"type":"AstTypeReference","location":"2,18 - 2,24","name":"number","nameLocation":"2,18 - 2,24","parameters":[]}},{"name":"method","type":"AstDeclaredClassProp","luauType":{"type":"AstTypeFunction","location":"3,21 - 4,11","generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"3,39 - 3,45","name":"number","nameLocation":"3,39 - 3,45","parameters":[]}]},"returnTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"3,48 - 3,54","name":"string","nameLocation":"3,48 - 3,54","parameters":[]}]}}}]})"; R"({"type":"AstStatDeclareClass","location":"1,22 - 4,11","name":"Foo","props":[{"name":"prop","type":"AstDeclaredClassProp","luauType":{"type":"AstTypeReference","location":"2,18 - 2,24","name":"number","nameLocation":"2,18 - 2,24","parameters":[]}},{"name":"method","type":"AstDeclaredClassProp","luauType":{"type":"AstTypeFunction","location":"3,21 - 4,11","generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"3,39 - 3,45","name":"number","nameLocation":"3,39 - 3,45","parameters":[]}]},"returnTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"3,48 - 3,54","name":"string","nameLocation":"3,48 - 3,54","parameters":[]}]}}}],"indexer":null})";
CHECK(toJson(root->body.data[0]) == expected1); CHECK(toJson(root->body.data[0]) == expected1);
std::string_view expected2 = std::string_view expected2 =
R"({"type":"AstStatDeclareClass","location":"6,22 - 8,11","name":"Bar","superName":"Foo","props":[{"name":"prop2","type":"AstDeclaredClassProp","luauType":{"type":"AstTypeReference","location":"7,19 - 7,25","name":"string","nameLocation":"7,19 - 7,25","parameters":[]}}]})"; R"({"type":"AstStatDeclareClass","location":"6,22 - 8,11","name":"Bar","superName":"Foo","props":[{"name":"prop2","type":"AstDeclaredClassProp","luauType":{"type":"AstTypeReference","location":"7,19 - 7,25","name":"string","nameLocation":"7,19 - 7,25","parameters":[]}}],"indexer":null})";
CHECK(toJson(root->body.data[1]) == expected2); CHECK(toJson(root->body.data[1]) == expected2);
} }

View File

@ -54,7 +54,8 @@ TEST_SUITE_BEGIN("AllocatorTests");
TEST_CASE("allocator_can_be_moved") TEST_CASE("allocator_can_be_moved")
{ {
Counter* c = nullptr; Counter* c = nullptr;
auto inner = [&]() { auto inner = [&]()
{
Luau::Allocator allocator; Luau::Allocator allocator;
c = allocator.alloc<Counter>(); c = allocator.alloc<Counter>();
Luau::Allocator moved{std::move(allocator)}; Luau::Allocator moved{std::move(allocator)};
@ -921,7 +922,8 @@ TEST_CASE_FIXTURE(Fixture, "parse_interpolated_string_double_brace_mid")
TEST_CASE_FIXTURE(Fixture, "parse_interpolated_string_without_end_brace") TEST_CASE_FIXTURE(Fixture, "parse_interpolated_string_without_end_brace")
{ {
auto columnOfEndBraceError = [this](const char* code) { auto columnOfEndBraceError = [this](const char* code)
{
try try
{ {
parse(code); parse(code);
@ -1882,6 +1884,44 @@ TEST_CASE_FIXTURE(Fixture, "class_method_properties")
CHECK_EQ(2, klass2->props.size); CHECK_EQ(2, klass2->props.size);
} }
TEST_CASE_FIXTURE(Fixture, "class_indexer")
{
ScopedFastFlag LuauParseDeclareClassIndexer("LuauParseDeclareClassIndexer", true);
AstStatBlock* stat = parseEx(R"(
declare class Foo
prop: boolean
[string]: number
end
)")
.root;
REQUIRE_EQ(stat->body.size, 1);
AstStatDeclareClass* declaredClass = stat->body.data[0]->as<AstStatDeclareClass>();
REQUIRE(declaredClass);
REQUIRE(declaredClass->indexer);
REQUIRE(declaredClass->indexer->indexType->is<AstTypeReference>());
CHECK(declaredClass->indexer->indexType->as<AstTypeReference>()->name == "string");
REQUIRE(declaredClass->indexer->resultType->is<AstTypeReference>());
CHECK(declaredClass->indexer->resultType->as<AstTypeReference>()->name == "number");
const ParseResult p1 = matchParseError(R"(
declare class Foo
[string]: number
-- can only have one indexer
[number]: number
end
)",
"Cannot have more than one class indexer");
REQUIRE_EQ(1, p1.root->body.size);
AstStatDeclareClass* klass = p1.root->body.data[0]->as<AstStatDeclareClass>();
REQUIRE(klass != nullptr);
CHECK(klass->indexer);
}
TEST_CASE_FIXTURE(Fixture, "parse_variadics") TEST_CASE_FIXTURE(Fixture, "parse_variadics")
{ {
//clang-format off //clang-format off
@ -2347,7 +2387,8 @@ public:
TEST_CASE_FIXTURE(Fixture, "recovery_of_parenthesized_expressions") TEST_CASE_FIXTURE(Fixture, "recovery_of_parenthesized_expressions")
{ {
auto checkAstEquivalence = [this](const char* codeWithErrors, const char* code) { auto checkAstEquivalence = [this](const char* codeWithErrors, const char* code)
{
try try
{ {
parse(codeWithErrors); parse(codeWithErrors);
@ -2367,7 +2408,8 @@ TEST_CASE_FIXTURE(Fixture, "recovery_of_parenthesized_expressions")
CHECK_EQ(counterWithErrors.count, counter.count); CHECK_EQ(counterWithErrors.count, counter.count);
}; };
auto checkRecovery = [this, checkAstEquivalence](const char* codeWithErrors, const char* code, unsigned expectedErrorCount) { auto checkRecovery = [this, checkAstEquivalence](const char* codeWithErrors, const char* code, unsigned expectedErrorCount)
{
try try
{ {
parse(codeWithErrors); parse(codeWithErrors);

View File

@ -394,6 +394,36 @@ TEST_CASE_FIXTURE(Fixture, "class_definition_string_props")
CHECK_EQ(toString(requireType("y")), "string"); CHECK_EQ(toString(requireType("y")), "string");
} }
TEST_CASE_FIXTURE(Fixture, "class_definition_indexer")
{
ScopedFastFlag LuauParseDeclareClassIndexer("LuauParseDeclareClassIndexer", true);
ScopedFastFlag LuauTypecheckClassTypeIndexers("LuauTypecheckClassTypeIndexers", true);
loadDefinition(R"(
declare class Foo
[number]: string
end
)");
CheckResult result = check(R"(
local x: Foo
local y = x[1]
)");
LUAU_REQUIRE_NO_ERRORS(result);
const ClassType* ctv = get<ClassType>(requireType("x"));
REQUIRE(ctv != nullptr);
REQUIRE(bool(ctv->indexer));
CHECK_EQ(*ctv->indexer->indexType, *builtinTypes->numberType);
CHECK_EQ(*ctv->indexer->indexResultType, *builtinTypes->stringType);
CHECK_EQ(toString(requireType("y")), "string");
}
TEST_CASE_FIXTURE(Fixture, "class_definitions_reference_other_classes") TEST_CASE_FIXTURE(Fixture, "class_definitions_reference_other_classes")
{ {
unfreeze(frontend.globals.globalTypes); unfreeze(frontend.globals.globalTypes);