2022-06-17 01:54:42 +01:00
|
|
|
|
|
|
|
#include "Luau/TypeChecker2.h"
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
|
|
|
|
#include "Luau/Ast.h"
|
|
|
|
#include "Luau/AstQuery.h"
|
|
|
|
#include "Luau/Clone.h"
|
2022-07-01 00:29:02 +01:00
|
|
|
#include "Luau/Instantiation.h"
|
2022-06-17 01:54:42 +01:00
|
|
|
#include "Luau/Normalize.h"
|
2022-07-01 00:29:02 +01:00
|
|
|
#include "Luau/TxnLog.h"
|
|
|
|
#include "Luau/TypeUtils.h"
|
2022-08-11 21:42:54 +01:00
|
|
|
#include "Luau/TypeVar.h"
|
2022-06-24 02:44:07 +01:00
|
|
|
#include "Luau/Unifier.h"
|
|
|
|
#include "Luau/ToString.h"
|
2022-06-17 01:54:42 +01:00
|
|
|
|
|
|
|
namespace Luau
|
|
|
|
{
|
|
|
|
|
|
|
|
struct TypeChecker2 : public AstVisitor
|
|
|
|
{
|
|
|
|
const SourceModule* sourceModule;
|
|
|
|
Module* module;
|
|
|
|
InternalErrorReporter ice; // FIXME accept a pointer from Frontend
|
2022-07-01 00:29:02 +01:00
|
|
|
SingletonTypes& singletonTypes;
|
2022-06-17 01:54:42 +01:00
|
|
|
|
|
|
|
TypeChecker2(const SourceModule* sourceModule, Module* module)
|
|
|
|
: sourceModule(sourceModule)
|
|
|
|
, module(module)
|
2022-07-01 00:29:02 +01:00
|
|
|
, singletonTypes(getSingletonTypes())
|
2022-06-17 01:54:42 +01:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
using AstVisitor::visit;
|
|
|
|
|
|
|
|
TypePackId lookupPack(AstExpr* expr)
|
|
|
|
{
|
2022-07-01 00:29:02 +01:00
|
|
|
// If a type isn't in the type graph, it probably means that a recursion limit was exceeded.
|
|
|
|
// We'll just return anyType in these cases. Typechecking against any is very fast and this
|
|
|
|
// allows us not to think about this very much in the actual typechecking logic.
|
2022-06-17 01:54:42 +01:00
|
|
|
TypePackId* tp = module->astTypePacks.find(expr);
|
2022-07-01 00:29:02 +01:00
|
|
|
if (tp)
|
|
|
|
return follow(*tp);
|
|
|
|
else
|
|
|
|
return singletonTypes.anyTypePack;
|
2022-06-17 01:54:42 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
TypeId lookupType(AstExpr* expr)
|
|
|
|
{
|
2022-07-01 00:29:02 +01:00
|
|
|
// If a type isn't in the type graph, it probably means that a recursion limit was exceeded.
|
|
|
|
// We'll just return anyType in these cases. Typechecking against any is very fast and this
|
|
|
|
// allows us not to think about this very much in the actual typechecking logic.
|
2022-06-17 01:54:42 +01:00
|
|
|
TypeId* ty = module->astTypes.find(expr);
|
2022-07-01 00:29:02 +01:00
|
|
|
if (ty)
|
|
|
|
return follow(*ty);
|
|
|
|
|
|
|
|
TypePackId* tp = module->astTypePacks.find(expr);
|
|
|
|
if (tp)
|
|
|
|
return flattenPack(*tp);
|
|
|
|
|
|
|
|
return singletonTypes.anyType;
|
2022-06-17 01:54:42 +01:00
|
|
|
}
|
|
|
|
|
2022-06-24 02:44:07 +01:00
|
|
|
TypeId lookupAnnotation(AstType* annotation)
|
|
|
|
{
|
|
|
|
TypeId* ty = module->astResolvedTypes.find(annotation);
|
|
|
|
LUAU_ASSERT(ty);
|
|
|
|
return follow(*ty);
|
|
|
|
}
|
|
|
|
|
2022-08-04 22:27:28 +01:00
|
|
|
TypePackId lookupPackAnnotation(AstTypePack* annotation)
|
|
|
|
{
|
|
|
|
TypePackId* tp = module->astResolvedTypePacks.find(annotation);
|
|
|
|
LUAU_ASSERT(tp);
|
|
|
|
return follow(*tp);
|
|
|
|
}
|
|
|
|
|
2022-06-24 02:44:07 +01:00
|
|
|
TypePackId reconstructPack(AstArray<AstExpr*> exprs, TypeArena& arena)
|
|
|
|
{
|
2022-07-29 04:41:13 +01:00
|
|
|
if (exprs.size == 0)
|
|
|
|
return arena.addTypePack(TypePack{{}, std::nullopt});
|
|
|
|
|
2022-06-24 02:44:07 +01:00
|
|
|
std::vector<TypeId> head;
|
|
|
|
|
|
|
|
for (size_t i = 0; i < exprs.size - 1; ++i)
|
|
|
|
{
|
|
|
|
head.push_back(lookupType(exprs.data[i]));
|
|
|
|
}
|
|
|
|
|
|
|
|
TypePackId tail = lookupPack(exprs.data[exprs.size - 1]);
|
|
|
|
return arena.addTypePack(TypePack{head, tail});
|
|
|
|
}
|
|
|
|
|
2022-07-29 04:41:13 +01:00
|
|
|
Scope* findInnermostScope(Location location)
|
2022-06-24 02:44:07 +01:00
|
|
|
{
|
2022-07-29 04:41:13 +01:00
|
|
|
Scope* bestScope = module->getModuleScope().get();
|
|
|
|
Location bestLocation = module->scopes[0].first;
|
2022-06-24 02:44:07 +01:00
|
|
|
|
2022-07-29 04:41:13 +01:00
|
|
|
for (size_t i = 0; i < module->scopes.size(); ++i)
|
2022-06-24 02:44:07 +01:00
|
|
|
{
|
2022-07-29 04:41:13 +01:00
|
|
|
auto& [scopeBounds, scope] = module->scopes[i];
|
2022-06-24 02:44:07 +01:00
|
|
|
if (scopeBounds.encloses(location))
|
|
|
|
{
|
|
|
|
if (scopeBounds.begin > bestLocation.begin || scopeBounds.end < bestLocation.end)
|
|
|
|
{
|
|
|
|
bestScope = scope.get();
|
|
|
|
bestLocation = scopeBounds;
|
|
|
|
}
|
|
|
|
}
|
2022-07-01 00:29:02 +01:00
|
|
|
else if (scopeBounds.begin > location.end)
|
2022-06-24 02:44:07 +01:00
|
|
|
{
|
|
|
|
// TODO: Is this sound? This relies on the fact that scopes are inserted
|
|
|
|
// into the scope list in the order that they appear in the AST.
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return bestScope;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool visit(AstStatLocal* local) override
|
|
|
|
{
|
|
|
|
for (size_t i = 0; i < local->values.size; ++i)
|
|
|
|
{
|
|
|
|
AstExpr* value = local->values.data[i];
|
|
|
|
if (i == local->values.size - 1)
|
|
|
|
{
|
|
|
|
if (i < local->values.size)
|
|
|
|
{
|
|
|
|
TypePackId valueTypes = lookupPack(value);
|
|
|
|
auto it = begin(valueTypes);
|
|
|
|
for (size_t j = i; j < local->vars.size; ++j)
|
|
|
|
{
|
|
|
|
if (it == end(valueTypes))
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
AstLocal* var = local->vars.data[i];
|
|
|
|
if (var->annotation)
|
|
|
|
{
|
|
|
|
TypeId varType = lookupAnnotation(var->annotation);
|
|
|
|
if (!isSubtype(*it, varType, ice))
|
|
|
|
{
|
|
|
|
reportError(TypeMismatch{varType, *it}, value->location);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
++it;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
TypeId valueType = lookupType(value);
|
|
|
|
AstLocal* var = local->vars.data[i];
|
|
|
|
|
|
|
|
if (var->annotation)
|
|
|
|
{
|
|
|
|
TypeId varType = lookupAnnotation(var->annotation);
|
|
|
|
if (!isSubtype(varType, valueType, ice))
|
|
|
|
{
|
|
|
|
reportError(TypeMismatch{varType, valueType}, value->location);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-06-17 01:54:42 +01:00
|
|
|
bool visit(AstStatAssign* assign) override
|
|
|
|
{
|
|
|
|
size_t count = std::min(assign->vars.size, assign->values.size);
|
|
|
|
|
|
|
|
for (size_t i = 0; i < count; ++i)
|
|
|
|
{
|
|
|
|
AstExpr* lhs = assign->vars.data[i];
|
2022-07-01 00:29:02 +01:00
|
|
|
TypeId lhsType = lookupType(lhs);
|
2022-06-17 01:54:42 +01:00
|
|
|
|
|
|
|
AstExpr* rhs = assign->values.data[i];
|
2022-07-01 00:29:02 +01:00
|
|
|
TypeId rhsType = lookupType(rhs);
|
2022-06-17 01:54:42 +01:00
|
|
|
|
2022-07-01 00:29:02 +01:00
|
|
|
if (!isSubtype(rhsType, lhsType, ice))
|
2022-06-17 01:54:42 +01:00
|
|
|
{
|
2022-07-01 00:29:02 +01:00
|
|
|
reportError(TypeMismatch{lhsType, rhsType}, rhs->location);
|
2022-06-17 01:54:42 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-06-24 02:44:07 +01:00
|
|
|
bool visit(AstStatReturn* ret) override
|
|
|
|
{
|
2022-07-29 04:41:13 +01:00
|
|
|
Scope* scope = findInnermostScope(ret->location);
|
2022-06-24 02:44:07 +01:00
|
|
|
TypePackId expectedRetType = scope->returnType;
|
|
|
|
|
|
|
|
TypeArena arena;
|
|
|
|
TypePackId actualRetType = reconstructPack(ret->list, arena);
|
|
|
|
|
|
|
|
UnifierSharedState sharedState{&ice};
|
|
|
|
Unifier u{&arena, Mode::Strict, ret->location, Covariant, sharedState};
|
|
|
|
u.anyIsTop = true;
|
|
|
|
|
|
|
|
u.tryUnify(actualRetType, expectedRetType);
|
|
|
|
const bool ok = u.errors.empty() && u.log.empty();
|
|
|
|
|
|
|
|
if (!ok)
|
|
|
|
{
|
|
|
|
for (const TypeError& e : u.errors)
|
2022-07-01 00:29:02 +01:00
|
|
|
reportError(e);
|
2022-06-24 02:44:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-06-17 01:54:42 +01:00
|
|
|
bool visit(AstExprCall* call) override
|
|
|
|
{
|
2022-07-01 00:29:02 +01:00
|
|
|
TypeArena arena;
|
|
|
|
Instantiation instantiation{TxnLog::empty(), &arena, TypeLevel{}};
|
|
|
|
|
2022-06-17 01:54:42 +01:00
|
|
|
TypePackId expectedRetType = lookupPack(call);
|
|
|
|
TypeId functionType = lookupType(call->func);
|
2022-07-01 00:29:02 +01:00
|
|
|
TypeId instantiatedFunctionType = instantiation.substitute(functionType).value_or(nullptr);
|
|
|
|
LUAU_ASSERT(functionType);
|
2022-06-17 01:54:42 +01:00
|
|
|
|
|
|
|
TypePack args;
|
|
|
|
for (const auto& arg : call->args)
|
|
|
|
{
|
|
|
|
TypeId argTy = module->astTypes[arg];
|
|
|
|
LUAU_ASSERT(argTy);
|
|
|
|
args.head.push_back(argTy);
|
|
|
|
}
|
|
|
|
|
|
|
|
TypePackId argsTp = arena.addTypePack(args);
|
|
|
|
FunctionTypeVar ftv{argsTp, expectedRetType};
|
|
|
|
TypeId expectedType = arena.addType(ftv);
|
2022-07-01 00:29:02 +01:00
|
|
|
if (!isSubtype(expectedType, instantiatedFunctionType, ice))
|
2022-06-17 01:54:42 +01:00
|
|
|
{
|
|
|
|
unfreeze(module->interfaceTypes);
|
|
|
|
CloneState cloneState;
|
|
|
|
expectedType = clone(expectedType, module->interfaceTypes, cloneState);
|
|
|
|
freeze(module->interfaceTypes);
|
|
|
|
reportError(TypeMismatch{expectedType, functionType}, call->location);
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-06-24 02:44:07 +01:00
|
|
|
bool visit(AstExprFunction* fn) override
|
|
|
|
{
|
|
|
|
TypeId inferredFnTy = lookupType(fn);
|
|
|
|
const FunctionTypeVar* inferredFtv = get<FunctionTypeVar>(inferredFnTy);
|
|
|
|
LUAU_ASSERT(inferredFtv);
|
|
|
|
|
|
|
|
auto argIt = begin(inferredFtv->argTypes);
|
|
|
|
for (const auto& arg : fn->args)
|
|
|
|
{
|
|
|
|
if (argIt == end(inferredFtv->argTypes))
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (arg->annotation)
|
|
|
|
{
|
|
|
|
TypeId inferredArgTy = *argIt;
|
|
|
|
TypeId annotatedArgTy = lookupAnnotation(arg->annotation);
|
|
|
|
|
|
|
|
if (!isSubtype(annotatedArgTy, inferredArgTy, ice))
|
|
|
|
{
|
|
|
|
reportError(TypeMismatch{annotatedArgTy, inferredArgTy}, arg->location);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
++argIt;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-06-17 01:54:42 +01:00
|
|
|
bool visit(AstExprIndexName* indexName) override
|
|
|
|
{
|
|
|
|
TypeId leftType = lookupType(indexName->expr);
|
|
|
|
TypeId resultType = lookupType(indexName);
|
|
|
|
|
|
|
|
// leftType must have a property called indexName->index
|
|
|
|
|
2022-08-11 21:42:54 +01:00
|
|
|
std::optional<TypeId> ty = getIndexTypeFromType(module->getModuleScope(), leftType, indexName->index.value, indexName->location, /* addErrors */ true);
|
|
|
|
if (ty)
|
2022-06-17 01:54:42 +01:00
|
|
|
{
|
2022-08-11 21:42:54 +01:00
|
|
|
if (!isSubtype(resultType, *ty, ice))
|
2022-06-17 01:54:42 +01:00
|
|
|
{
|
2022-08-11 21:42:54 +01:00
|
|
|
reportError(TypeMismatch{resultType, *ty}, indexName->location);
|
2022-06-17 01:54:42 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool visit(AstExprConstantNumber* number) override
|
|
|
|
{
|
|
|
|
TypeId actualType = lookupType(number);
|
|
|
|
TypeId numberType = getSingletonTypes().numberType;
|
|
|
|
|
2022-07-01 00:29:02 +01:00
|
|
|
if (!isSubtype(numberType, actualType, ice))
|
2022-06-17 01:54:42 +01:00
|
|
|
{
|
|
|
|
reportError(TypeMismatch{actualType, numberType}, number->location);
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool visit(AstExprConstantString* string) override
|
|
|
|
{
|
|
|
|
TypeId actualType = lookupType(string);
|
|
|
|
TypeId stringType = getSingletonTypes().stringType;
|
|
|
|
|
2022-07-01 00:29:02 +01:00
|
|
|
if (!isSubtype(stringType, actualType, ice))
|
2022-06-17 01:54:42 +01:00
|
|
|
{
|
|
|
|
reportError(TypeMismatch{actualType, stringType}, string->location);
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-08-11 21:42:54 +01:00
|
|
|
bool visit(AstExprTypeAssertion* expr) override
|
|
|
|
{
|
|
|
|
TypeId annotationType = lookupAnnotation(expr->annotation);
|
|
|
|
TypeId computedType = lookupType(expr->expr);
|
|
|
|
|
|
|
|
// Note: As an optimization, we try 'number <: number | string' first, as that is the more likely case.
|
|
|
|
if (isSubtype(annotationType, computedType, ice))
|
|
|
|
return true;
|
|
|
|
|
|
|
|
if (isSubtype(computedType, annotationType, ice))
|
|
|
|
return true;
|
|
|
|
|
|
|
|
reportError(TypesAreUnrelated{computedType, annotationType}, expr->location);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-07-01 00:29:02 +01:00
|
|
|
/** Extract a TypeId for the first type of the provided pack.
|
|
|
|
*
|
|
|
|
* Note that this may require modifying some types. I hope this doesn't cause problems!
|
|
|
|
*/
|
|
|
|
TypeId flattenPack(TypePackId pack)
|
|
|
|
{
|
|
|
|
pack = follow(pack);
|
|
|
|
|
2022-07-14 23:39:35 +01:00
|
|
|
while (true)
|
2022-07-01 00:29:02 +01:00
|
|
|
{
|
2022-07-14 23:39:35 +01:00
|
|
|
auto tp = get<TypePack>(pack);
|
|
|
|
if (tp && tp->head.empty() && tp->tail)
|
2022-07-01 00:29:02 +01:00
|
|
|
pack = *tp->tail;
|
2022-07-14 23:39:35 +01:00
|
|
|
else
|
|
|
|
break;
|
2022-07-01 00:29:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (auto ty = first(pack))
|
|
|
|
return *ty;
|
|
|
|
else if (auto vtp = get<VariadicTypePack>(pack))
|
|
|
|
return vtp->ty;
|
|
|
|
else if (auto ftp = get<FreeTypePack>(pack))
|
|
|
|
{
|
|
|
|
TypeId result = module->internalTypes.addType(FreeTypeVar{ftp->scope});
|
|
|
|
TypePackId freeTail = module->internalTypes.addTypePack(FreeTypePack{ftp->scope});
|
|
|
|
|
|
|
|
TypePack& resultPack = asMutable(pack)->ty.emplace<TypePack>();
|
|
|
|
resultPack.head.assign(1, result);
|
|
|
|
resultPack.tail = freeTail;
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
else if (get<Unifiable::Error>(pack))
|
|
|
|
return singletonTypes.errorRecoveryType();
|
|
|
|
else
|
|
|
|
ice.ice("flattenPack got a weird pack!");
|
|
|
|
}
|
|
|
|
|
2022-06-24 02:44:07 +01:00
|
|
|
bool visit(AstType* ty) override
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool visit(AstTypeReference* ty) override
|
|
|
|
{
|
2022-07-29 04:41:13 +01:00
|
|
|
Scope* scope = findInnermostScope(ty->location);
|
2022-08-04 22:27:28 +01:00
|
|
|
LUAU_ASSERT(scope);
|
2022-06-24 02:44:07 +01:00
|
|
|
|
|
|
|
// TODO: Imported types
|
2022-08-04 22:27:28 +01:00
|
|
|
|
2022-08-11 21:42:54 +01:00
|
|
|
std::optional<TypeFun> alias = scope->lookupType(ty->name.value);
|
2022-08-04 22:27:28 +01:00
|
|
|
|
|
|
|
if (alias.has_value())
|
|
|
|
{
|
|
|
|
size_t typesRequired = alias->typeParams.size();
|
|
|
|
size_t packsRequired = alias->typePackParams.size();
|
|
|
|
|
|
|
|
bool hasDefaultTypes = std::any_of(alias->typeParams.begin(), alias->typeParams.end(), [](auto&& el) {
|
|
|
|
return el.defaultValue.has_value();
|
|
|
|
});
|
|
|
|
|
|
|
|
bool hasDefaultPacks = std::any_of(alias->typePackParams.begin(), alias->typePackParams.end(), [](auto&& el) {
|
|
|
|
return el.defaultValue.has_value();
|
|
|
|
});
|
|
|
|
|
|
|
|
if (!ty->hasParameterList)
|
|
|
|
{
|
|
|
|
if ((!alias->typeParams.empty() && !hasDefaultTypes) || (!alias->typePackParams.empty() && !hasDefaultPacks))
|
|
|
|
{
|
|
|
|
reportError(GenericError{"Type parameter list is required"}, ty->location);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t typesProvided = 0;
|
|
|
|
size_t extraTypes = 0;
|
|
|
|
size_t packsProvided = 0;
|
|
|
|
|
|
|
|
for (const AstTypeOrPack& p : ty->parameters)
|
|
|
|
{
|
|
|
|
if (p.type)
|
|
|
|
{
|
|
|
|
if (packsProvided != 0)
|
|
|
|
{
|
|
|
|
reportError(GenericError{"Type parameters must come before type pack parameters"}, ty->location);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (typesProvided < typesRequired)
|
|
|
|
{
|
|
|
|
typesProvided += 1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
extraTypes += 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (p.typePack)
|
|
|
|
{
|
|
|
|
TypePackId tp = lookupPackAnnotation(p.typePack);
|
|
|
|
|
|
|
|
if (typesProvided < typesRequired && size(tp) == 1 && finite(tp) && first(tp))
|
|
|
|
{
|
|
|
|
typesProvided += 1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
packsProvided += 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (extraTypes != 0 && packsProvided == 0)
|
|
|
|
{
|
|
|
|
packsProvided += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (size_t i = typesProvided; i < typesRequired; ++i)
|
|
|
|
{
|
|
|
|
if (alias->typeParams[i].defaultValue)
|
|
|
|
{
|
|
|
|
typesProvided += 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (size_t i = packsProvided; i < packsProvided; ++i)
|
|
|
|
{
|
|
|
|
if (alias->typePackParams[i].defaultValue)
|
|
|
|
{
|
|
|
|
packsProvided += 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (extraTypes == 0 && packsProvided + 1 == packsRequired)
|
|
|
|
{
|
|
|
|
packsProvided += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (typesProvided != typesRequired || packsProvided != packsRequired)
|
|
|
|
{
|
|
|
|
reportError(IncorrectGenericParameterCount{
|
|
|
|
/* name */ ty->name.value,
|
|
|
|
/* typeFun */ *alias,
|
|
|
|
/* actualParameters */ typesProvided,
|
|
|
|
/* actualPackParameters */ packsProvided,
|
|
|
|
},
|
|
|
|
ty->location);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-08-11 21:42:54 +01:00
|
|
|
if (scope->lookupPack(ty->name.value))
|
2022-08-04 22:27:28 +01:00
|
|
|
{
|
|
|
|
reportError(
|
|
|
|
SwappedGenericTypeParameter{
|
|
|
|
ty->name.value,
|
|
|
|
SwappedGenericTypeParameter::Kind::Type,
|
|
|
|
},
|
|
|
|
ty->location);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
reportError(UnknownSymbol{ty->name.value, UnknownSymbol::Context::Type}, ty->location);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool visit(AstTypePack*) override
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool visit(AstTypePackGeneric* tp) override
|
|
|
|
{
|
|
|
|
Scope* scope = findInnermostScope(tp->location);
|
|
|
|
LUAU_ASSERT(scope);
|
|
|
|
|
2022-08-11 21:42:54 +01:00
|
|
|
std::optional<TypePackId> alias = scope->lookupPack(tp->genericName.value);
|
2022-08-04 22:27:28 +01:00
|
|
|
if (!alias.has_value())
|
2022-06-24 02:44:07 +01:00
|
|
|
{
|
2022-08-11 21:42:54 +01:00
|
|
|
if (scope->lookupType(tp->genericName.value))
|
2022-08-04 22:27:28 +01:00
|
|
|
{
|
|
|
|
reportError(
|
|
|
|
SwappedGenericTypeParameter{
|
|
|
|
tp->genericName.value,
|
|
|
|
SwappedGenericTypeParameter::Kind::Pack,
|
|
|
|
},
|
|
|
|
tp->location);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
reportError(UnknownSymbol{tp->genericName.value, UnknownSymbol::Context::Type}, tp->location);
|
|
|
|
}
|
2022-06-24 02:44:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-06-17 01:54:42 +01:00
|
|
|
void reportError(TypeErrorData&& data, const Location& location)
|
|
|
|
{
|
|
|
|
module->errors.emplace_back(location, sourceModule->name, std::move(data));
|
|
|
|
}
|
2022-07-01 00:29:02 +01:00
|
|
|
|
|
|
|
void reportError(TypeError e)
|
|
|
|
{
|
|
|
|
module->errors.emplace_back(std::move(e));
|
|
|
|
}
|
2022-08-11 21:42:54 +01:00
|
|
|
|
|
|
|
std::optional<TypeId> getIndexTypeFromType(
|
|
|
|
const ScopePtr& scope, TypeId type, const Name& name, const Location& location, bool addErrors)
|
|
|
|
{
|
|
|
|
type = follow(type);
|
|
|
|
|
|
|
|
if (get<ErrorTypeVar>(type) || get<AnyTypeVar>(type) || get<NeverTypeVar>(type))
|
|
|
|
return type;
|
|
|
|
|
|
|
|
if (auto f = get<FreeTypeVar>(type))
|
|
|
|
*asMutable(type) = TableTypeVar{TableState::Free, f->level};
|
|
|
|
|
|
|
|
if (isString(type))
|
|
|
|
{
|
|
|
|
std::optional<TypeId> mtIndex = Luau::findMetatableEntry(module->errors, singletonTypes.stringType, "__index", location);
|
|
|
|
LUAU_ASSERT(mtIndex);
|
|
|
|
type = *mtIndex;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (TableTypeVar* tableType = getMutableTableType(type))
|
|
|
|
{
|
|
|
|
|
|
|
|
return findTablePropertyRespectingMeta(module->errors, type, name, location);
|
|
|
|
}
|
|
|
|
else if (const ClassTypeVar* cls = get<ClassTypeVar>(type))
|
|
|
|
{
|
|
|
|
const Property* prop = lookupClassProp(cls, name);
|
|
|
|
if (prop)
|
|
|
|
return prop->type;
|
|
|
|
}
|
|
|
|
else if (const UnionTypeVar* utv = get<UnionTypeVar>(type))
|
|
|
|
{
|
|
|
|
std::vector<TypeId> goodOptions;
|
|
|
|
std::vector<TypeId> badOptions;
|
|
|
|
|
|
|
|
for (TypeId t : utv)
|
|
|
|
{
|
|
|
|
// TODO: we should probably limit recursion here?
|
|
|
|
// RecursionLimiter _rl(&recursionCount, FInt::LuauTypeInferRecursionLimit);
|
|
|
|
|
|
|
|
// Not needed when we normalize types.
|
|
|
|
if (get<AnyTypeVar>(follow(t)))
|
|
|
|
return t;
|
|
|
|
|
|
|
|
if (std::optional<TypeId> ty = getIndexTypeFromType(scope, t, name, location, /* addErrors= */ false))
|
|
|
|
goodOptions.push_back(*ty);
|
|
|
|
else
|
|
|
|
badOptions.push_back(t);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!badOptions.empty())
|
|
|
|
{
|
|
|
|
if (addErrors)
|
|
|
|
{
|
|
|
|
if (goodOptions.empty())
|
|
|
|
reportError(UnknownProperty{type, name}, location);
|
|
|
|
else
|
|
|
|
reportError(MissingUnionProperty{type, badOptions, name}, location);
|
|
|
|
}
|
|
|
|
return std::nullopt;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<TypeId> result = reduceUnion(goodOptions);
|
|
|
|
if (result.empty())
|
|
|
|
return singletonTypes.neverType;
|
|
|
|
|
|
|
|
if (result.size() == 1)
|
|
|
|
return result[0];
|
|
|
|
|
|
|
|
return module->internalTypes.addType(UnionTypeVar{std::move(result)});
|
|
|
|
}
|
|
|
|
else if (const IntersectionTypeVar* itv = get<IntersectionTypeVar>(type))
|
|
|
|
{
|
|
|
|
std::vector<TypeId> parts;
|
|
|
|
|
|
|
|
for (TypeId t : itv->parts)
|
|
|
|
{
|
|
|
|
// TODO: we should probably limit recursion here?
|
|
|
|
// RecursionLimiter _rl(&recursionCount, FInt::LuauTypeInferRecursionLimit);
|
|
|
|
|
|
|
|
if (std::optional<TypeId> ty = getIndexTypeFromType(scope, t, name, location, /* addErrors= */ false))
|
|
|
|
parts.push_back(*ty);
|
|
|
|
}
|
|
|
|
|
|
|
|
// If no parts of the intersection had the property we looked up for, it never existed at all.
|
|
|
|
if (parts.empty())
|
|
|
|
{
|
|
|
|
if (addErrors)
|
|
|
|
reportError(UnknownProperty{type, name}, location);
|
|
|
|
return std::nullopt;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (parts.size() == 1)
|
|
|
|
return parts[0];
|
|
|
|
|
|
|
|
return module->internalTypes.addType(IntersectionTypeVar{std::move(parts)}); // Not at all correct.
|
|
|
|
}
|
|
|
|
|
|
|
|
if (addErrors)
|
|
|
|
reportError(UnknownProperty{type, name}, location);
|
|
|
|
|
|
|
|
return std::nullopt;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<TypeId> reduceUnion(const std::vector<TypeId>& types)
|
|
|
|
{
|
|
|
|
std::vector<TypeId> result;
|
|
|
|
for (TypeId t : types)
|
|
|
|
{
|
|
|
|
t = follow(t);
|
|
|
|
if (get<NeverTypeVar>(t))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (get<ErrorTypeVar>(t) || get<AnyTypeVar>(t))
|
|
|
|
return {t};
|
|
|
|
|
|
|
|
if (const UnionTypeVar* utv = get<UnionTypeVar>(t))
|
|
|
|
{
|
|
|
|
for (TypeId ty : utv)
|
|
|
|
{
|
|
|
|
ty = follow(ty);
|
|
|
|
if (get<NeverTypeVar>(ty))
|
|
|
|
continue;
|
|
|
|
if (get<ErrorTypeVar>(ty) || get<AnyTypeVar>(ty))
|
|
|
|
return {ty};
|
|
|
|
|
|
|
|
if (result.end() == std::find(result.begin(), result.end(), ty))
|
|
|
|
result.push_back(ty);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (std::find(result.begin(), result.end(), t) == result.end())
|
|
|
|
result.push_back(t);
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
2022-06-17 01:54:42 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
void check(const SourceModule& sourceModule, Module* module)
|
|
|
|
{
|
|
|
|
TypeChecker2 typeChecker{&sourceModule, module};
|
|
|
|
|
|
|
|
sourceModule.root->visit(&typeChecker);
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace Luau
|