2022-06-03 23:15:45 +01:00
|
|
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
|
|
|
|
|
|
|
#include "Luau/ConstraintGraphBuilder.h"
|
|
|
|
|
2022-06-24 02:56:00 +01:00
|
|
|
#include "Luau/Scope.h"
|
|
|
|
|
2022-06-03 23:15:45 +01:00
|
|
|
namespace Luau
|
|
|
|
{
|
|
|
|
|
2022-06-17 02:05:14 +01:00
|
|
|
const AstStat* getFallthrough(const AstStat* node); // TypeInfer.cpp
|
2022-06-03 23:15:45 +01:00
|
|
|
|
|
|
|
ConstraintGraphBuilder::ConstraintGraphBuilder(TypeArena* arena)
|
|
|
|
: singletonTypes(getSingletonTypes())
|
|
|
|
, arena(arena)
|
|
|
|
, rootScope(nullptr)
|
|
|
|
{
|
|
|
|
LUAU_ASSERT(arena);
|
|
|
|
}
|
|
|
|
|
|
|
|
TypeId ConstraintGraphBuilder::freshType(Scope2* scope)
|
|
|
|
{
|
|
|
|
LUAU_ASSERT(scope);
|
|
|
|
return arena->addType(FreeTypeVar{scope});
|
|
|
|
}
|
|
|
|
|
|
|
|
TypePackId ConstraintGraphBuilder::freshTypePack(Scope2* scope)
|
|
|
|
{
|
|
|
|
LUAU_ASSERT(scope);
|
|
|
|
FreeTypePack f{scope};
|
|
|
|
return arena->addTypePack(TypePackVar{std::move(f)});
|
|
|
|
}
|
|
|
|
|
|
|
|
Scope2* ConstraintGraphBuilder::childScope(Location location, Scope2* parent)
|
|
|
|
{
|
|
|
|
LUAU_ASSERT(parent);
|
|
|
|
auto scope = std::make_unique<Scope2>();
|
|
|
|
Scope2* borrow = scope.get();
|
|
|
|
scopes.emplace_back(location, std::move(scope));
|
|
|
|
|
|
|
|
borrow->parent = parent;
|
|
|
|
borrow->returnType = parent->returnType;
|
|
|
|
parent->children.push_back(borrow);
|
|
|
|
|
|
|
|
return borrow;
|
|
|
|
}
|
|
|
|
|
2022-06-24 02:56:00 +01:00
|
|
|
void ConstraintGraphBuilder::addConstraint(Scope2* scope, ConstraintV cv)
|
2022-06-03 23:15:45 +01:00
|
|
|
{
|
|
|
|
LUAU_ASSERT(scope);
|
2022-06-24 02:56:00 +01:00
|
|
|
scope->constraints.emplace_back(new Constraint{std::move(cv)});
|
2022-06-03 23:15:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void ConstraintGraphBuilder::addConstraint(Scope2* scope, std::unique_ptr<Constraint> c)
|
|
|
|
{
|
|
|
|
LUAU_ASSERT(scope);
|
|
|
|
scope->constraints.emplace_back(std::move(c));
|
|
|
|
}
|
|
|
|
|
|
|
|
void ConstraintGraphBuilder::visit(AstStatBlock* block)
|
|
|
|
{
|
|
|
|
LUAU_ASSERT(scopes.empty());
|
|
|
|
LUAU_ASSERT(rootScope == nullptr);
|
|
|
|
scopes.emplace_back(block->location, std::make_unique<Scope2>());
|
|
|
|
rootScope = scopes.back().second.get();
|
|
|
|
rootScope->returnType = freshTypePack(rootScope);
|
|
|
|
|
2022-06-24 02:56:00 +01:00
|
|
|
// TODO: We should share the global scope.
|
|
|
|
rootScope->typeBindings["nil"] = singletonTypes.nilType;
|
|
|
|
rootScope->typeBindings["number"] = singletonTypes.numberType;
|
|
|
|
rootScope->typeBindings["string"] = singletonTypes.stringType;
|
|
|
|
rootScope->typeBindings["boolean"] = singletonTypes.booleanType;
|
|
|
|
rootScope->typeBindings["thread"] = singletonTypes.threadType;
|
|
|
|
|
2022-06-03 23:15:45 +01:00
|
|
|
visit(rootScope, block);
|
|
|
|
}
|
|
|
|
|
|
|
|
void ConstraintGraphBuilder::visit(Scope2* scope, AstStat* stat)
|
|
|
|
{
|
|
|
|
LUAU_ASSERT(scope);
|
|
|
|
|
|
|
|
if (auto s = stat->as<AstStatBlock>())
|
|
|
|
visit(scope, s);
|
|
|
|
else if (auto s = stat->as<AstStatLocal>())
|
|
|
|
visit(scope, s);
|
2022-06-17 02:05:14 +01:00
|
|
|
else if (auto f = stat->as<AstStatFunction>())
|
|
|
|
visit(scope, f);
|
2022-06-03 23:15:45 +01:00
|
|
|
else if (auto f = stat->as<AstStatLocalFunction>())
|
|
|
|
visit(scope, f);
|
|
|
|
else if (auto r = stat->as<AstStatReturn>())
|
|
|
|
visit(scope, r);
|
2022-06-17 02:05:14 +01:00
|
|
|
else if (auto a = stat->as<AstStatAssign>())
|
|
|
|
visit(scope, a);
|
|
|
|
else if (auto e = stat->as<AstStatExpr>())
|
|
|
|
checkPack(scope, e->expr);
|
|
|
|
else if (auto i = stat->as<AstStatIf>())
|
|
|
|
visit(scope, i);
|
2022-06-24 02:56:00 +01:00
|
|
|
else if (auto a = stat->as<AstStatTypeAlias>())
|
|
|
|
visit(scope, a);
|
2022-06-03 23:15:45 +01:00
|
|
|
else
|
|
|
|
LUAU_ASSERT(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
void ConstraintGraphBuilder::visit(Scope2* scope, AstStatLocal* local)
|
|
|
|
{
|
|
|
|
LUAU_ASSERT(scope);
|
|
|
|
|
|
|
|
std::vector<TypeId> varTypes;
|
|
|
|
|
|
|
|
for (AstLocal* local : local->vars)
|
|
|
|
{
|
|
|
|
TypeId ty = freshType(scope);
|
2022-06-24 02:56:00 +01:00
|
|
|
|
|
|
|
if (local->annotation)
|
|
|
|
{
|
|
|
|
TypeId annotation = resolveType(scope, local->annotation);
|
|
|
|
addConstraint(scope, SubtypeConstraint{ty, annotation});
|
|
|
|
}
|
|
|
|
|
2022-06-03 23:15:45 +01:00
|
|
|
varTypes.push_back(ty);
|
|
|
|
scope->bindings[local] = ty;
|
|
|
|
}
|
|
|
|
|
2022-06-17 02:05:14 +01:00
|
|
|
for (size_t i = 0; i < local->values.size; ++i)
|
2022-06-03 23:15:45 +01:00
|
|
|
{
|
2022-06-17 02:05:14 +01:00
|
|
|
if (local->values.data[i]->is<AstExprConstantNil>())
|
|
|
|
{
|
|
|
|
// HACK: we leave nil-initialized things floating under the assumption that they will later be populated.
|
|
|
|
// See the test TypeInfer/infer_locals_with_nil_value.
|
|
|
|
// Better flow awareness should make this obsolete.
|
|
|
|
}
|
|
|
|
else if (i == local->values.size - 1)
|
|
|
|
{
|
|
|
|
TypePackId exprPack = checkPack(scope, local->values.data[i]);
|
|
|
|
|
|
|
|
if (i < local->vars.size)
|
|
|
|
{
|
|
|
|
std::vector<TypeId> tailValues{varTypes.begin() + i, varTypes.end()};
|
|
|
|
TypePackId tailPack = arena->addTypePack(std::move(tailValues));
|
2022-06-24 02:56:00 +01:00
|
|
|
addConstraint(scope, PackSubtypeConstraint{exprPack, tailPack});
|
2022-06-17 02:05:14 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
2022-06-03 23:15:45 +01:00
|
|
|
{
|
|
|
|
TypeId exprType = check(scope, local->values.data[i]);
|
2022-06-17 02:05:14 +01:00
|
|
|
if (i < varTypes.size())
|
2022-06-24 02:56:00 +01:00
|
|
|
addConstraint(scope, SubtypeConstraint{varTypes[i], exprType});
|
2022-06-03 23:15:45 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void addConstraints(Constraint* constraint, Scope2* scope)
|
|
|
|
{
|
|
|
|
LUAU_ASSERT(scope);
|
|
|
|
|
|
|
|
scope->constraints.reserve(scope->constraints.size() + scope->constraints.size());
|
|
|
|
|
|
|
|
for (const auto& c : scope->constraints)
|
2022-06-17 02:05:14 +01:00
|
|
|
constraint->dependencies.push_back(NotNull{c.get()});
|
2022-06-03 23:15:45 +01:00
|
|
|
|
|
|
|
for (Scope2* childScope : scope->children)
|
|
|
|
addConstraints(constraint, childScope);
|
|
|
|
}
|
|
|
|
|
|
|
|
void ConstraintGraphBuilder::visit(Scope2* scope, AstStatLocalFunction* function)
|
|
|
|
{
|
|
|
|
LUAU_ASSERT(scope);
|
|
|
|
|
|
|
|
// Local
|
|
|
|
// Global
|
|
|
|
// Dotted path
|
|
|
|
// Self?
|
|
|
|
|
|
|
|
TypeId functionType = nullptr;
|
|
|
|
auto ty = scope->lookup(function->name);
|
2022-06-17 02:05:14 +01:00
|
|
|
if (ty.has_value())
|
|
|
|
{
|
|
|
|
// TODO: This is duplicate definition of a local function. Is this allowed?
|
|
|
|
functionType = *ty;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
functionType = arena->addType(BlockedTypeVar{});
|
|
|
|
scope->bindings[function->name] = functionType;
|
|
|
|
}
|
2022-06-03 23:15:45 +01:00
|
|
|
|
2022-06-17 02:05:14 +01:00
|
|
|
auto [actualFunctionType, innerScope] = checkFunctionSignature(scope, function->func);
|
|
|
|
innerScope->bindings[function->name] = actualFunctionType;
|
2022-06-03 23:15:45 +01:00
|
|
|
|
2022-06-17 02:05:14 +01:00
|
|
|
checkFunctionBody(innerScope, function->func);
|
2022-06-03 23:15:45 +01:00
|
|
|
|
2022-06-24 02:56:00 +01:00
|
|
|
std::unique_ptr<Constraint> c{new Constraint{GeneralizationConstraint{functionType, actualFunctionType, innerScope}}};
|
2022-06-17 02:05:14 +01:00
|
|
|
addConstraints(c.get(), innerScope);
|
|
|
|
|
|
|
|
addConstraint(scope, std::move(c));
|
|
|
|
}
|
|
|
|
|
|
|
|
void ConstraintGraphBuilder::visit(Scope2* scope, AstStatFunction* function)
|
|
|
|
{
|
|
|
|
// Name could be AstStatLocal, AstStatGlobal, AstStatIndexName.
|
|
|
|
// With or without self
|
|
|
|
|
|
|
|
TypeId functionType = nullptr;
|
|
|
|
|
|
|
|
auto [actualFunctionType, innerScope] = checkFunctionSignature(scope, function->func);
|
2022-06-03 23:15:45 +01:00
|
|
|
|
2022-06-17 02:05:14 +01:00
|
|
|
if (AstExprLocal* localName = function->name->as<AstExprLocal>())
|
2022-06-03 23:15:45 +01:00
|
|
|
{
|
2022-06-17 02:05:14 +01:00
|
|
|
std::optional<TypeId> existingFunctionTy = scope->lookup(localName->local);
|
|
|
|
if (existingFunctionTy)
|
|
|
|
{
|
|
|
|
// Duplicate definition
|
|
|
|
functionType = *existingFunctionTy;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
functionType = arena->addType(BlockedTypeVar{});
|
|
|
|
scope->bindings[localName->local] = functionType;
|
|
|
|
}
|
|
|
|
innerScope->bindings[localName->local] = actualFunctionType;
|
|
|
|
}
|
|
|
|
else if (AstExprGlobal* globalName = function->name->as<AstExprGlobal>())
|
|
|
|
{
|
|
|
|
std::optional<TypeId> existingFunctionTy = scope->lookup(globalName->name);
|
|
|
|
if (existingFunctionTy)
|
|
|
|
{
|
|
|
|
// Duplicate definition
|
|
|
|
functionType = *existingFunctionTy;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
functionType = arena->addType(BlockedTypeVar{});
|
|
|
|
rootScope->bindings[globalName->name] = functionType;
|
|
|
|
}
|
|
|
|
innerScope->bindings[globalName->name] = actualFunctionType;
|
|
|
|
}
|
|
|
|
else if (AstExprIndexName* indexName = function->name->as<AstExprIndexName>())
|
|
|
|
{
|
|
|
|
LUAU_ASSERT(0); // not yet implemented
|
2022-06-03 23:15:45 +01:00
|
|
|
}
|
|
|
|
|
2022-06-17 02:05:14 +01:00
|
|
|
checkFunctionBody(innerScope, function->func);
|
2022-06-03 23:15:45 +01:00
|
|
|
|
2022-06-24 02:56:00 +01:00
|
|
|
std::unique_ptr<Constraint> c{new Constraint{GeneralizationConstraint{functionType, actualFunctionType, innerScope}}};
|
2022-06-03 23:15:45 +01:00
|
|
|
addConstraints(c.get(), innerScope);
|
|
|
|
|
|
|
|
addConstraint(scope, std::move(c));
|
|
|
|
}
|
|
|
|
|
|
|
|
void ConstraintGraphBuilder::visit(Scope2* scope, AstStatReturn* ret)
|
|
|
|
{
|
|
|
|
LUAU_ASSERT(scope);
|
|
|
|
|
|
|
|
TypePackId exprTypes = checkPack(scope, ret->list);
|
2022-06-24 02:56:00 +01:00
|
|
|
addConstraint(scope, PackSubtypeConstraint{exprTypes, scope->returnType});
|
2022-06-03 23:15:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void ConstraintGraphBuilder::visit(Scope2* scope, AstStatBlock* block)
|
|
|
|
{
|
|
|
|
LUAU_ASSERT(scope);
|
|
|
|
|
2022-06-24 02:56:00 +01:00
|
|
|
// In order to enable mutually-recursive type aliases, we need to
|
|
|
|
// populate the type bindings before we actually check any of the
|
|
|
|
// alias statements. Since we're not ready to actually resolve
|
|
|
|
// any of the annotations, we just use a fresh type for now.
|
|
|
|
for (AstStat* stat : block->body)
|
|
|
|
{
|
|
|
|
if (auto alias = stat->as<AstStatTypeAlias>())
|
|
|
|
{
|
|
|
|
TypeId initialType = freshType(scope);
|
|
|
|
scope->typeBindings[alias->name.value] = initialType;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-03 23:15:45 +01:00
|
|
|
for (AstStat* stat : block->body)
|
|
|
|
visit(scope, stat);
|
|
|
|
}
|
|
|
|
|
2022-06-17 02:05:14 +01:00
|
|
|
void ConstraintGraphBuilder::visit(Scope2* scope, AstStatAssign* assign)
|
|
|
|
{
|
|
|
|
TypePackId varPackId = checkExprList(scope, assign->vars);
|
|
|
|
TypePackId valuePack = checkPack(scope, assign->values);
|
|
|
|
|
2022-06-24 02:56:00 +01:00
|
|
|
addConstraint(scope, PackSubtypeConstraint{valuePack, varPackId});
|
2022-06-17 02:05:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void ConstraintGraphBuilder::visit(Scope2* scope, AstStatIf* ifStatement)
|
|
|
|
{
|
|
|
|
check(scope, ifStatement->condition);
|
|
|
|
|
|
|
|
Scope2* thenScope = childScope(ifStatement->thenbody->location, scope);
|
|
|
|
visit(thenScope, ifStatement->thenbody);
|
|
|
|
|
|
|
|
if (ifStatement->elsebody)
|
|
|
|
{
|
|
|
|
Scope2* elseScope = childScope(ifStatement->elsebody->location, scope);
|
|
|
|
visit(elseScope, ifStatement->elsebody);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-24 02:56:00 +01:00
|
|
|
void ConstraintGraphBuilder::visit(Scope2* scope, AstStatTypeAlias* alias)
|
|
|
|
{
|
|
|
|
// TODO: Exported type aliases
|
|
|
|
// TODO: Generic type aliases
|
|
|
|
|
|
|
|
auto it = scope->typeBindings.find(alias->name.value);
|
|
|
|
// This should always be here since we do a separate pass over the
|
|
|
|
// AST to set up typeBindings. If it's not, we've somehow skipped
|
|
|
|
// this alias in that first pass.
|
|
|
|
LUAU_ASSERT(it != scope->typeBindings.end());
|
|
|
|
|
|
|
|
TypeId ty = resolveType(scope, alias->type);
|
|
|
|
|
|
|
|
// Rather than using a subtype constraint, we instead directly bind
|
|
|
|
// the free type we generated in the first pass to the resolved type.
|
|
|
|
// This prevents a case where you could cause another constraint to
|
|
|
|
// bind the free alias type to an unrelated type, causing havoc.
|
|
|
|
asMutable(it->second)->ty.emplace<BoundTypeVar>(ty);
|
|
|
|
|
|
|
|
addConstraint(scope, NameConstraint{ty, alias->name.value});
|
|
|
|
}
|
|
|
|
|
2022-06-03 23:15:45 +01:00
|
|
|
TypePackId ConstraintGraphBuilder::checkPack(Scope2* scope, AstArray<AstExpr*> exprs)
|
|
|
|
{
|
|
|
|
LUAU_ASSERT(scope);
|
|
|
|
|
|
|
|
if (exprs.size == 0)
|
|
|
|
return arena->addTypePack({});
|
|
|
|
|
|
|
|
std::vector<TypeId> types;
|
|
|
|
TypePackId last = nullptr;
|
|
|
|
|
|
|
|
for (size_t i = 0; i < exprs.size; ++i)
|
|
|
|
{
|
|
|
|
if (i < exprs.size - 1)
|
|
|
|
types.push_back(check(scope, exprs.data[i]));
|
|
|
|
else
|
|
|
|
last = checkPack(scope, exprs.data[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
LUAU_ASSERT(last != nullptr);
|
|
|
|
|
|
|
|
return arena->addTypePack(TypePack{std::move(types), last});
|
|
|
|
}
|
|
|
|
|
2022-06-17 02:05:14 +01:00
|
|
|
TypePackId ConstraintGraphBuilder::checkExprList(Scope2* scope, const AstArray<AstExpr*>& exprs)
|
|
|
|
{
|
|
|
|
TypePackId result = arena->addTypePack({});
|
|
|
|
TypePack* resultPack = getMutable<TypePack>(result);
|
|
|
|
LUAU_ASSERT(resultPack);
|
|
|
|
|
|
|
|
for (size_t i = 0; i < exprs.size; ++i)
|
|
|
|
{
|
|
|
|
AstExpr* expr = exprs.data[i];
|
|
|
|
if (i < exprs.size - 1)
|
|
|
|
resultPack->head.push_back(check(scope, expr));
|
|
|
|
else
|
|
|
|
resultPack->tail = checkPack(scope, expr);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (resultPack->head.empty() && resultPack->tail)
|
|
|
|
return *resultPack->tail;
|
|
|
|
else
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2022-06-03 23:15:45 +01:00
|
|
|
TypePackId ConstraintGraphBuilder::checkPack(Scope2* scope, AstExpr* expr)
|
|
|
|
{
|
|
|
|
LUAU_ASSERT(scope);
|
|
|
|
|
2022-06-17 02:05:14 +01:00
|
|
|
TypePackId result = nullptr;
|
|
|
|
|
|
|
|
if (AstExprCall* call = expr->as<AstExprCall>())
|
|
|
|
{
|
|
|
|
std::vector<TypeId> args;
|
|
|
|
|
|
|
|
for (AstExpr* arg : call->args)
|
|
|
|
{
|
|
|
|
args.push_back(check(scope, arg));
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO self
|
|
|
|
|
|
|
|
TypeId fnType = check(scope, call->func);
|
|
|
|
|
|
|
|
astOriginalCallTypes[call->func] = fnType;
|
|
|
|
|
|
|
|
TypeId instantiatedType = freshType(scope);
|
2022-06-24 02:56:00 +01:00
|
|
|
addConstraint(scope, InstantiationConstraint{instantiatedType, fnType});
|
2022-06-17 02:05:14 +01:00
|
|
|
|
|
|
|
TypePackId rets = freshTypePack(scope);
|
|
|
|
FunctionTypeVar ftv(arena->addTypePack(TypePack{args, {}}), rets);
|
|
|
|
TypeId inferredFnType = arena->addType(ftv);
|
|
|
|
|
2022-06-24 02:56:00 +01:00
|
|
|
addConstraint(scope, SubtypeConstraint{inferredFnType, instantiatedType});
|
2022-06-17 02:05:14 +01:00
|
|
|
result = rets;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
TypeId t = check(scope, expr);
|
|
|
|
result = arena->addTypePack({t});
|
|
|
|
}
|
|
|
|
|
|
|
|
LUAU_ASSERT(result);
|
|
|
|
astTypePacks[expr] = result;
|
|
|
|
return result;
|
2022-06-03 23:15:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
TypeId ConstraintGraphBuilder::check(Scope2* scope, AstExpr* expr)
|
|
|
|
{
|
|
|
|
LUAU_ASSERT(scope);
|
|
|
|
|
2022-06-17 02:05:14 +01:00
|
|
|
TypeId result = nullptr;
|
|
|
|
|
|
|
|
if (auto group = expr->as<AstExprGroup>())
|
|
|
|
result = check(scope, group->expr);
|
|
|
|
else if (expr->is<AstExprConstantString>())
|
|
|
|
result = singletonTypes.stringType;
|
|
|
|
else if (expr->is<AstExprConstantNumber>())
|
|
|
|
result = singletonTypes.numberType;
|
|
|
|
else if (expr->is<AstExprConstantBool>())
|
|
|
|
result = singletonTypes.booleanType;
|
|
|
|
else if (expr->is<AstExprConstantNil>())
|
|
|
|
result = singletonTypes.nilType;
|
2022-06-03 23:15:45 +01:00
|
|
|
else if (auto a = expr->as<AstExprLocal>())
|
|
|
|
{
|
|
|
|
std::optional<TypeId> ty = scope->lookup(a->local);
|
|
|
|
if (ty)
|
2022-06-17 02:05:14 +01:00
|
|
|
result = *ty;
|
|
|
|
else
|
|
|
|
result = singletonTypes.errorRecoveryType(); // FIXME? Record an error at this point?
|
|
|
|
}
|
|
|
|
else if (auto g = expr->as<AstExprGlobal>())
|
|
|
|
{
|
|
|
|
std::optional<TypeId> ty = scope->lookup(g->name);
|
|
|
|
if (ty)
|
|
|
|
result = *ty;
|
2022-06-03 23:15:45 +01:00
|
|
|
else
|
2022-06-17 02:05:14 +01:00
|
|
|
result = singletonTypes.errorRecoveryType(); // FIXME? Record an error at this point?
|
2022-06-03 23:15:45 +01:00
|
|
|
}
|
|
|
|
else if (auto a = expr->as<AstExprCall>())
|
|
|
|
{
|
2022-06-17 02:05:14 +01:00
|
|
|
TypePackId packResult = checkPack(scope, expr);
|
|
|
|
if (auto f = first(packResult))
|
|
|
|
return *f;
|
|
|
|
else if (get<FreeTypePack>(packResult))
|
|
|
|
{
|
|
|
|
TypeId typeResult = freshType(scope);
|
|
|
|
TypePack onePack{{typeResult}, freshTypePack(scope)};
|
|
|
|
TypePackId oneTypePack = arena->addTypePack(std::move(onePack));
|
|
|
|
|
2022-06-24 02:56:00 +01:00
|
|
|
addConstraint(scope, PackSubtypeConstraint{packResult, oneTypePack});
|
2022-06-17 02:05:14 +01:00
|
|
|
|
|
|
|
return typeResult;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (auto a = expr->as<AstExprFunction>())
|
|
|
|
{
|
|
|
|
auto [fnType, functionScope] = checkFunctionSignature(scope, a);
|
|
|
|
checkFunctionBody(functionScope, a);
|
|
|
|
return fnType;
|
|
|
|
}
|
|
|
|
else if (auto indexName = expr->as<AstExprIndexName>())
|
|
|
|
{
|
|
|
|
result = check(scope, indexName);
|
|
|
|
}
|
|
|
|
else if (auto table = expr->as<AstExprTable>())
|
|
|
|
{
|
|
|
|
result = checkExprTable(scope, table);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
LUAU_ASSERT(0);
|
|
|
|
result = freshType(scope);
|
|
|
|
}
|
|
|
|
|
|
|
|
LUAU_ASSERT(result);
|
|
|
|
astTypes[expr] = result;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
TypeId ConstraintGraphBuilder::check(Scope2* scope, AstExprIndexName* indexName)
|
|
|
|
{
|
|
|
|
TypeId obj = check(scope, indexName->expr);
|
|
|
|
TypeId result = freshType(scope);
|
|
|
|
|
|
|
|
TableTypeVar::Props props{{indexName->index.value, Property{result}}};
|
|
|
|
const std::optional<TableIndexer> indexer;
|
|
|
|
TableTypeVar ttv{std::move(props), indexer, TypeLevel{}, TableState::Free};
|
|
|
|
|
|
|
|
TypeId expectedTableType = arena->addType(std::move(ttv));
|
2022-06-03 23:15:45 +01:00
|
|
|
|
2022-06-24 02:56:00 +01:00
|
|
|
addConstraint(scope, SubtypeConstraint{obj, expectedTableType});
|
2022-06-17 02:05:14 +01:00
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
TypeId ConstraintGraphBuilder::checkExprTable(Scope2* scope, AstExprTable* expr)
|
|
|
|
{
|
|
|
|
TypeId ty = arena->addType(TableTypeVar{});
|
|
|
|
TableTypeVar* ttv = getMutable<TableTypeVar>(ty);
|
|
|
|
LUAU_ASSERT(ttv);
|
|
|
|
|
2022-06-24 02:56:00 +01:00
|
|
|
auto createIndexer = [this, scope, ttv](TypeId currentIndexType, TypeId currentResultType) {
|
2022-06-17 02:05:14 +01:00
|
|
|
if (!ttv->indexer)
|
2022-06-03 23:15:45 +01:00
|
|
|
{
|
2022-06-17 02:05:14 +01:00
|
|
|
TypeId indexType = this->freshType(scope);
|
|
|
|
TypeId resultType = this->freshType(scope);
|
|
|
|
ttv->indexer = TableIndexer{indexType, resultType};
|
2022-06-03 23:15:45 +01:00
|
|
|
}
|
|
|
|
|
2022-06-24 02:56:00 +01:00
|
|
|
addConstraint(scope, SubtypeConstraint{ttv->indexer->indexType, currentIndexType});
|
|
|
|
addConstraint(scope, SubtypeConstraint{ttv->indexer->indexResultType, currentResultType});
|
2022-06-17 02:05:14 +01:00
|
|
|
};
|
2022-06-03 23:15:45 +01:00
|
|
|
|
2022-06-17 02:05:14 +01:00
|
|
|
for (const AstExprTable::Item& item : expr->items)
|
|
|
|
{
|
|
|
|
TypeId itemTy = check(scope, item.value);
|
2022-06-03 23:15:45 +01:00
|
|
|
|
2022-06-17 02:05:14 +01:00
|
|
|
if (item.key)
|
|
|
|
{
|
|
|
|
// Even though we don't need to use the type of the item's key if
|
|
|
|
// it's a string constant, we still want to check it to populate
|
|
|
|
// astTypes.
|
|
|
|
TypeId keyTy = check(scope, item.key);
|
|
|
|
|
|
|
|
if (AstExprConstantString* key = item.key->as<AstExprConstantString>())
|
|
|
|
{
|
|
|
|
ttv->props[key->value.begin()] = {itemTy};
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-06-24 02:56:00 +01:00
|
|
|
createIndexer(keyTy, itemTy);
|
2022-06-17 02:05:14 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
TypeId numberType = singletonTypes.numberType;
|
2022-06-24 02:56:00 +01:00
|
|
|
createIndexer(numberType, itemTy);
|
2022-06-17 02:05:14 +01:00
|
|
|
}
|
2022-06-03 23:15:45 +01:00
|
|
|
}
|
2022-06-17 02:05:14 +01:00
|
|
|
|
|
|
|
return ty;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::pair<TypeId, Scope2*> ConstraintGraphBuilder::checkFunctionSignature(Scope2* parent, AstExprFunction* fn)
|
|
|
|
{
|
|
|
|
Scope2* innerScope = childScope(fn->body->location, parent);
|
|
|
|
TypePackId returnType = freshTypePack(innerScope);
|
|
|
|
innerScope->returnType = returnType;
|
|
|
|
|
2022-06-24 02:56:00 +01:00
|
|
|
if (fn->returnAnnotation)
|
|
|
|
{
|
|
|
|
TypePackId annotatedRetType = resolveTypePack(innerScope, *fn->returnAnnotation);
|
|
|
|
addConstraint(innerScope, PackSubtypeConstraint{returnType, annotatedRetType});
|
|
|
|
}
|
|
|
|
|
2022-06-17 02:05:14 +01:00
|
|
|
std::vector<TypeId> argTypes;
|
|
|
|
|
|
|
|
for (AstLocal* local : fn->args)
|
2022-06-03 23:15:45 +01:00
|
|
|
{
|
2022-06-17 02:05:14 +01:00
|
|
|
TypeId t = freshType(innerScope);
|
|
|
|
argTypes.push_back(t);
|
2022-06-24 02:56:00 +01:00
|
|
|
innerScope->bindings[local] = t;
|
|
|
|
|
|
|
|
if (local->annotation)
|
|
|
|
{
|
|
|
|
TypeId argAnnotation = resolveType(innerScope, local->annotation);
|
|
|
|
addConstraint(innerScope, SubtypeConstraint{t, argAnnotation});
|
|
|
|
}
|
2022-06-17 02:05:14 +01:00
|
|
|
}
|
|
|
|
|
2022-06-24 02:56:00 +01:00
|
|
|
// TODO: Vararg annotation.
|
|
|
|
|
2022-06-17 02:05:14 +01:00
|
|
|
FunctionTypeVar actualFunction{arena->addTypePack(argTypes), returnType};
|
|
|
|
TypeId actualFunctionType = arena->addType(std::move(actualFunction));
|
|
|
|
LUAU_ASSERT(actualFunctionType);
|
|
|
|
astTypes[fn] = actualFunctionType;
|
|
|
|
|
|
|
|
return {actualFunctionType, innerScope};
|
|
|
|
}
|
|
|
|
|
|
|
|
void ConstraintGraphBuilder::checkFunctionBody(Scope2* scope, AstExprFunction* fn)
|
|
|
|
{
|
|
|
|
for (AstStat* stat : fn->body->body)
|
|
|
|
visit(scope, stat);
|
|
|
|
|
|
|
|
// If it is possible for execution to reach the end of the function, the return type must be compatible with ()
|
|
|
|
|
|
|
|
if (nullptr != getFallthrough(fn->body))
|
|
|
|
{
|
|
|
|
TypePackId empty = arena->addTypePack({}); // TODO we could have CSG retain one of these forever
|
2022-06-24 02:56:00 +01:00
|
|
|
addConstraint(scope, PackSubtypeConstraint{scope->returnType, empty});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
TypeId ConstraintGraphBuilder::resolveType(Scope2* scope, AstType* ty)
|
|
|
|
{
|
|
|
|
TypeId result = nullptr;
|
|
|
|
|
|
|
|
if (auto ref = ty->as<AstTypeReference>())
|
|
|
|
{
|
|
|
|
// TODO: Support imported types w/ require tracing.
|
|
|
|
// TODO: Support generic type references.
|
|
|
|
LUAU_ASSERT(!ref->prefix);
|
|
|
|
LUAU_ASSERT(!ref->hasParameterList);
|
|
|
|
|
|
|
|
// TODO: If it doesn't exist, should we introduce a free binding?
|
|
|
|
// This is probably important for handling type aliases.
|
|
|
|
result = scope->lookupTypeBinding(ref->name.value).value_or(singletonTypes.errorRecoveryType());
|
2022-06-03 23:15:45 +01:00
|
|
|
}
|
2022-06-24 02:56:00 +01:00
|
|
|
else if (auto tab = ty->as<AstTypeTable>())
|
|
|
|
{
|
|
|
|
TableTypeVar::Props props;
|
|
|
|
std::optional<TableIndexer> indexer;
|
|
|
|
|
|
|
|
for (const AstTableProp& prop : tab->props)
|
|
|
|
{
|
|
|
|
std::string name = prop.name.value;
|
|
|
|
// TODO: Recursion limit.
|
|
|
|
TypeId propTy = resolveType(scope, prop.type);
|
|
|
|
// TODO: Fill in location.
|
|
|
|
props[name] = {propTy};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (tab->indexer)
|
|
|
|
{
|
|
|
|
// TODO: Recursion limit.
|
|
|
|
indexer = TableIndexer{
|
|
|
|
resolveType(scope, tab->indexer->indexType),
|
|
|
|
resolveType(scope, tab->indexer->resultType),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Remove TypeLevel{} here, we don't need it.
|
|
|
|
result = arena->addType(TableTypeVar{props, indexer, TypeLevel{}, TableState::Sealed});
|
|
|
|
}
|
|
|
|
else if (auto fn = ty->as<AstTypeFunction>())
|
|
|
|
{
|
|
|
|
// TODO: Generic functions.
|
|
|
|
// TODO: Scope (though it may not be needed).
|
|
|
|
// TODO: Recursion limit.
|
|
|
|
TypePackId argTypes = resolveTypePack(scope, fn->argTypes);
|
|
|
|
TypePackId returnTypes = resolveTypePack(scope, fn->returnTypes);
|
|
|
|
|
|
|
|
// TODO: Is this the right constructor to use?
|
|
|
|
result = arena->addType(FunctionTypeVar{argTypes, returnTypes});
|
|
|
|
|
|
|
|
FunctionTypeVar* ftv = getMutable<FunctionTypeVar>(result);
|
|
|
|
ftv->argNames.reserve(fn->argNames.size);
|
|
|
|
for (const auto& el : fn->argNames)
|
|
|
|
{
|
|
|
|
if (el)
|
|
|
|
{
|
|
|
|
const auto& [name, location] = *el;
|
|
|
|
ftv->argNames.push_back(FunctionArgument{name.value, location});
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ftv->argNames.push_back(std::nullopt);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (auto tof = ty->as<AstTypeTypeof>())
|
|
|
|
{
|
|
|
|
// TODO: Recursion limit.
|
|
|
|
TypeId exprType = check(scope, tof->expr);
|
|
|
|
result = exprType;
|
|
|
|
}
|
|
|
|
else if (auto unionAnnotation = ty->as<AstTypeUnion>())
|
|
|
|
{
|
|
|
|
std::vector<TypeId> parts;
|
|
|
|
for (AstType* part : unionAnnotation->types)
|
|
|
|
{
|
|
|
|
// TODO: Recursion limit.
|
|
|
|
parts.push_back(resolveType(scope, part));
|
|
|
|
}
|
|
|
|
|
|
|
|
result = arena->addType(UnionTypeVar{parts});
|
|
|
|
}
|
|
|
|
else if (auto intersectionAnnotation = ty->as<AstTypeIntersection>())
|
|
|
|
{
|
|
|
|
std::vector<TypeId> parts;
|
|
|
|
for (AstType* part : intersectionAnnotation->types)
|
|
|
|
{
|
|
|
|
// TODO: Recursion limit.
|
|
|
|
parts.push_back(resolveType(scope, part));
|
|
|
|
}
|
|
|
|
|
|
|
|
result = arena->addType(IntersectionTypeVar{parts});
|
|
|
|
}
|
|
|
|
else if (auto boolAnnotation = ty->as<AstTypeSingletonBool>())
|
|
|
|
{
|
|
|
|
result = arena->addType(SingletonTypeVar(BooleanSingleton{boolAnnotation->value}));
|
|
|
|
}
|
|
|
|
else if (auto stringAnnotation = ty->as<AstTypeSingletonString>())
|
|
|
|
{
|
|
|
|
result = arena->addType(SingletonTypeVar(StringSingleton{std::string(stringAnnotation->value.data, stringAnnotation->value.size)}));
|
|
|
|
}
|
|
|
|
else if (ty->is<AstTypeError>())
|
|
|
|
{
|
|
|
|
result = singletonTypes.errorRecoveryType();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
LUAU_ASSERT(0);
|
|
|
|
result = singletonTypes.errorRecoveryType();
|
|
|
|
}
|
|
|
|
|
|
|
|
astResolvedTypes[ty] = result;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
TypePackId ConstraintGraphBuilder::resolveTypePack(Scope2* scope, AstTypePack* tp)
|
|
|
|
{
|
|
|
|
TypePackId result;
|
|
|
|
if (auto expl = tp->as<AstTypePackExplicit>())
|
|
|
|
{
|
|
|
|
result = resolveTypePack(scope, expl->typeList);
|
|
|
|
}
|
|
|
|
else if (auto var = tp->as<AstTypePackVariadic>())
|
|
|
|
{
|
|
|
|
TypeId ty = resolveType(scope, var->variadicType);
|
|
|
|
result = arena->addTypePack(TypePackVar{VariadicTypePack{ty}});
|
|
|
|
}
|
|
|
|
else if (auto gen = tp->as<AstTypePackGeneric>())
|
|
|
|
{
|
|
|
|
result = arena->addTypePack(TypePackVar{GenericTypePack{scope, gen->genericName.value}});
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
LUAU_ASSERT(0);
|
|
|
|
result = singletonTypes.errorRecoveryTypePack();
|
|
|
|
}
|
|
|
|
|
|
|
|
astResolvedTypePacks[tp] = result;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
TypePackId ConstraintGraphBuilder::resolveTypePack(Scope2* scope, const AstTypeList& list)
|
|
|
|
{
|
|
|
|
std::vector<TypeId> head;
|
|
|
|
|
|
|
|
for (AstType* headTy : list.types)
|
|
|
|
{
|
|
|
|
head.push_back(resolveType(scope, headTy));
|
|
|
|
}
|
|
|
|
|
|
|
|
std::optional<TypePackId> tail = std::nullopt;
|
|
|
|
if (list.tailType)
|
|
|
|
{
|
|
|
|
tail = resolveTypePack(scope, list.tailType);
|
|
|
|
}
|
|
|
|
|
|
|
|
return arena->addTypePack(TypePack{head, tail});
|
2022-06-03 23:15:45 +01:00
|
|
|
}
|
|
|
|
|
2022-06-17 02:05:14 +01:00
|
|
|
void collectConstraints(std::vector<NotNull<Constraint>>& result, Scope2* scope)
|
2022-06-03 23:15:45 +01:00
|
|
|
{
|
|
|
|
for (const auto& c : scope->constraints)
|
2022-06-17 02:05:14 +01:00
|
|
|
result.push_back(NotNull{c.get()});
|
2022-06-03 23:15:45 +01:00
|
|
|
|
|
|
|
for (Scope2* child : scope->children)
|
|
|
|
collectConstraints(result, child);
|
|
|
|
}
|
|
|
|
|
2022-06-17 02:05:14 +01:00
|
|
|
std::vector<NotNull<Constraint>> collectConstraints(Scope2* rootScope)
|
2022-06-03 23:15:45 +01:00
|
|
|
{
|
2022-06-17 02:05:14 +01:00
|
|
|
std::vector<NotNull<Constraint>> result;
|
2022-06-03 23:15:45 +01:00
|
|
|
collectConstraints(result, rootScope);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace Luau
|