mirror of
https://github.com/luau-lang/luau.git
synced 2025-03-13 15:34:27 +00:00

### What's new * Added lint warning for using redundant `@native` attributes on functions inside a `--!native` module * Improved typechecking speed in old solver for modules with large types ### New Solver * Fixed the length type function sealing the table prematurely * Fixed crashes caused by general table indexing expressions ### VM * Added support for a specialized 3-argument fast-call instruction to improve performance of `vector` constructor, `buffer` writes and a few `bit32` methods --- ### Internal Contributors Co-authored-by: Aaron Weiss <aaronweiss@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Aviral Goel <agoel@roblox.com> Co-authored-by: Vighnesh Vijay <vvijay@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com> --------- Co-authored-by: Aaron Weiss <aaronweiss@roblox.com> Co-authored-by: Alexander McCord <amccord@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Vighnesh <vvijay@roblox.com> Co-authored-by: Aviral Goel <agoel@roblox.com> Co-authored-by: David Cope <dcope@roblox.com> Co-authored-by: Lily Brown <lbrown@roblox.com>
1241 lines
46 KiB
C++
1241 lines
46 KiB
C++
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
|
#include "Luau/BuiltinDefinitions.h"
|
|
|
|
#include "Luau/Ast.h"
|
|
#include "Luau/Frontend.h"
|
|
#include "Luau/Symbol.h"
|
|
#include "Luau/Common.h"
|
|
#include "Luau/ToString.h"
|
|
#include "Luau/ConstraintSolver.h"
|
|
#include "Luau/ConstraintGenerator.h"
|
|
#include "Luau/NotNull.h"
|
|
#include "Luau/TypeInfer.h"
|
|
#include "Luau/TypeFamily.h"
|
|
#include "Luau/TypePack.h"
|
|
#include "Luau/Type.h"
|
|
#include "Luau/TypeUtils.h"
|
|
|
|
#include <algorithm>
|
|
|
|
/** FIXME: Many of these type definitions are not quite completely accurate.
|
|
*
|
|
* Some of them require richer generics than we have. For instance, we do not yet have a way to talk
|
|
* about a function that takes any number of values, but where each value must have some specific type.
|
|
*/
|
|
|
|
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
|
|
|
namespace Luau
|
|
{
|
|
|
|
static std::optional<WithPredicate<TypePackId>> magicFunctionSelect(
|
|
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate);
|
|
static std::optional<WithPredicate<TypePackId>> magicFunctionSetMetaTable(
|
|
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate);
|
|
static std::optional<WithPredicate<TypePackId>> magicFunctionAssert(
|
|
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate);
|
|
static std::optional<WithPredicate<TypePackId>> magicFunctionPack(
|
|
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate);
|
|
static std::optional<WithPredicate<TypePackId>> magicFunctionRequire(
|
|
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate);
|
|
|
|
|
|
static bool dcrMagicFunctionSelect(MagicFunctionCallContext context);
|
|
static bool dcrMagicFunctionRequire(MagicFunctionCallContext context);
|
|
static bool dcrMagicFunctionPack(MagicFunctionCallContext context);
|
|
|
|
TypeId makeUnion(TypeArena& arena, std::vector<TypeId>&& types)
|
|
{
|
|
return arena.addType(UnionType{std::move(types)});
|
|
}
|
|
|
|
TypeId makeIntersection(TypeArena& arena, std::vector<TypeId>&& types)
|
|
{
|
|
return arena.addType(IntersectionType{std::move(types)});
|
|
}
|
|
|
|
TypeId makeOption(NotNull<BuiltinTypes> builtinTypes, TypeArena& arena, TypeId t)
|
|
{
|
|
LUAU_ASSERT(t);
|
|
return makeUnion(arena, {builtinTypes->nilType, t});
|
|
}
|
|
|
|
TypeId makeFunction(
|
|
TypeArena& arena, std::optional<TypeId> selfType, std::initializer_list<TypeId> paramTypes, std::initializer_list<TypeId> retTypes, bool checked)
|
|
{
|
|
return makeFunction(arena, selfType, {}, {}, paramTypes, {}, retTypes, checked);
|
|
}
|
|
|
|
TypeId makeFunction(TypeArena& arena, std::optional<TypeId> selfType, std::initializer_list<TypeId> generics,
|
|
std::initializer_list<TypePackId> genericPacks, std::initializer_list<TypeId> paramTypes, std::initializer_list<TypeId> retTypes, bool checked)
|
|
{
|
|
return makeFunction(arena, selfType, generics, genericPacks, paramTypes, {}, retTypes, checked);
|
|
}
|
|
|
|
TypeId makeFunction(TypeArena& arena, std::optional<TypeId> selfType, std::initializer_list<TypeId> paramTypes,
|
|
std::initializer_list<std::string> paramNames, std::initializer_list<TypeId> retTypes, bool checked)
|
|
{
|
|
return makeFunction(arena, selfType, {}, {}, paramTypes, paramNames, retTypes, checked);
|
|
}
|
|
|
|
TypeId makeFunction(TypeArena& arena, std::optional<TypeId> selfType, std::initializer_list<TypeId> generics,
|
|
std::initializer_list<TypePackId> genericPacks, std::initializer_list<TypeId> paramTypes, std::initializer_list<std::string> paramNames,
|
|
std::initializer_list<TypeId> retTypes, bool checked)
|
|
{
|
|
std::vector<TypeId> params;
|
|
if (selfType)
|
|
params.push_back(*selfType);
|
|
for (auto&& p : paramTypes)
|
|
params.push_back(p);
|
|
|
|
TypePackId paramPack = arena.addTypePack(std::move(params));
|
|
TypePackId retPack = arena.addTypePack(std::vector<TypeId>(retTypes));
|
|
FunctionType ftv{generics, genericPacks, paramPack, retPack, {}, selfType.has_value()};
|
|
|
|
if (selfType)
|
|
ftv.argNames.push_back(Luau::FunctionArgument{"self", {}});
|
|
|
|
if (paramNames.size() != 0)
|
|
{
|
|
for (auto&& p : paramNames)
|
|
ftv.argNames.push_back(Luau::FunctionArgument{std::move(p), {}});
|
|
}
|
|
else if (selfType)
|
|
{
|
|
// If argument names were not provided, but we have already added a name for 'self' argument, we have to fill remaining slots as well
|
|
for (size_t i = 0; i < paramTypes.size(); i++)
|
|
ftv.argNames.push_back(std::nullopt);
|
|
}
|
|
|
|
ftv.isCheckedFunction = checked;
|
|
|
|
return arena.addType(std::move(ftv));
|
|
}
|
|
|
|
void attachMagicFunction(TypeId ty, MagicFunction fn)
|
|
{
|
|
if (auto ftv = getMutable<FunctionType>(ty))
|
|
ftv->magicFunction = fn;
|
|
else
|
|
LUAU_ASSERT(!"Got a non functional type");
|
|
}
|
|
|
|
void attachDcrMagicFunction(TypeId ty, DcrMagicFunction fn)
|
|
{
|
|
if (auto ftv = getMutable<FunctionType>(ty))
|
|
ftv->dcrMagicFunction = fn;
|
|
else
|
|
LUAU_ASSERT(!"Got a non functional type");
|
|
}
|
|
|
|
void attachDcrMagicRefinement(TypeId ty, DcrMagicRefinement fn)
|
|
{
|
|
if (auto ftv = getMutable<FunctionType>(ty))
|
|
ftv->dcrMagicRefinement = fn;
|
|
else
|
|
LUAU_ASSERT(!"Got a non functional type");
|
|
}
|
|
|
|
Property makeProperty(TypeId ty, std::optional<std::string> documentationSymbol)
|
|
{
|
|
return {
|
|
/* type */ ty,
|
|
/* deprecated */ false,
|
|
/* deprecatedSuggestion */ {},
|
|
/* location */ std::nullopt,
|
|
/* tags */ {},
|
|
documentationSymbol,
|
|
};
|
|
}
|
|
|
|
void addGlobalBinding(GlobalTypes& globals, const std::string& name, TypeId ty, const std::string& packageName)
|
|
{
|
|
addGlobalBinding(globals, globals.globalScope, name, ty, packageName);
|
|
}
|
|
|
|
void addGlobalBinding(GlobalTypes& globals, const std::string& name, Binding binding)
|
|
{
|
|
addGlobalBinding(globals, globals.globalScope, name, binding);
|
|
}
|
|
|
|
void addGlobalBinding(GlobalTypes& globals, const ScopePtr& scope, const std::string& name, TypeId ty, const std::string& packageName)
|
|
{
|
|
std::string documentationSymbol = packageName + "/global/" + name;
|
|
addGlobalBinding(globals, scope, name, Binding{ty, Location{}, {}, {}, documentationSymbol});
|
|
}
|
|
|
|
void addGlobalBinding(GlobalTypes& globals, const ScopePtr& scope, const std::string& name, Binding binding)
|
|
{
|
|
scope->bindings[globals.globalNames.names->getOrAdd(name.c_str())] = binding;
|
|
}
|
|
|
|
std::optional<Binding> tryGetGlobalBinding(GlobalTypes& globals, const std::string& name)
|
|
{
|
|
AstName astName = globals.globalNames.names->getOrAdd(name.c_str());
|
|
auto it = globals.globalScope->bindings.find(astName);
|
|
if (it != globals.globalScope->bindings.end())
|
|
return it->second;
|
|
|
|
return std::nullopt;
|
|
}
|
|
|
|
TypeId getGlobalBinding(GlobalTypes& globals, const std::string& name)
|
|
{
|
|
auto t = tryGetGlobalBinding(globals, name);
|
|
LUAU_ASSERT(t.has_value());
|
|
return t->typeId;
|
|
}
|
|
|
|
Binding* tryGetGlobalBindingRef(GlobalTypes& globals, const std::string& name)
|
|
{
|
|
AstName astName = globals.globalNames.names->get(name.c_str());
|
|
if (astName == AstName())
|
|
return nullptr;
|
|
|
|
auto it = globals.globalScope->bindings.find(astName);
|
|
if (it != globals.globalScope->bindings.end())
|
|
return &it->second;
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void assignPropDocumentationSymbols(TableType::Props& props, const std::string& baseName)
|
|
{
|
|
for (auto& [name, prop] : props)
|
|
{
|
|
prop.documentationSymbol = baseName + "." + name;
|
|
}
|
|
}
|
|
|
|
void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeCheckForAutocomplete)
|
|
{
|
|
LUAU_ASSERT(!globals.globalTypes.types.isFrozen());
|
|
LUAU_ASSERT(!globals.globalTypes.typePacks.isFrozen());
|
|
|
|
TypeArena& arena = globals.globalTypes;
|
|
NotNull<BuiltinTypes> builtinTypes = globals.builtinTypes;
|
|
|
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
|
kBuiltinTypeFamilies.addToScope(NotNull{&arena}, NotNull{globals.globalScope.get()});
|
|
|
|
LoadDefinitionFileResult loadResult = frontend.loadDefinitionFile(
|
|
globals, globals.globalScope, getBuiltinDefinitionSource(), "@luau", /* captureComments */ false, typeCheckForAutocomplete);
|
|
LUAU_ASSERT(loadResult.success);
|
|
|
|
TypeId genericK = arena.addType(GenericType{"K"});
|
|
TypeId genericV = arena.addType(GenericType{"V"});
|
|
TypeId mapOfKtoV = arena.addType(TableType{{}, TableIndexer(genericK, genericV), globals.globalScope->level, TableState::Generic});
|
|
|
|
std::optional<TypeId> stringMetatableTy = getMetatable(builtinTypes->stringType, builtinTypes);
|
|
LUAU_ASSERT(stringMetatableTy);
|
|
const TableType* stringMetatableTable = get<TableType>(follow(*stringMetatableTy));
|
|
LUAU_ASSERT(stringMetatableTable);
|
|
|
|
auto it = stringMetatableTable->props.find("__index");
|
|
LUAU_ASSERT(it != stringMetatableTable->props.end());
|
|
|
|
addGlobalBinding(globals, "string", it->second.type(), "@luau");
|
|
|
|
// next<K, V>(t: Table<K, V>, i: K?) -> (K?, V)
|
|
TypePackId nextArgsTypePack = arena.addTypePack(TypePack{{mapOfKtoV, makeOption(builtinTypes, arena, genericK)}});
|
|
TypePackId nextRetsTypePack = arena.addTypePack(TypePack{{makeOption(builtinTypes, arena, genericK), genericV}});
|
|
addGlobalBinding(globals, "next", arena.addType(FunctionType{{genericK, genericV}, {}, nextArgsTypePack, nextRetsTypePack}), "@luau");
|
|
|
|
TypePackId pairsArgsTypePack = arena.addTypePack({mapOfKtoV});
|
|
|
|
TypeId pairsNext = arena.addType(FunctionType{nextArgsTypePack, nextRetsTypePack});
|
|
TypePackId pairsReturnTypePack = arena.addTypePack(TypePack{{pairsNext, mapOfKtoV, builtinTypes->nilType}});
|
|
|
|
// pairs<K, V>(t: Table<K, V>) -> ((Table<K, V>, K?) -> (K, V), Table<K, V>, nil)
|
|
addGlobalBinding(globals, "pairs", arena.addType(FunctionType{{genericK, genericV}, {}, pairsArgsTypePack, pairsReturnTypePack}), "@luau");
|
|
|
|
TypeId genericMT = arena.addType(GenericType{"MT"});
|
|
|
|
TableType tab{TableState::Generic, globals.globalScope->level};
|
|
TypeId tabTy = arena.addType(tab);
|
|
|
|
TypeId tableMetaMT = arena.addType(MetatableType{tabTy, genericMT});
|
|
|
|
// getmetatable : <MT>({ @metatable MT, {+ +} }) -> MT
|
|
addGlobalBinding(globals, "getmetatable", makeFunction(arena, std::nullopt, {genericMT}, {}, {tableMetaMT}, {genericMT}), "@luau");
|
|
|
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
|
{
|
|
TypeId genericT = arena.addType(GenericType{"T"});
|
|
TypeId tMetaMT = arena.addType(MetatableType{genericT, genericMT});
|
|
|
|
// clang-format off
|
|
// setmetatable<T: {}, MT>(T, MT) -> { @metatable MT, T }
|
|
addGlobalBinding(globals, "setmetatable",
|
|
arena.addType(
|
|
FunctionType{
|
|
{genericT, genericMT},
|
|
{},
|
|
arena.addTypePack(TypePack{{genericT, genericMT}}),
|
|
arena.addTypePack(TypePack{{tMetaMT}})
|
|
}
|
|
), "@luau"
|
|
);
|
|
// clang-format on
|
|
}
|
|
else
|
|
{
|
|
// clang-format off
|
|
// setmetatable<T: {}, MT>(T, MT) -> { @metatable MT, T }
|
|
addGlobalBinding(globals, "setmetatable",
|
|
arena.addType(
|
|
FunctionType{
|
|
{genericMT},
|
|
{},
|
|
arena.addTypePack(TypePack{{tabTy, genericMT}}),
|
|
arena.addTypePack(TypePack{{tableMetaMT}})
|
|
}
|
|
), "@luau"
|
|
);
|
|
// clang-format on
|
|
}
|
|
|
|
for (const auto& pair : globals.globalScope->bindings)
|
|
{
|
|
persist(pair.second.typeId);
|
|
|
|
if (TableType* ttv = getMutable<TableType>(pair.second.typeId))
|
|
{
|
|
if (!ttv->name)
|
|
ttv->name = "typeof(" + toString(pair.first) + ")";
|
|
}
|
|
}
|
|
|
|
attachMagicFunction(getGlobalBinding(globals, "assert"), magicFunctionAssert);
|
|
|
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
|
{
|
|
// declare function assert<T>(value: T, errorMessage: string?): intersect<T, ~(false?)>
|
|
TypeId genericT = arena.addType(GenericType{"T"});
|
|
TypeId refinedTy = arena.addType(TypeFamilyInstanceType{
|
|
NotNull{&kBuiltinTypeFamilies.intersectFamily}, {genericT, arena.addType(NegationType{builtinTypes->falsyType})}, {}});
|
|
|
|
TypeId assertTy = arena.addType(FunctionType{
|
|
{genericT}, {}, arena.addTypePack(TypePack{{genericT, builtinTypes->optionalStringType}}), arena.addTypePack(TypePack{{refinedTy}})});
|
|
addGlobalBinding(globals, "assert", assertTy, "@luau");
|
|
}
|
|
|
|
attachMagicFunction(getGlobalBinding(globals, "setmetatable"), magicFunctionSetMetaTable);
|
|
attachMagicFunction(getGlobalBinding(globals, "select"), magicFunctionSelect);
|
|
attachDcrMagicFunction(getGlobalBinding(globals, "select"), dcrMagicFunctionSelect);
|
|
|
|
if (TableType* ttv = getMutable<TableType>(getGlobalBinding(globals, "table")))
|
|
{
|
|
// tabTy is a generic table type which we can't express via declaration syntax yet
|
|
ttv->props["freeze"] = makeProperty(makeFunction(arena, std::nullopt, {tabTy}, {tabTy}), "@luau/global/table.freeze");
|
|
ttv->props["clone"] = makeProperty(makeFunction(arena, std::nullopt, {tabTy}, {tabTy}), "@luau/global/table.clone");
|
|
|
|
ttv->props["getn"].deprecated = true;
|
|
ttv->props["getn"].deprecatedSuggestion = "#";
|
|
ttv->props["foreach"].deprecated = true;
|
|
ttv->props["foreachi"].deprecated = true;
|
|
|
|
attachMagicFunction(ttv->props["pack"].type(), magicFunctionPack);
|
|
attachDcrMagicFunction(ttv->props["pack"].type(), dcrMagicFunctionPack);
|
|
}
|
|
|
|
attachMagicFunction(getGlobalBinding(globals, "require"), magicFunctionRequire);
|
|
attachDcrMagicFunction(getGlobalBinding(globals, "require"), dcrMagicFunctionRequire);
|
|
}
|
|
|
|
static std::vector<TypeId> parseFormatString(NotNull<BuiltinTypes> builtinTypes, const char* data, size_t size)
|
|
{
|
|
const char* options = "cdiouxXeEfgGqs*";
|
|
|
|
std::vector<TypeId> result;
|
|
|
|
for (size_t i = 0; i < size; ++i)
|
|
{
|
|
if (data[i] == '%')
|
|
{
|
|
i++;
|
|
|
|
if (i < size && data[i] == '%')
|
|
continue;
|
|
|
|
// we just ignore all characters (including flags/precision) up until first alphabetic character
|
|
while (i < size && !(data[i] > 0 && (isalpha(data[i]) || data[i] == '*')))
|
|
i++;
|
|
|
|
if (i == size)
|
|
break;
|
|
|
|
if (data[i] == 'q' || data[i] == 's')
|
|
result.push_back(builtinTypes->stringType);
|
|
else if (data[i] == '*')
|
|
result.push_back(builtinTypes->unknownType);
|
|
else if (strchr(options, data[i]))
|
|
result.push_back(builtinTypes->numberType);
|
|
else
|
|
result.push_back(builtinTypes->errorRecoveryType(builtinTypes->anyType));
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
std::optional<WithPredicate<TypePackId>> magicFunctionFormat(
|
|
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate)
|
|
{
|
|
auto [paramPack, _predicates] = withPredicate;
|
|
|
|
TypeArena& arena = typechecker.currentModule->internalTypes;
|
|
|
|
AstExprConstantString* fmt = nullptr;
|
|
if (auto index = expr.func->as<AstExprIndexName>(); index && expr.self)
|
|
{
|
|
if (auto group = index->expr->as<AstExprGroup>())
|
|
fmt = group->expr->as<AstExprConstantString>();
|
|
else
|
|
fmt = index->expr->as<AstExprConstantString>();
|
|
}
|
|
|
|
if (!expr.self && expr.args.size > 0)
|
|
fmt = expr.args.data[0]->as<AstExprConstantString>();
|
|
|
|
if (!fmt)
|
|
return std::nullopt;
|
|
|
|
std::vector<TypeId> expected = parseFormatString(typechecker.builtinTypes, fmt->value.data, fmt->value.size);
|
|
const auto& [params, tail] = flatten(paramPack);
|
|
|
|
size_t paramOffset = 1;
|
|
size_t dataOffset = expr.self ? 0 : 1;
|
|
|
|
// unify the prefix one argument at a time
|
|
for (size_t i = 0; i < expected.size() && i + paramOffset < params.size(); ++i)
|
|
{
|
|
Location location = expr.args.data[std::min(i + dataOffset, expr.args.size - 1)]->location;
|
|
|
|
typechecker.unify(params[i + paramOffset], expected[i], scope, location);
|
|
}
|
|
|
|
// if we know the argument count or if we have too many arguments for sure, we can issue an error
|
|
size_t numActualParams = params.size();
|
|
size_t numExpectedParams = expected.size() + 1; // + 1 for the format string
|
|
|
|
if (numExpectedParams != numActualParams && (!tail || numExpectedParams < numActualParams))
|
|
typechecker.reportError(TypeError{expr.location, CountMismatch{numExpectedParams, std::nullopt, numActualParams}});
|
|
|
|
return WithPredicate<TypePackId>{arena.addTypePack({typechecker.stringType})};
|
|
}
|
|
|
|
static bool dcrMagicFunctionFormat(MagicFunctionCallContext context)
|
|
{
|
|
TypeArena* arena = context.solver->arena;
|
|
|
|
AstExprConstantString* fmt = nullptr;
|
|
if (auto index = context.callSite->func->as<AstExprIndexName>(); index && context.callSite->self)
|
|
{
|
|
if (auto group = index->expr->as<AstExprGroup>())
|
|
fmt = group->expr->as<AstExprConstantString>();
|
|
else
|
|
fmt = index->expr->as<AstExprConstantString>();
|
|
}
|
|
|
|
if (!context.callSite->self && context.callSite->args.size > 0)
|
|
fmt = context.callSite->args.data[0]->as<AstExprConstantString>();
|
|
|
|
if (!fmt)
|
|
return false;
|
|
|
|
std::vector<TypeId> expected = parseFormatString(context.solver->builtinTypes, fmt->value.data, fmt->value.size);
|
|
const auto& [params, tail] = flatten(context.arguments);
|
|
|
|
size_t paramOffset = 1;
|
|
|
|
// unify the prefix one argument at a time
|
|
for (size_t i = 0; i < expected.size() && i + paramOffset < params.size(); ++i)
|
|
{
|
|
context.solver->unify(context.constraint, params[i + paramOffset], expected[i]);
|
|
}
|
|
|
|
// if we know the argument count or if we have too many arguments for sure, we can issue an error
|
|
size_t numActualParams = params.size();
|
|
size_t numExpectedParams = expected.size() + 1; // + 1 for the format string
|
|
|
|
if (numExpectedParams != numActualParams && (!tail || numExpectedParams < numActualParams))
|
|
context.solver->reportError(TypeError{context.callSite->location, CountMismatch{numExpectedParams, std::nullopt, numActualParams}});
|
|
|
|
TypePackId resultPack = arena->addTypePack({context.solver->builtinTypes->stringType});
|
|
asMutable(context.result)->ty.emplace<BoundTypePack>(resultPack);
|
|
|
|
return true;
|
|
}
|
|
|
|
static std::vector<TypeId> parsePatternString(NotNull<BuiltinTypes> builtinTypes, const char* data, size_t size)
|
|
{
|
|
std::vector<TypeId> result;
|
|
int depth = 0;
|
|
bool parsingSet = false;
|
|
|
|
for (size_t i = 0; i < size; ++i)
|
|
{
|
|
if (data[i] == '%')
|
|
{
|
|
++i;
|
|
if (!parsingSet && i < size && data[i] == 'b')
|
|
i += 2;
|
|
}
|
|
else if (!parsingSet && data[i] == '[')
|
|
{
|
|
parsingSet = true;
|
|
if (i + 1 < size && data[i + 1] == ']')
|
|
i += 1;
|
|
}
|
|
else if (parsingSet && data[i] == ']')
|
|
{
|
|
parsingSet = false;
|
|
}
|
|
else if (data[i] == '(')
|
|
{
|
|
if (parsingSet)
|
|
continue;
|
|
|
|
if (i + 1 < size && data[i + 1] == ')')
|
|
{
|
|
i++;
|
|
result.push_back(builtinTypes->optionalNumberType);
|
|
continue;
|
|
}
|
|
|
|
++depth;
|
|
result.push_back(builtinTypes->optionalStringType);
|
|
}
|
|
else if (data[i] == ')')
|
|
{
|
|
if (parsingSet)
|
|
continue;
|
|
|
|
--depth;
|
|
|
|
if (depth < 0)
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (depth != 0 || parsingSet)
|
|
return std::vector<TypeId>();
|
|
|
|
if (result.empty())
|
|
result.push_back(builtinTypes->optionalStringType);
|
|
|
|
return result;
|
|
}
|
|
|
|
static std::optional<WithPredicate<TypePackId>> magicFunctionGmatch(
|
|
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate)
|
|
{
|
|
auto [paramPack, _predicates] = withPredicate;
|
|
const auto& [params, tail] = flatten(paramPack);
|
|
|
|
if (params.size() != 2)
|
|
return std::nullopt;
|
|
|
|
TypeArena& arena = typechecker.currentModule->internalTypes;
|
|
|
|
AstExprConstantString* pattern = nullptr;
|
|
size_t index = expr.self ? 0 : 1;
|
|
if (expr.args.size > index)
|
|
pattern = expr.args.data[index]->as<AstExprConstantString>();
|
|
|
|
if (!pattern)
|
|
return std::nullopt;
|
|
|
|
std::vector<TypeId> returnTypes = parsePatternString(typechecker.builtinTypes, pattern->value.data, pattern->value.size);
|
|
|
|
if (returnTypes.empty())
|
|
return std::nullopt;
|
|
|
|
typechecker.unify(params[0], typechecker.stringType, scope, expr.args.data[0]->location);
|
|
|
|
const TypePackId emptyPack = arena.addTypePack({});
|
|
const TypePackId returnList = arena.addTypePack(returnTypes);
|
|
const TypeId iteratorType = arena.addType(FunctionType{emptyPack, returnList});
|
|
return WithPredicate<TypePackId>{arena.addTypePack({iteratorType})};
|
|
}
|
|
|
|
static bool dcrMagicFunctionGmatch(MagicFunctionCallContext context)
|
|
{
|
|
const auto& [params, tail] = flatten(context.arguments);
|
|
|
|
if (params.size() != 2)
|
|
return false;
|
|
|
|
TypeArena* arena = context.solver->arena;
|
|
|
|
AstExprConstantString* pattern = nullptr;
|
|
size_t index = context.callSite->self ? 0 : 1;
|
|
if (context.callSite->args.size > index)
|
|
pattern = context.callSite->args.data[index]->as<AstExprConstantString>();
|
|
|
|
if (!pattern)
|
|
return false;
|
|
|
|
std::vector<TypeId> returnTypes = parsePatternString(context.solver->builtinTypes, pattern->value.data, pattern->value.size);
|
|
|
|
if (returnTypes.empty())
|
|
return false;
|
|
|
|
context.solver->unify(context.constraint, params[0], context.solver->builtinTypes->stringType);
|
|
|
|
const TypePackId emptyPack = arena->addTypePack({});
|
|
const TypePackId returnList = arena->addTypePack(returnTypes);
|
|
const TypeId iteratorType = arena->addType(FunctionType{emptyPack, returnList});
|
|
const TypePackId resTypePack = arena->addTypePack({iteratorType});
|
|
asMutable(context.result)->ty.emplace<BoundTypePack>(resTypePack);
|
|
|
|
return true;
|
|
}
|
|
|
|
static std::optional<WithPredicate<TypePackId>> magicFunctionMatch(
|
|
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate)
|
|
{
|
|
auto [paramPack, _predicates] = withPredicate;
|
|
const auto& [params, tail] = flatten(paramPack);
|
|
|
|
if (params.size() < 2 || params.size() > 3)
|
|
return std::nullopt;
|
|
|
|
TypeArena& arena = typechecker.currentModule->internalTypes;
|
|
|
|
AstExprConstantString* pattern = nullptr;
|
|
size_t patternIndex = expr.self ? 0 : 1;
|
|
if (expr.args.size > patternIndex)
|
|
pattern = expr.args.data[patternIndex]->as<AstExprConstantString>();
|
|
|
|
if (!pattern)
|
|
return std::nullopt;
|
|
|
|
std::vector<TypeId> returnTypes = parsePatternString(typechecker.builtinTypes, pattern->value.data, pattern->value.size);
|
|
|
|
if (returnTypes.empty())
|
|
return std::nullopt;
|
|
|
|
typechecker.unify(params[0], typechecker.stringType, scope, expr.args.data[0]->location);
|
|
|
|
const TypeId optionalNumber = arena.addType(UnionType{{typechecker.nilType, typechecker.numberType}});
|
|
|
|
size_t initIndex = expr.self ? 1 : 2;
|
|
if (params.size() == 3 && expr.args.size > initIndex)
|
|
typechecker.unify(params[2], optionalNumber, scope, expr.args.data[initIndex]->location);
|
|
|
|
const TypePackId returnList = arena.addTypePack(returnTypes);
|
|
return WithPredicate<TypePackId>{returnList};
|
|
}
|
|
|
|
static bool dcrMagicFunctionMatch(MagicFunctionCallContext context)
|
|
{
|
|
const auto& [params, tail] = flatten(context.arguments);
|
|
|
|
if (params.size() < 2 || params.size() > 3)
|
|
return false;
|
|
|
|
TypeArena* arena = context.solver->arena;
|
|
|
|
AstExprConstantString* pattern = nullptr;
|
|
size_t patternIndex = context.callSite->self ? 0 : 1;
|
|
if (context.callSite->args.size > patternIndex)
|
|
pattern = context.callSite->args.data[patternIndex]->as<AstExprConstantString>();
|
|
|
|
if (!pattern)
|
|
return false;
|
|
|
|
std::vector<TypeId> returnTypes = parsePatternString(context.solver->builtinTypes, pattern->value.data, pattern->value.size);
|
|
|
|
if (returnTypes.empty())
|
|
return false;
|
|
|
|
context.solver->unify(context.constraint, params[0], context.solver->builtinTypes->stringType);
|
|
|
|
const TypeId optionalNumber = arena->addType(UnionType{{context.solver->builtinTypes->nilType, context.solver->builtinTypes->numberType}});
|
|
|
|
size_t initIndex = context.callSite->self ? 1 : 2;
|
|
if (params.size() == 3 && context.callSite->args.size > initIndex)
|
|
context.solver->unify(context.constraint, params[2], optionalNumber);
|
|
|
|
const TypePackId returnList = arena->addTypePack(returnTypes);
|
|
asMutable(context.result)->ty.emplace<BoundTypePack>(returnList);
|
|
|
|
return true;
|
|
}
|
|
|
|
static std::optional<WithPredicate<TypePackId>> magicFunctionFind(
|
|
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate)
|
|
{
|
|
auto [paramPack, _predicates] = withPredicate;
|
|
const auto& [params, tail] = flatten(paramPack);
|
|
|
|
if (params.size() < 2 || params.size() > 4)
|
|
return std::nullopt;
|
|
|
|
TypeArena& arena = typechecker.currentModule->internalTypes;
|
|
|
|
AstExprConstantString* pattern = nullptr;
|
|
size_t patternIndex = expr.self ? 0 : 1;
|
|
if (expr.args.size > patternIndex)
|
|
pattern = expr.args.data[patternIndex]->as<AstExprConstantString>();
|
|
|
|
if (!pattern)
|
|
return std::nullopt;
|
|
|
|
bool plain = false;
|
|
size_t plainIndex = expr.self ? 2 : 3;
|
|
if (expr.args.size > plainIndex)
|
|
{
|
|
AstExprConstantBool* p = expr.args.data[plainIndex]->as<AstExprConstantBool>();
|
|
plain = p && p->value;
|
|
}
|
|
|
|
std::vector<TypeId> returnTypes;
|
|
if (!plain)
|
|
{
|
|
returnTypes = parsePatternString(typechecker.builtinTypes, pattern->value.data, pattern->value.size);
|
|
|
|
if (returnTypes.empty())
|
|
return std::nullopt;
|
|
}
|
|
|
|
typechecker.unify(params[0], typechecker.stringType, scope, expr.args.data[0]->location);
|
|
|
|
const TypeId optionalNumber = arena.addType(UnionType{{typechecker.nilType, typechecker.numberType}});
|
|
const TypeId optionalBoolean = arena.addType(UnionType{{typechecker.nilType, typechecker.booleanType}});
|
|
|
|
size_t initIndex = expr.self ? 1 : 2;
|
|
if (params.size() >= 3 && expr.args.size > initIndex)
|
|
typechecker.unify(params[2], optionalNumber, scope, expr.args.data[initIndex]->location);
|
|
|
|
if (params.size() == 4 && expr.args.size > plainIndex)
|
|
typechecker.unify(params[3], optionalBoolean, scope, expr.args.data[plainIndex]->location);
|
|
|
|
returnTypes.insert(returnTypes.begin(), {optionalNumber, optionalNumber});
|
|
|
|
const TypePackId returnList = arena.addTypePack(returnTypes);
|
|
return WithPredicate<TypePackId>{returnList};
|
|
}
|
|
|
|
static bool dcrMagicFunctionFind(MagicFunctionCallContext context)
|
|
{
|
|
const auto& [params, tail] = flatten(context.arguments);
|
|
|
|
if (params.size() < 2 || params.size() > 4)
|
|
return false;
|
|
|
|
TypeArena* arena = context.solver->arena;
|
|
NotNull<BuiltinTypes> builtinTypes = context.solver->builtinTypes;
|
|
|
|
AstExprConstantString* pattern = nullptr;
|
|
size_t patternIndex = context.callSite->self ? 0 : 1;
|
|
if (context.callSite->args.size > patternIndex)
|
|
pattern = context.callSite->args.data[patternIndex]->as<AstExprConstantString>();
|
|
|
|
if (!pattern)
|
|
return false;
|
|
|
|
bool plain = false;
|
|
size_t plainIndex = context.callSite->self ? 2 : 3;
|
|
if (context.callSite->args.size > plainIndex)
|
|
{
|
|
AstExprConstantBool* p = context.callSite->args.data[plainIndex]->as<AstExprConstantBool>();
|
|
plain = p && p->value;
|
|
}
|
|
|
|
std::vector<TypeId> returnTypes;
|
|
if (!plain)
|
|
{
|
|
returnTypes = parsePatternString(builtinTypes, pattern->value.data, pattern->value.size);
|
|
|
|
if (returnTypes.empty())
|
|
return false;
|
|
}
|
|
|
|
context.solver->unify(context.constraint, params[0], builtinTypes->stringType);
|
|
|
|
const TypeId optionalNumber = arena->addType(UnionType{{builtinTypes->nilType, builtinTypes->numberType}});
|
|
const TypeId optionalBoolean = arena->addType(UnionType{{builtinTypes->nilType, builtinTypes->booleanType}});
|
|
|
|
size_t initIndex = context.callSite->self ? 1 : 2;
|
|
if (params.size() >= 3 && context.callSite->args.size > initIndex)
|
|
context.solver->unify(context.constraint, params[2], optionalNumber);
|
|
|
|
if (params.size() == 4 && context.callSite->args.size > plainIndex)
|
|
context.solver->unify(context.constraint, params[3], optionalBoolean);
|
|
|
|
returnTypes.insert(returnTypes.begin(), {optionalNumber, optionalNumber});
|
|
|
|
const TypePackId returnList = arena->addTypePack(returnTypes);
|
|
asMutable(context.result)->ty.emplace<BoundTypePack>(returnList);
|
|
return true;
|
|
}
|
|
|
|
TypeId makeStringMetatable(NotNull<BuiltinTypes> builtinTypes)
|
|
{
|
|
NotNull<TypeArena> arena{builtinTypes->arena.get()};
|
|
|
|
const TypeId nilType = builtinTypes->nilType;
|
|
const TypeId numberType = builtinTypes->numberType;
|
|
const TypeId booleanType = builtinTypes->booleanType;
|
|
const TypeId stringType = builtinTypes->stringType;
|
|
|
|
const TypeId optionalNumber = arena->addType(UnionType{{nilType, numberType}});
|
|
const TypeId optionalString = arena->addType(UnionType{{nilType, stringType}});
|
|
const TypeId optionalBoolean = arena->addType(UnionType{{nilType, booleanType}});
|
|
|
|
const TypePackId oneStringPack = arena->addTypePack({stringType});
|
|
const TypePackId anyTypePack = builtinTypes->anyTypePack;
|
|
|
|
const TypePackId variadicTailPack = FFlag::DebugLuauDeferredConstraintResolution ? builtinTypes->unknownTypePack : anyTypePack;
|
|
const TypePackId emptyPack = arena->addTypePack({});
|
|
const TypePackId stringVariadicList = arena->addTypePack(TypePackVar{VariadicTypePack{stringType}});
|
|
const TypePackId numberVariadicList = arena->addTypePack(TypePackVar{VariadicTypePack{numberType}});
|
|
|
|
|
|
FunctionType formatFTV{arena->addTypePack(TypePack{{stringType}, variadicTailPack}), oneStringPack};
|
|
formatFTV.magicFunction = &magicFunctionFormat;
|
|
formatFTV.isCheckedFunction = true;
|
|
const TypeId formatFn = arena->addType(formatFTV);
|
|
attachDcrMagicFunction(formatFn, dcrMagicFunctionFormat);
|
|
|
|
|
|
const TypeId stringToStringType = makeFunction(*arena, std::nullopt, {}, {}, {stringType}, {}, {stringType}, /* checked */ true);
|
|
|
|
const TypeId replArgType =
|
|
arena->addType(UnionType{{stringType, arena->addType(TableType({}, TableIndexer(stringType, stringType), TypeLevel{}, TableState::Generic)),
|
|
makeFunction(*arena, std::nullopt, {}, {}, {stringType}, {}, {stringType}, /* checked */ false)}});
|
|
const TypeId gsubFunc =
|
|
makeFunction(*arena, stringType, {}, {}, {stringType, replArgType, optionalNumber}, {}, {stringType, numberType}, /* checked */ false);
|
|
const TypeId gmatchFunc =
|
|
makeFunction(*arena, stringType, {}, {}, {stringType}, {}, {arena->addType(FunctionType{emptyPack, stringVariadicList})}, /* checked */ true);
|
|
attachMagicFunction(gmatchFunc, magicFunctionGmatch);
|
|
attachDcrMagicFunction(gmatchFunc, dcrMagicFunctionGmatch);
|
|
|
|
FunctionType matchFuncTy{
|
|
arena->addTypePack({stringType, stringType, optionalNumber}), arena->addTypePack(TypePackVar{VariadicTypePack{stringType}})};
|
|
matchFuncTy.isCheckedFunction = true;
|
|
const TypeId matchFunc = arena->addType(matchFuncTy);
|
|
attachMagicFunction(matchFunc, magicFunctionMatch);
|
|
attachDcrMagicFunction(matchFunc, dcrMagicFunctionMatch);
|
|
|
|
FunctionType findFuncTy{arena->addTypePack({stringType, stringType, optionalNumber, optionalBoolean}),
|
|
arena->addTypePack(TypePack{{optionalNumber, optionalNumber}, stringVariadicList})};
|
|
findFuncTy.isCheckedFunction = true;
|
|
const TypeId findFunc = arena->addType(findFuncTy);
|
|
attachMagicFunction(findFunc, magicFunctionFind);
|
|
attachDcrMagicFunction(findFunc, dcrMagicFunctionFind);
|
|
|
|
// string.byte : string -> number? -> number? -> ...number
|
|
FunctionType stringDotByte{arena->addTypePack({stringType, optionalNumber, optionalNumber}), numberVariadicList};
|
|
stringDotByte.isCheckedFunction = true;
|
|
|
|
// string.char : .... number -> string
|
|
FunctionType stringDotChar{numberVariadicList, arena->addTypePack({stringType})};
|
|
stringDotChar.isCheckedFunction = true;
|
|
|
|
// string.unpack : string -> string -> number? -> ...any
|
|
FunctionType stringDotUnpack{
|
|
arena->addTypePack(TypePack{{stringType, stringType, optionalNumber}}),
|
|
variadicTailPack,
|
|
};
|
|
stringDotUnpack.isCheckedFunction = true;
|
|
|
|
TableType::Props stringLib = {
|
|
{"byte", {arena->addType(stringDotByte)}},
|
|
{"char", {arena->addType(stringDotChar)}},
|
|
{"find", {findFunc}},
|
|
{"format", {formatFn}}, // FIXME
|
|
{"gmatch", {gmatchFunc}},
|
|
{"gsub", {gsubFunc}},
|
|
{"len", {makeFunction(*arena, stringType, {}, {}, {}, {}, {numberType}, /* checked */ true)}},
|
|
{"lower", {stringToStringType}},
|
|
{"match", {matchFunc}},
|
|
{"rep", {makeFunction(*arena, stringType, {}, {}, {numberType}, {}, {stringType}, /* checked */ true)}},
|
|
{"reverse", {stringToStringType}},
|
|
{"sub", {makeFunction(*arena, stringType, {}, {}, {numberType, optionalNumber}, {}, {stringType}, /* checked */ true)}},
|
|
{"upper", {stringToStringType}},
|
|
{"split", {makeFunction(*arena, stringType, {}, {}, {optionalString}, {},
|
|
{arena->addType(TableType{{}, TableIndexer{numberType, stringType}, TypeLevel{}, TableState::Sealed})},
|
|
/* checked */ true)}},
|
|
{"pack", {arena->addType(FunctionType{
|
|
arena->addTypePack(TypePack{{stringType}, variadicTailPack}),
|
|
oneStringPack,
|
|
})}},
|
|
{"packsize", {makeFunction(*arena, stringType, {}, {}, {}, {}, {numberType}, /* checked */ true)}},
|
|
{"unpack", {arena->addType(stringDotUnpack)}},
|
|
};
|
|
|
|
assignPropDocumentationSymbols(stringLib, "@luau/global/string");
|
|
|
|
TypeId tableType = arena->addType(TableType{std::move(stringLib), std::nullopt, TypeLevel{}, TableState::Sealed});
|
|
|
|
if (TableType* ttv = getMutable<TableType>(tableType))
|
|
ttv->name = "typeof(string)";
|
|
|
|
return arena->addType(TableType{{{{"__index", {tableType}}}}, std::nullopt, TypeLevel{}, TableState::Sealed});
|
|
}
|
|
|
|
static std::optional<WithPredicate<TypePackId>> magicFunctionSelect(
|
|
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate)
|
|
{
|
|
auto [paramPack, _predicates] = withPredicate;
|
|
|
|
(void)scope;
|
|
|
|
if (expr.args.size <= 0)
|
|
{
|
|
typechecker.reportError(TypeError{expr.location, GenericError{"select should take 1 or more arguments"}});
|
|
return std::nullopt;
|
|
}
|
|
|
|
AstExpr* arg1 = expr.args.data[0];
|
|
if (AstExprConstantNumber* num = arg1->as<AstExprConstantNumber>())
|
|
{
|
|
const auto& [v, tail] = flatten(paramPack);
|
|
|
|
int offset = int(num->value);
|
|
if (offset > 0)
|
|
{
|
|
if (size_t(offset) < v.size())
|
|
{
|
|
std::vector<TypeId> result(v.begin() + offset, v.end());
|
|
return WithPredicate<TypePackId>{typechecker.currentModule->internalTypes.addTypePack(TypePack{std::move(result), tail})};
|
|
}
|
|
else if (tail)
|
|
return WithPredicate<TypePackId>{*tail};
|
|
}
|
|
|
|
typechecker.reportError(TypeError{arg1->location, GenericError{"bad argument #1 to select (index out of range)"}});
|
|
}
|
|
else if (AstExprConstantString* str = arg1->as<AstExprConstantString>())
|
|
{
|
|
if (str->value.size == 1 && str->value.data[0] == '#')
|
|
return WithPredicate<TypePackId>{typechecker.currentModule->internalTypes.addTypePack({typechecker.numberType})};
|
|
}
|
|
|
|
return std::nullopt;
|
|
}
|
|
|
|
static bool dcrMagicFunctionSelect(MagicFunctionCallContext context)
|
|
{
|
|
if (context.callSite->args.size <= 0)
|
|
{
|
|
context.solver->reportError(TypeError{context.callSite->location, GenericError{"select should take 1 or more arguments"}});
|
|
return false;
|
|
}
|
|
|
|
AstExpr* arg1 = context.callSite->args.data[0];
|
|
|
|
if (AstExprConstantNumber* num = arg1->as<AstExprConstantNumber>())
|
|
{
|
|
const auto& [v, tail] = flatten(context.arguments);
|
|
|
|
int offset = int(num->value);
|
|
if (offset > 0)
|
|
{
|
|
if (size_t(offset) < v.size())
|
|
{
|
|
std::vector<TypeId> res(v.begin() + offset, v.end());
|
|
TypePackId resTypePack = context.solver->arena->addTypePack({std::move(res), tail});
|
|
asMutable(context.result)->ty.emplace<BoundTypePack>(resTypePack);
|
|
}
|
|
else if (tail)
|
|
asMutable(context.result)->ty.emplace<BoundTypePack>(*tail);
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
if (AstExprConstantString* str = arg1->as<AstExprConstantString>())
|
|
{
|
|
if (str->value.size == 1 && str->value.data[0] == '#')
|
|
{
|
|
TypePackId numberTypePack = context.solver->arena->addTypePack({context.solver->builtinTypes->numberType});
|
|
asMutable(context.result)->ty.emplace<BoundTypePack>(numberTypePack);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static std::optional<WithPredicate<TypePackId>> magicFunctionSetMetaTable(
|
|
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate)
|
|
{
|
|
auto [paramPack, _predicates] = withPredicate;
|
|
|
|
if (size(paramPack) < 2 && finite(paramPack))
|
|
return std::nullopt;
|
|
|
|
TypeArena& arena = typechecker.currentModule->internalTypes;
|
|
|
|
std::vector<TypeId> expectedArgs = typechecker.unTypePack(scope, paramPack, 2, expr.location);
|
|
|
|
TypeId target = follow(expectedArgs[0]);
|
|
TypeId mt = follow(expectedArgs[1]);
|
|
|
|
typechecker.tablify(target);
|
|
typechecker.tablify(mt);
|
|
|
|
if (const auto& tab = get<TableType>(target))
|
|
{
|
|
if (target->persistent)
|
|
{
|
|
typechecker.reportError(TypeError{expr.location, CannotExtendTable{target, CannotExtendTable::Metatable}});
|
|
}
|
|
else
|
|
{
|
|
const TableType* mtTtv = get<TableType>(mt);
|
|
MetatableType mtv{target, mt};
|
|
if ((tab->name || tab->syntheticName) && (mtTtv && (mtTtv->name || mtTtv->syntheticName)))
|
|
{
|
|
std::string tableName = tab->name ? *tab->name : *tab->syntheticName;
|
|
std::string metatableName = mtTtv->name ? *mtTtv->name : *mtTtv->syntheticName;
|
|
|
|
if (tableName == metatableName)
|
|
mtv.syntheticName = tableName;
|
|
}
|
|
|
|
TypeId mtTy = arena.addType(mtv);
|
|
|
|
if (expr.args.size < 1)
|
|
return std::nullopt;
|
|
|
|
if (!expr.self)
|
|
{
|
|
AstExpr* targetExpr = expr.args.data[0];
|
|
if (AstExprLocal* targetLocal = targetExpr->as<AstExprLocal>())
|
|
{
|
|
const Name targetName(targetLocal->local->name.value);
|
|
scope->bindings[targetLocal->local] = Binding{mtTy, expr.location};
|
|
}
|
|
}
|
|
|
|
return WithPredicate<TypePackId>{arena.addTypePack({mtTy})};
|
|
}
|
|
}
|
|
else if (get<AnyType>(target) || get<ErrorType>(target) || isTableIntersection(target))
|
|
{
|
|
}
|
|
else if (isTableUnion(target))
|
|
{
|
|
const UnionType* ut = get<UnionType>(target);
|
|
LUAU_ASSERT(ut);
|
|
|
|
std::vector<TypeId> resultParts;
|
|
|
|
for (TypeId ty : ut)
|
|
resultParts.push_back(arena.addType(MetatableType{ty, mt}));
|
|
|
|
return WithPredicate<TypePackId>{arena.addTypePack({arena.addType(UnionType{std::move(resultParts)})})};
|
|
}
|
|
else
|
|
{
|
|
typechecker.reportError(TypeError{expr.location, GenericError{"setmetatable should take a table"}});
|
|
}
|
|
|
|
return WithPredicate<TypePackId>{arena.addTypePack({target})};
|
|
}
|
|
|
|
static std::optional<WithPredicate<TypePackId>> magicFunctionAssert(
|
|
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate)
|
|
{
|
|
auto [paramPack, predicates] = withPredicate;
|
|
|
|
TypeArena& arena = typechecker.currentModule->internalTypes;
|
|
|
|
auto [head, tail] = flatten(paramPack);
|
|
if (head.empty() && tail)
|
|
{
|
|
std::optional<TypeId> fst = first(*tail);
|
|
if (!fst)
|
|
return WithPredicate<TypePackId>{paramPack};
|
|
head.push_back(*fst);
|
|
}
|
|
|
|
typechecker.resolve(predicates, scope, true);
|
|
|
|
if (head.size() > 0)
|
|
{
|
|
auto [ty, ok] = typechecker.pickTypesFromSense(head[0], true, typechecker.builtinTypes->nilType);
|
|
if (get<NeverType>(*ty))
|
|
head = {*ty};
|
|
else
|
|
head[0] = *ty;
|
|
}
|
|
|
|
return WithPredicate<TypePackId>{arena.addTypePack(TypePack{std::move(head), tail})};
|
|
}
|
|
|
|
static std::optional<WithPredicate<TypePackId>> magicFunctionPack(
|
|
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate)
|
|
{
|
|
auto [paramPack, _predicates] = withPredicate;
|
|
|
|
TypeArena& arena = typechecker.currentModule->internalTypes;
|
|
|
|
const auto& [paramTypes, paramTail] = flatten(paramPack);
|
|
|
|
std::vector<TypeId> options;
|
|
options.reserve(paramTypes.size());
|
|
for (auto type : paramTypes)
|
|
options.push_back(type);
|
|
|
|
if (paramTail)
|
|
{
|
|
if (const VariadicTypePack* vtp = get<VariadicTypePack>(*paramTail))
|
|
options.push_back(vtp->ty);
|
|
}
|
|
|
|
options = reduceUnion(options);
|
|
|
|
// table.pack() -> {| n: number, [number]: nil |}
|
|
// table.pack(1) -> {| n: number, [number]: number |}
|
|
// table.pack(1, "foo") -> {| n: number, [number]: number | string |}
|
|
TypeId result = nullptr;
|
|
if (options.empty())
|
|
result = typechecker.nilType;
|
|
else if (options.size() == 1)
|
|
result = options[0];
|
|
else
|
|
result = arena.addType(UnionType{std::move(options)});
|
|
|
|
TypeId packedTable =
|
|
arena.addType(TableType{{{"n", {typechecker.numberType}}}, TableIndexer(typechecker.numberType, result), scope->level, TableState::Sealed});
|
|
|
|
return WithPredicate<TypePackId>{arena.addTypePack({packedTable})};
|
|
}
|
|
|
|
static bool dcrMagicFunctionPack(MagicFunctionCallContext context)
|
|
{
|
|
|
|
TypeArena* arena = context.solver->arena;
|
|
|
|
const auto& [paramTypes, paramTail] = flatten(context.arguments);
|
|
|
|
std::vector<TypeId> options;
|
|
options.reserve(paramTypes.size());
|
|
for (auto type : paramTypes)
|
|
options.push_back(type);
|
|
|
|
if (paramTail)
|
|
{
|
|
if (const VariadicTypePack* vtp = get<VariadicTypePack>(*paramTail))
|
|
options.push_back(vtp->ty);
|
|
}
|
|
|
|
options = reduceUnion(options);
|
|
|
|
// table.pack() -> {| n: number, [number]: nil |}
|
|
// table.pack(1) -> {| n: number, [number]: number |}
|
|
// table.pack(1, "foo") -> {| n: number, [number]: number | string |}
|
|
TypeId result = nullptr;
|
|
if (options.empty())
|
|
result = context.solver->builtinTypes->nilType;
|
|
else if (options.size() == 1)
|
|
result = options[0];
|
|
else
|
|
result = arena->addType(UnionType{std::move(options)});
|
|
|
|
TypeId numberType = context.solver->builtinTypes->numberType;
|
|
TypeId packedTable = arena->addType(TableType{{{"n", {numberType}}}, TableIndexer(numberType, result), {}, TableState::Sealed});
|
|
|
|
TypePackId tableTypePack = arena->addTypePack({packedTable});
|
|
asMutable(context.result)->ty.emplace<BoundTypePack>(tableTypePack);
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool checkRequirePath(TypeChecker& typechecker, AstExpr* expr)
|
|
{
|
|
// require(foo.parent.bar) will technically work, but it depends on legacy goop that
|
|
// Luau does not and could not support without a bunch of work. It's deprecated anyway, so
|
|
// we'll warn here if we see it.
|
|
bool good = true;
|
|
AstExprIndexName* indexExpr = expr->as<AstExprIndexName>();
|
|
|
|
while (indexExpr)
|
|
{
|
|
if (indexExpr->index == "parent")
|
|
{
|
|
typechecker.reportError(indexExpr->indexLocation, DeprecatedApiUsed{"parent", "Parent"});
|
|
good = false;
|
|
}
|
|
|
|
indexExpr = indexExpr->expr->as<AstExprIndexName>();
|
|
}
|
|
|
|
return good;
|
|
}
|
|
|
|
static std::optional<WithPredicate<TypePackId>> magicFunctionRequire(
|
|
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate)
|
|
{
|
|
TypeArena& arena = typechecker.currentModule->internalTypes;
|
|
|
|
if (expr.args.size != 1)
|
|
{
|
|
typechecker.reportError(TypeError{expr.location, GenericError{"require takes 1 argument"}});
|
|
return std::nullopt;
|
|
}
|
|
|
|
if (!checkRequirePath(typechecker, expr.args.data[0]))
|
|
return std::nullopt;
|
|
|
|
if (auto moduleInfo = typechecker.resolver->resolveModuleInfo(typechecker.currentModule->name, expr))
|
|
return WithPredicate<TypePackId>{arena.addTypePack({typechecker.checkRequire(scope, *moduleInfo, expr.location)})};
|
|
|
|
return std::nullopt;
|
|
}
|
|
|
|
static bool checkRequirePathDcr(NotNull<ConstraintSolver> solver, AstExpr* expr)
|
|
{
|
|
// require(foo.parent.bar) will technically work, but it depends on legacy goop that
|
|
// Luau does not and could not support without a bunch of work. It's deprecated anyway, so
|
|
// we'll warn here if we see it.
|
|
bool good = true;
|
|
AstExprIndexName* indexExpr = expr->as<AstExprIndexName>();
|
|
|
|
while (indexExpr)
|
|
{
|
|
if (indexExpr->index == "parent")
|
|
{
|
|
solver->reportError(DeprecatedApiUsed{"parent", "Parent"}, indexExpr->indexLocation);
|
|
good = false;
|
|
}
|
|
|
|
indexExpr = indexExpr->expr->as<AstExprIndexName>();
|
|
}
|
|
|
|
return good;
|
|
}
|
|
|
|
static bool dcrMagicFunctionRequire(MagicFunctionCallContext context)
|
|
{
|
|
if (context.callSite->args.size != 1)
|
|
{
|
|
context.solver->reportError(GenericError{"require takes 1 argument"}, context.callSite->location);
|
|
return false;
|
|
}
|
|
|
|
if (!checkRequirePathDcr(context.solver, context.callSite->args.data[0]))
|
|
return false;
|
|
|
|
if (auto moduleInfo = context.solver->moduleResolver->resolveModuleInfo(context.solver->currentModuleName, *context.callSite))
|
|
{
|
|
TypeId moduleType = context.solver->resolveModule(*moduleInfo, context.callSite->location);
|
|
TypePackId moduleResult = context.solver->arena->addTypePack({moduleType});
|
|
asMutable(context.result)->ty.emplace<BoundTypePack>(moduleResult);
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
} // namespace Luau
|