mirror of
https://github.com/luau-lang/luau.git
synced 2024-11-15 06:15:44 +08:00
Sync to upstream/release/597 (#1054)
# New Type Solver - Implement bidirectional type inference for higher order functions so that we can provide a more precise type improving the autocomplete's human factors. - We seal all tables, so we changed the stringification to make it a little lighter on users. - Fixed a case of array-out-of-bound access. - Type families no longer depends on `TxnLog` and `Unifier`. - Type refinements now waits until the free types are sufficiently solved. # Native Code Generation - Remove cached slot lookup for `executeSETTABLEKS` function because it is a fallback in the event of a cache miss, making the cached slot lookup redundant. - Optimized repeated array lookups, e.g. `a[3]` in `a[3] = a[3] / 2` is done once. # Misc - On some platforms, it is necessary to use `gmtime_s` with the arguments reversed to get the current time. You can now define `DOCTEST_CONFIG_USE_GMTIME_S` to build and run unit tests on those platforms. --------- Co-authored-by: Arseny Kapoulkine <arseny.kapoulkine@gmail.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Lily Brown <lbrown@roblox.com> Co-authored-by: Aaron Weiss <aaronweiss@roblox.com>
This commit is contained in:
parent
16fbfe912c
commit
1d0b449181
@ -106,6 +106,14 @@ struct ConstraintGraphBuilder
|
||||
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope, DcrLogger* logger, NotNull<DataFlowGraph> dfg,
|
||||
std::vector<RequireCycle> requireCycles);
|
||||
|
||||
/**
|
||||
* The entry point to the ConstraintGraphBuilder. This will construct a set
|
||||
* of scopes, constraints, and free types that can be solved later.
|
||||
* @param block the root block to generate constraints for.
|
||||
*/
|
||||
void visitModuleRoot(AstStatBlock* block);
|
||||
|
||||
private:
|
||||
/**
|
||||
* Fabricates a new free type belonging to a given scope.
|
||||
* @param scope the scope the free type belongs to.
|
||||
@ -143,13 +151,6 @@ struct ConstraintGraphBuilder
|
||||
|
||||
void applyRefinements(const ScopePtr& scope, Location location, RefinementId refinement);
|
||||
|
||||
/**
|
||||
* The entry point to the ConstraintGraphBuilder. This will construct a set
|
||||
* of scopes, constraints, and free types that can be solved later.
|
||||
* @param block the root block to generate constraints for.
|
||||
*/
|
||||
void visit(AstStatBlock* block);
|
||||
|
||||
ControlFlow visitBlockWithoutChildScope(const ScopePtr& scope, AstStatBlock* block);
|
||||
|
||||
ControlFlow visit(const ScopePtr& scope, AstStat* stat);
|
||||
@ -172,7 +173,8 @@ struct ConstraintGraphBuilder
|
||||
ControlFlow visit(const ScopePtr& scope, AstStatError* error);
|
||||
|
||||
InferencePack checkPack(const ScopePtr& scope, AstArray<AstExpr*> exprs, const std::vector<std::optional<TypeId>>& expectedTypes = {});
|
||||
InferencePack checkPack(const ScopePtr& scope, AstExpr* expr, const std::vector<std::optional<TypeId>>& expectedTypes = {});
|
||||
InferencePack checkPack(
|
||||
const ScopePtr& scope, AstExpr* expr, const std::vector<std::optional<TypeId>>& expectedTypes = {}, bool generalize = true);
|
||||
|
||||
InferencePack checkPack(const ScopePtr& scope, AstExprCall* call);
|
||||
|
||||
@ -182,10 +184,11 @@ struct ConstraintGraphBuilder
|
||||
* @param expr the expression to check.
|
||||
* @param expectedType the type of the expression that is expected from its
|
||||
* surrounding context. Used to implement bidirectional type checking.
|
||||
* @param generalize If true, generalize any lambdas that are encountered.
|
||||
* @return the type of the expression.
|
||||
*/
|
||||
Inference check(const ScopePtr& scope, AstExpr* expr, ValueContext context = ValueContext::RValue, std::optional<TypeId> expectedType = {},
|
||||
bool forceSingleton = false);
|
||||
bool forceSingleton = false, bool generalize = true);
|
||||
|
||||
Inference check(const ScopePtr& scope, AstExprConstantString* string, std::optional<TypeId> expectedType, bool forceSingleton);
|
||||
Inference check(const ScopePtr& scope, AstExprConstantBool* bool_, std::optional<TypeId> expectedType, bool forceSingleton);
|
||||
@ -193,7 +196,7 @@ struct ConstraintGraphBuilder
|
||||
Inference check(const ScopePtr& scope, AstExprGlobal* global);
|
||||
Inference check(const ScopePtr& scope, AstExprIndexName* indexName);
|
||||
Inference check(const ScopePtr& scope, AstExprIndexExpr* indexExpr);
|
||||
Inference check(const ScopePtr& scope, AstExprFunction* func, std::optional<TypeId> expectedType);
|
||||
Inference check(const ScopePtr& scope, AstExprFunction* func, std::optional<TypeId> expectedType, bool generalize);
|
||||
Inference check(const ScopePtr& scope, AstExprUnary* unary);
|
||||
Inference check(const ScopePtr& scope, AstExprBinary* binary, std::optional<TypeId> expectedType);
|
||||
Inference check(const ScopePtr& scope, AstExprIfElse* ifElse, std::optional<TypeId> expectedType);
|
||||
|
15
Analysis/include/Luau/NonStrictTypeChecker.h
Normal file
15
Analysis/include/Luau/NonStrictTypeChecker.h
Normal file
@ -0,0 +1,15 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Module.h"
|
||||
#include "Luau/NotNull.h"
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct BuiltinTypes;
|
||||
|
||||
|
||||
void checkNonStrict(NotNull<BuiltinTypes> builtinTypes, Module* module);
|
||||
|
||||
} // namespace Luau
|
@ -8,7 +8,6 @@
|
||||
#include "Luau/TypePack.h"
|
||||
#include "Luau/Unifiable.h"
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauCopyBeforeNormalizing)
|
||||
LUAU_FASTFLAG(DebugLuauReadWriteProperties)
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
@ -253,8 +252,10 @@ private:
|
||||
|
||||
void cloneChildren(FreeType* t)
|
||||
{
|
||||
// TODO: clone lower and upper bounds.
|
||||
// TODO: In the new solver, we should ice.
|
||||
if (t->lowerBound)
|
||||
t->lowerBound = shallowClone(t->lowerBound);
|
||||
if (t->upperBound)
|
||||
t->upperBound = shallowClone(t->upperBound);
|
||||
}
|
||||
|
||||
void cloneChildren(GenericType* t)
|
||||
@ -376,7 +377,11 @@ private:
|
||||
|
||||
void cloneChildren(TypeFamilyInstanceType* t)
|
||||
{
|
||||
// TODO: In the new solver, we should ice.
|
||||
for (TypeId& ty : t->typeArguments)
|
||||
ty = shallowClone(ty);
|
||||
|
||||
for (TypePackId& tp : t->packArguments)
|
||||
tp = shallowClone(tp);
|
||||
}
|
||||
|
||||
void cloneChildren(FreeTypePack* t)
|
||||
@ -416,7 +421,11 @@ private:
|
||||
|
||||
void cloneChildren(TypeFamilyInstanceTypePack* t)
|
||||
{
|
||||
// TODO: In the new solver, we should ice.
|
||||
for (TypeId& ty : t->typeArguments)
|
||||
ty = shallowClone(ty);
|
||||
|
||||
for (TypePackId& tp : t->packArguments)
|
||||
tp = shallowClone(tp);
|
||||
}
|
||||
};
|
||||
|
||||
@ -560,8 +569,6 @@ struct TypePackCloner
|
||||
void operator()(const Unifiable::Bound<TypePackId>& t)
|
||||
{
|
||||
TypePackId cloned = clone(t.boundTo, dest, cloneState);
|
||||
if (FFlag::DebugLuauCopyBeforeNormalizing)
|
||||
cloned = dest.addTypePack(TypePackVar{BoundTypePack{cloned}});
|
||||
seenTypePacks[typePackId] = cloned;
|
||||
}
|
||||
|
||||
@ -629,8 +636,6 @@ void TypeCloner::operator()(const GenericType& t)
|
||||
void TypeCloner::operator()(const Unifiable::Bound<TypeId>& t)
|
||||
{
|
||||
TypeId boundTo = clone(t.boundTo, dest, cloneState);
|
||||
if (FFlag::DebugLuauCopyBeforeNormalizing)
|
||||
boundTo = dest.addType(BoundType{boundTo});
|
||||
seenTypes[typeId] = boundTo;
|
||||
}
|
||||
|
||||
@ -701,7 +706,7 @@ void TypeCloner::operator()(const FunctionType& t)
|
||||
void TypeCloner::operator()(const TableType& t)
|
||||
{
|
||||
// If table is now bound to another one, we ignore the content of the original
|
||||
if (!FFlag::DebugLuauCopyBeforeNormalizing && t.boundTo)
|
||||
if (t.boundTo)
|
||||
{
|
||||
TypeId boundTo = clone(*t.boundTo, dest, cloneState);
|
||||
seenTypes[typeId] = boundTo;
|
||||
@ -718,9 +723,6 @@ void TypeCloner::operator()(const TableType& t)
|
||||
|
||||
ttv->level = TypeLevel{0, 0};
|
||||
|
||||
if (FFlag::DebugLuauCopyBeforeNormalizing && t.boundTo)
|
||||
ttv->boundTo = clone(*t.boundTo, dest, cloneState);
|
||||
|
||||
for (const auto& [name, prop] : t.props)
|
||||
ttv->props[name] = clone(prop, dest, cloneState);
|
||||
|
||||
|
@ -160,6 +160,25 @@ ConstraintGraphBuilder::ConstraintGraphBuilder(ModulePtr module, NotNull<Normali
|
||||
LUAU_ASSERT(module);
|
||||
}
|
||||
|
||||
void ConstraintGraphBuilder::visitModuleRoot(AstStatBlock* block)
|
||||
{
|
||||
LUAU_ASSERT(scopes.empty());
|
||||
LUAU_ASSERT(rootScope == nullptr);
|
||||
ScopePtr scope = std::make_shared<Scope>(globalScope);
|
||||
rootScope = scope.get();
|
||||
scopes.emplace_back(block->location, scope);
|
||||
module->astScopes[block] = NotNull{scope.get()};
|
||||
|
||||
rootScope->returnType = freshTypePack(scope);
|
||||
|
||||
prepopulateGlobalScope(scope, block);
|
||||
|
||||
visitBlockWithoutChildScope(scope, block);
|
||||
|
||||
if (logger)
|
||||
logger->captureGenerationModule(module);
|
||||
}
|
||||
|
||||
TypeId ConstraintGraphBuilder::freshType(const ScopePtr& scope)
|
||||
{
|
||||
return Luau::freshType(arena, builtinTypes, scope.get());
|
||||
@ -444,25 +463,6 @@ void ConstraintGraphBuilder::applyRefinements(const ScopePtr& scope, Location lo
|
||||
addConstraint(scope, location, c);
|
||||
}
|
||||
|
||||
void ConstraintGraphBuilder::visit(AstStatBlock* block)
|
||||
{
|
||||
LUAU_ASSERT(scopes.empty());
|
||||
LUAU_ASSERT(rootScope == nullptr);
|
||||
ScopePtr scope = std::make_shared<Scope>(globalScope);
|
||||
rootScope = scope.get();
|
||||
scopes.emplace_back(block->location, scope);
|
||||
module->astScopes[block] = NotNull{scope.get()};
|
||||
|
||||
rootScope->returnType = freshTypePack(scope);
|
||||
|
||||
prepopulateGlobalScope(scope, block);
|
||||
|
||||
visitBlockWithoutChildScope(scope, block);
|
||||
|
||||
if (logger)
|
||||
logger->captureGenerationModule(module);
|
||||
}
|
||||
|
||||
ControlFlow ConstraintGraphBuilder::visitBlockWithoutChildScope(const ScopePtr& scope, AstStatBlock* block)
|
||||
{
|
||||
RecursionCounter counter{&recursionCount};
|
||||
@ -616,7 +616,7 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocal* l
|
||||
// See the test TypeInfer/infer_locals_with_nil_value. Better flow
|
||||
// awareness should make this obsolete.
|
||||
|
||||
if (!varTypes[i])
|
||||
if (i < varTypes.size() && !varTypes[i])
|
||||
varTypes[i] = freshType(scope);
|
||||
}
|
||||
// Only function calls and vararg expressions can produce packs. All
|
||||
@ -627,7 +627,7 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocal* l
|
||||
if (hasAnnotation)
|
||||
expectedType = varTypes.at(i);
|
||||
|
||||
TypeId exprType = check(scope, value, ValueContext::RValue, expectedType).ty;
|
||||
TypeId exprType = check(scope, value, ValueContext::RValue, expectedType, /*forceSingleton*/ false, /*generalize*/ true).ty;
|
||||
if (i < varTypes.size())
|
||||
{
|
||||
if (varTypes[i])
|
||||
@ -645,7 +645,7 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocal* l
|
||||
if (hasAnnotation)
|
||||
expectedTypes.insert(begin(expectedTypes), begin(varTypes) + i, end(varTypes));
|
||||
|
||||
TypePackId exprPack = checkPack(scope, value, expectedTypes).tp;
|
||||
TypePackId exprPack = checkPack(scope, value, expectedTypes, /*generalize*/ true).tp;
|
||||
|
||||
if (i < local->vars.size)
|
||||
{
|
||||
@ -1380,7 +1380,8 @@ InferencePack ConstraintGraphBuilder::checkPack(
|
||||
return InferencePack{arena->addTypePack(TypePack{std::move(head), tail})};
|
||||
}
|
||||
|
||||
InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExpr* expr, const std::vector<std::optional<TypeId>>& expectedTypes)
|
||||
InferencePack ConstraintGraphBuilder::checkPack(
|
||||
const ScopePtr& scope, AstExpr* expr, const std::vector<std::optional<TypeId>>& expectedTypes, bool generalize)
|
||||
{
|
||||
RecursionCounter counter{&recursionCount};
|
||||
|
||||
@ -1406,7 +1407,7 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExpr*
|
||||
std::optional<TypeId> expectedType;
|
||||
if (!expectedTypes.empty())
|
||||
expectedType = expectedTypes[0];
|
||||
TypeId t = check(scope, expr, ValueContext::RValue, expectedType).ty;
|
||||
TypeId t = check(scope, expr, ValueContext::RValue, expectedType, /*forceSingletons*/ false, generalize).ty;
|
||||
result = InferencePack{arena->addTypePack({t})};
|
||||
}
|
||||
|
||||
@ -1454,51 +1455,25 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCa
|
||||
discriminantTypes.push_back(std::nullopt);
|
||||
}
|
||||
|
||||
Checkpoint startCheckpoint = checkpoint(this);
|
||||
TypeId fnType = check(scope, call->func).ty;
|
||||
Checkpoint fnEndCheckpoint = checkpoint(this);
|
||||
|
||||
std::vector<std::optional<TypeId>> expectedTypesForCall = getExpectedCallTypesForFunctionOverloads(fnType);
|
||||
|
||||
module->astOriginalCallTypes[call->func] = fnType;
|
||||
module->astOriginalCallTypes[call] = fnType;
|
||||
|
||||
TypePackId expectedArgPack = arena->freshTypePack(scope.get());
|
||||
TypePackId expectedRetPack = arena->freshTypePack(scope.get());
|
||||
TypeId expectedFunctionType = arena->addType(FunctionType{expectedArgPack, expectedRetPack, std::nullopt, call->self});
|
||||
|
||||
TypeId instantiatedFnType = arena->addType(BlockedType{});
|
||||
addConstraint(scope, call->location, InstantiationConstraint{instantiatedFnType, fnType});
|
||||
|
||||
NotNull<Constraint> extractArgsConstraint = addConstraint(scope, call->location, SubtypeConstraint{instantiatedFnType, expectedFunctionType});
|
||||
|
||||
// Fully solve fnType, then extract its argument list as expectedArgPack.
|
||||
forEachConstraint(startCheckpoint, fnEndCheckpoint, this, [extractArgsConstraint](const ConstraintPtr& constraint) {
|
||||
extractArgsConstraint->dependencies.emplace_back(constraint.get());
|
||||
});
|
||||
|
||||
const AstExpr* lastArg = exprArgs.size() ? exprArgs[exprArgs.size() - 1] : nullptr;
|
||||
const bool needTail = lastArg && (lastArg->is<AstExprCall>() || lastArg->is<AstExprVarargs>());
|
||||
|
||||
TypePack expectedArgs;
|
||||
|
||||
if (!needTail)
|
||||
expectedArgs = extendTypePack(*arena, builtinTypes, expectedArgPack, exprArgs.size(), expectedTypesForCall);
|
||||
else
|
||||
expectedArgs = extendTypePack(*arena, builtinTypes, expectedArgPack, exprArgs.size() - 1, expectedTypesForCall);
|
||||
Checkpoint argBeginCheckpoint = checkpoint(this);
|
||||
|
||||
std::vector<TypeId> args;
|
||||
std::optional<TypePackId> argTail;
|
||||
std::vector<RefinementId> argumentRefinements;
|
||||
|
||||
Checkpoint argCheckpoint = checkpoint(this);
|
||||
|
||||
for (size_t i = 0; i < exprArgs.size(); ++i)
|
||||
{
|
||||
AstExpr* arg = exprArgs[i];
|
||||
std::optional<TypeId> expectedType;
|
||||
if (i < expectedArgs.head.size())
|
||||
expectedType = expectedArgs.head[i];
|
||||
|
||||
if (i == 0 && call->self)
|
||||
{
|
||||
@ -1514,7 +1489,8 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCa
|
||||
}
|
||||
else if (i < exprArgs.size() - 1 || !(arg->is<AstExprCall>() || arg->is<AstExprVarargs>()))
|
||||
{
|
||||
auto [ty, refinement] = check(scope, arg, ValueContext::RValue, expectedType);
|
||||
auto [ty, refinement] =
|
||||
check(scope, arg, ValueContext::RValue, /*expectedType*/ std::nullopt, /*forceSingleton*/ false, /*generalize*/ false);
|
||||
args.push_back(ty);
|
||||
argumentRefinements.push_back(refinement);
|
||||
}
|
||||
@ -1528,12 +1504,6 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCa
|
||||
|
||||
Checkpoint argEndCheckpoint = checkpoint(this);
|
||||
|
||||
// Do not solve argument constraints until after we have extracted the
|
||||
// expected types from the callable.
|
||||
forEachConstraint(argCheckpoint, argEndCheckpoint, this, [extractArgsConstraint](const ConstraintPtr& constraint) {
|
||||
constraint->dependencies.push_back(extractArgsConstraint);
|
||||
});
|
||||
|
||||
if (matchSetmetatable(*call))
|
||||
{
|
||||
TypePack argTailPack;
|
||||
@ -1609,8 +1579,8 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCa
|
||||
// This ensures, for instance, that we start inferring the contents of
|
||||
// lambdas under the assumption that their arguments and return types
|
||||
// will be compatible with the enclosing function call.
|
||||
forEachConstraint(fnEndCheckpoint, argEndCheckpoint, this, [fcc](const ConstraintPtr& constraint) {
|
||||
fcc->dependencies.emplace_back(constraint.get());
|
||||
forEachConstraint(argBeginCheckpoint, argEndCheckpoint, this, [fcc](const ConstraintPtr& constraint) {
|
||||
constraint->dependencies.emplace_back(fcc);
|
||||
});
|
||||
|
||||
return InferencePack{rets, {refinementArena.variadic(returnRefinements)}};
|
||||
@ -1618,7 +1588,7 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCa
|
||||
}
|
||||
|
||||
Inference ConstraintGraphBuilder::check(
|
||||
const ScopePtr& scope, AstExpr* expr, ValueContext context, std::optional<TypeId> expectedType, bool forceSingleton)
|
||||
const ScopePtr& scope, AstExpr* expr, ValueContext context, std::optional<TypeId> expectedType, bool forceSingleton, bool generalize)
|
||||
{
|
||||
RecursionCounter counter{&recursionCount};
|
||||
|
||||
@ -1649,7 +1619,7 @@ Inference ConstraintGraphBuilder::check(
|
||||
else if (auto call = expr->as<AstExprCall>())
|
||||
result = flattenPack(scope, expr->location, checkPack(scope, call)); // TODO: needs predicates too
|
||||
else if (auto a = expr->as<AstExprFunction>())
|
||||
result = check(scope, a, expectedType);
|
||||
result = check(scope, a, expectedType, generalize);
|
||||
else if (auto indexName = expr->as<AstExprIndexName>())
|
||||
result = check(scope, indexName);
|
||||
else if (auto indexExpr = expr->as<AstExprIndexExpr>())
|
||||
@ -1817,13 +1787,15 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIndexExpr*
|
||||
return Inference{result};
|
||||
}
|
||||
|
||||
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprFunction* func, std::optional<TypeId> expectedType)
|
||||
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprFunction* func, std::optional<TypeId> expectedType, bool generalize)
|
||||
{
|
||||
Checkpoint startCheckpoint = checkpoint(this);
|
||||
FunctionSignature sig = checkFunctionSignature(scope, func, expectedType);
|
||||
checkFunctionBody(sig.bodyScope, func);
|
||||
Checkpoint endCheckpoint = checkpoint(this);
|
||||
|
||||
if (generalize)
|
||||
{
|
||||
TypeId generalizedTy = arena->addType(BlockedType{});
|
||||
NotNull<Constraint> gc = addConstraint(sig.signatureScope, func->location, GeneralizationConstraint{generalizedTy, sig.signature});
|
||||
|
||||
@ -1842,6 +1814,11 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprFunction*
|
||||
|
||||
return Inference{generalizedTy};
|
||||
}
|
||||
else
|
||||
{
|
||||
return Inference{sig.signature};
|
||||
}
|
||||
}
|
||||
|
||||
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprUnary* unary)
|
||||
{
|
||||
@ -2381,10 +2358,11 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS
|
||||
argTy = resolveType(signatureScope, local->annotation, /* inTypeArguments */ false, /* replaceErrorWithFresh*/ true);
|
||||
else
|
||||
{
|
||||
argTy = freshType(signatureScope);
|
||||
|
||||
if (i < expectedArgPack.head.size())
|
||||
addConstraint(signatureScope, local->location, SubtypeConstraint{argTy, expectedArgPack.head[i]});
|
||||
argTy = expectedArgPack.head[i];
|
||||
else
|
||||
argTy = freshType(signatureScope);
|
||||
}
|
||||
|
||||
argTypes.push_back(argTy);
|
||||
|
@ -1380,6 +1380,44 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
|
||||
*asMutable(follow(*ty)) = BoundType{builtinTypes->anyType};
|
||||
}
|
||||
|
||||
// We know the type of the function and the arguments it expects to receive.
|
||||
// We also know the TypeIds of the actual arguments that will be passed.
|
||||
//
|
||||
// Bidirectional type checking: Force those TypeIds to be the expected
|
||||
// arguments. If something is incoherent, we'll spot it in type checking.
|
||||
//
|
||||
// Most important detail: If a function argument is a lambda, we also want
|
||||
// to force unannotated argument types of that lambda to be the expected
|
||||
// types.
|
||||
|
||||
// FIXME: Bidirectional type checking of overloaded functions is not yet supported.
|
||||
if (auto ftv = get<FunctionType>(fn))
|
||||
{
|
||||
const std::vector<TypeId> expectedArgs = flatten(ftv->argTypes).first;
|
||||
const std::vector<TypeId> argPackHead = flatten(argsPack).first;
|
||||
|
||||
for (size_t i = 0; i < c.callSite->args.size && i < expectedArgs.size() && i < argPackHead.size(); ++i)
|
||||
{
|
||||
const FunctionType* expectedLambdaTy = get<FunctionType>(follow(expectedArgs[i]));
|
||||
const FunctionType* lambdaTy = get<FunctionType>(follow(argPackHead[i]));
|
||||
const AstExprFunction* lambdaExpr = c.callSite->args.data[i]->as<AstExprFunction>();
|
||||
|
||||
if (expectedLambdaTy && lambdaTy && lambdaExpr)
|
||||
{
|
||||
const std::vector<TypeId> expectedLambdaArgTys = flatten(expectedLambdaTy->argTypes).first;
|
||||
const std::vector<TypeId> lambdaArgTys = flatten(lambdaTy->argTypes).first;
|
||||
|
||||
for (size_t j = 0; j < expectedLambdaArgTys.size() && j < lambdaArgTys.size() && j < lambdaExpr->args.size; ++j)
|
||||
{
|
||||
if (!lambdaExpr->args.data[j]->annotation && get<FreeType>(follow(lambdaArgTys[j])))
|
||||
{
|
||||
asMutable(lambdaArgTys[j])->ty.emplace<BoundType>(expectedLambdaArgTys[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TypeId inferredTy = arena->addType(FunctionType{TypeLevel{}, constraint->scope.get(), argsPack, c.result});
|
||||
Unifier2 u2{NotNull{arena}, builtinTypes, constraint->scope, NotNull{&iceReporter}};
|
||||
|
||||
|
@ -37,6 +37,7 @@ LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauReadWriteProperties, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTypecheckLimitControls, false)
|
||||
LUAU_FASTFLAGVARIABLE(CorrectEarlyReturnInMarkDirty, false)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauNewNonStrictMode, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -1257,7 +1258,7 @@ ModulePtr check(const SourceModule& sourceModule, const std::vector<RequireCycle
|
||||
ConstraintGraphBuilder cgb{result, NotNull{&normalizer}, moduleResolver, builtinTypes, iceHandler, parentScope, std::move(prepareModuleScope),
|
||||
logger.get(), NotNull{&dfg}, requireCycles};
|
||||
|
||||
cgb.visit(sourceModule.root);
|
||||
cgb.visitModuleRoot(sourceModule.root);
|
||||
result->errors = std::move(cgb.errors);
|
||||
|
||||
ConstraintSolver cs{NotNull{&normalizer}, NotNull(cgb.rootScope), borrowConstraints(cgb.constraints), result->humanReadableName, moduleResolver,
|
||||
|
87
Analysis/src/NonStrictTypeChecker.cpp
Normal file
87
Analysis/src/NonStrictTypeChecker.cpp
Normal file
@ -0,0 +1,87 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/NonStrictTypeChecker.h"
|
||||
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/Subtyping.h"
|
||||
#include "Luau/Normalize.h"
|
||||
#include "Luau/Error.h"
|
||||
#include "Luau/TypeArena.h"
|
||||
#include "Luau/Def.h"
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct NonStrictContext
|
||||
{
|
||||
std::unordered_map<DefId, TypeId> context;
|
||||
|
||||
NonStrictContext() = default;
|
||||
|
||||
NonStrictContext(const NonStrictContext&) = delete;
|
||||
NonStrictContext& operator=(const NonStrictContext&) = delete;
|
||||
|
||||
NonStrictContext(NonStrictContext&&) = default;
|
||||
NonStrictContext& operator=(NonStrictContext&&) = default;
|
||||
|
||||
void unionContexts(const NonStrictContext& other)
|
||||
{
|
||||
// TODO: unimplemented
|
||||
}
|
||||
|
||||
void intersectContexts(const NonStrictContext& other)
|
||||
{
|
||||
// TODO: unimplemented
|
||||
}
|
||||
|
||||
void removeFromContext(const std::vector<DefId>& defs)
|
||||
{
|
||||
// TODO: unimplemented
|
||||
}
|
||||
|
||||
std::optional<TypeId> find(const DefId& def)
|
||||
{
|
||||
// TODO: unimplemented
|
||||
return {};
|
||||
}
|
||||
|
||||
// Satisfies means that for a given DefId n, and an actual type t for `n`, t satisfies the context if t <: context[n]
|
||||
// ice if the DefId is not in the context
|
||||
bool satisfies(const DefId& def, TypeId inferredType)
|
||||
{
|
||||
// TODO: unimplemented
|
||||
return false;
|
||||
}
|
||||
|
||||
bool willRunTimeError(const DefId& def, TypeId inferredType)
|
||||
{
|
||||
return satisfies(def, inferredType);
|
||||
}
|
||||
};
|
||||
|
||||
struct NonStrictTypeChecker
|
||||
{
|
||||
|
||||
NotNull<BuiltinTypes> builtinTypes;
|
||||
const NotNull<InternalErrorReporter> ice;
|
||||
TypeArena arena;
|
||||
Module* module;
|
||||
Normalizer normalizer;
|
||||
Subtyping subtyping;
|
||||
|
||||
|
||||
NonStrictTypeChecker(NotNull<BuiltinTypes> builtinTypes, Subtyping subtyping, const NotNull<InternalErrorReporter> ice,
|
||||
NotNull<UnifierSharedState> unifierState, Module* module)
|
||||
: builtinTypes(builtinTypes)
|
||||
, ice(ice)
|
||||
, module(module)
|
||||
, normalizer{&arena, builtinTypes, unifierState, /* cache inhabitance */ true}
|
||||
, subtyping{builtinTypes, NotNull{&arena}, NotNull(&normalizer), ice, NotNull{module->getModuleScope().get()}}
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
void checkNonStrict(NotNull<BuiltinTypes> builtinTypes, Module* module)
|
||||
{
|
||||
// TODO: unimplemented
|
||||
}
|
||||
} // namespace Luau
|
@ -11,7 +11,6 @@
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/Unifier.h"
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauCopyBeforeNormalizing, false)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauCheckNormalizeInvariant, false)
|
||||
|
||||
// This could theoretically be 2000 on amd64, but x86 requires this.
|
||||
|
@ -696,16 +696,33 @@ struct TypeStringifier
|
||||
|
||||
std::string openbrace = "@@@";
|
||||
std::string closedbrace = "@@@?!";
|
||||
switch (state.opts.hideTableKind ? TableState::Unsealed : ttv.state)
|
||||
switch (state.opts.hideTableKind ? (FFlag::DebugLuauDeferredConstraintResolution ? TableState::Sealed : TableState::Unsealed) : ttv.state)
|
||||
{
|
||||
case TableState::Sealed:
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
openbrace = "{";
|
||||
closedbrace = "}";
|
||||
}
|
||||
else
|
||||
{
|
||||
state.result.invalid = true;
|
||||
openbrace = "{|";
|
||||
closedbrace = "|}";
|
||||
}
|
||||
break;
|
||||
case TableState::Unsealed:
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
state.result.invalid = true;
|
||||
openbrace = "{|";
|
||||
closedbrace = "|}";
|
||||
}
|
||||
else
|
||||
{
|
||||
openbrace = "{";
|
||||
closedbrace = "}";
|
||||
}
|
||||
break;
|
||||
case TableState::Free:
|
||||
state.result.invalid = true;
|
||||
|
@ -320,14 +320,26 @@ bool Unifier2::unify(TypePackId subTp, TypePackId superTp)
|
||||
for (size_t i = 0; i < maxLength; ++i)
|
||||
unify(subTypes[i], superTypes[i]);
|
||||
|
||||
if (!subTail || !superTail)
|
||||
return true;
|
||||
|
||||
if (subTail && superTail)
|
||||
{
|
||||
TypePackId followedSubTail = follow(*subTail);
|
||||
TypePackId followedSuperTail = follow(*superTail);
|
||||
|
||||
if (get<FreeTypePack>(followedSubTail) || get<FreeTypePack>(followedSuperTail))
|
||||
return unify(followedSubTail, followedSuperTail);
|
||||
}
|
||||
else if (subTail)
|
||||
{
|
||||
TypePackId followedSubTail = follow(*subTail);
|
||||
if (get<FreeTypePack>(followedSubTail))
|
||||
asMutable(followedSubTail)->ty.emplace<BoundTypePack>(builtinTypes->emptyTypePack);
|
||||
}
|
||||
else if (superTail)
|
||||
{
|
||||
TypePackId followedSuperTail = follow(*superTail);
|
||||
if (get<FreeTypePack>(followedSuperTail))
|
||||
asMutable(followedSuperTail)->ty.emplace<BoundTypePack>(builtinTypes->emptyTypePack);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -582,13 +594,6 @@ struct MutatingGeneralizer : TypeOnceVisitor
|
||||
TableType* tt = getMutable<TableType>(ty);
|
||||
LUAU_ASSERT(tt);
|
||||
|
||||
// We only unseal tables if they occur within function argument or
|
||||
// return lists. In principle, we could always seal tables when
|
||||
// generalizing, but that would mean that we'd lose the ability to
|
||||
// report the existence of unsealed tables via things like hovertype.
|
||||
if (tt->state == TableState::Unsealed && !isWithinFunction)
|
||||
return true;
|
||||
|
||||
tt->state = TableState::Sealed;
|
||||
|
||||
return true;
|
||||
@ -611,10 +616,7 @@ std::optional<TypeId> Unifier2::generalize(TypeId ty)
|
||||
{
|
||||
ty = follow(ty);
|
||||
|
||||
if (ty->owningArena != arena)
|
||||
return ty;
|
||||
|
||||
if (ty->persistent)
|
||||
if (ty->owningArena != arena || ty->persistent)
|
||||
return ty;
|
||||
|
||||
if (const FunctionType* ft = get<FunctionType>(ty); ft && (!ft->generics.empty() || !ft->genericPacks.empty()))
|
||||
@ -627,16 +629,23 @@ std::optional<TypeId> Unifier2::generalize(TypeId ty)
|
||||
|
||||
gen.traverse(ty);
|
||||
|
||||
std::optional<TypeId> res = ty;
|
||||
/* MutatingGeneralizer mutates types in place, so it is possible that ty has
|
||||
* been transmuted to a BoundType. We must follow it again and verify that
|
||||
* we are allowed to mutate it before we attach generics to it.
|
||||
*/
|
||||
ty = follow(ty);
|
||||
|
||||
FunctionType* ftv = getMutable<FunctionType>(follow(*res));
|
||||
if (ty->owningArena != arena || ty->persistent)
|
||||
return ty;
|
||||
|
||||
FunctionType* ftv = getMutable<FunctionType>(ty);
|
||||
if (ftv)
|
||||
{
|
||||
ftv->generics = std::move(gen.generics);
|
||||
ftv->genericPacks = std::move(gen.genericPacks);
|
||||
}
|
||||
|
||||
return res;
|
||||
return ty;
|
||||
}
|
||||
|
||||
TypeId Unifier2::mkUnion(TypeId left, TypeId right)
|
||||
|
@ -477,17 +477,9 @@ const Instruction* executeSETTABLEKS(lua_State* L, const Instruction* pc, StkId
|
||||
{
|
||||
Table* h = hvalue(rb);
|
||||
|
||||
int slot = LUAU_INSN_C(insn) & h->nodemask8;
|
||||
LuaNode* n = &h->node[slot];
|
||||
// we ignore the fast path that checks for the cached slot since IrTranslation already checks for it.
|
||||
|
||||
// fast-path: value is in expected slot
|
||||
if (LUAU_LIKELY(ttisstring(gkey(n)) && tsvalue(gkey(n)) == tsvalue(kv) && !ttisnil(gval(n)) && !h->readonly))
|
||||
{
|
||||
setobj2t(L, gval(n), ra);
|
||||
luaC_barriert(L, h, ra);
|
||||
return pc;
|
||||
}
|
||||
else if (fastnotm(h->metatable, TM_NEWINDEX) && !h->readonly)
|
||||
if (fastnotm(h->metatable, TM_NEWINDEX) && !h->readonly)
|
||||
{
|
||||
VM_PROTECT_PC(); // set may fail
|
||||
|
||||
@ -502,6 +494,7 @@ const Instruction* executeSETTABLEKS(lua_State* L, const Instruction* pc, StkId
|
||||
else
|
||||
{
|
||||
// slow-path, may invoke Lua calls via __newindex metamethod
|
||||
int slot = LUAU_INSN_C(insn) & h->nodemask8;
|
||||
L->cachedslot = slot;
|
||||
VM_PROTECT(luaV_settable(L, rb, kv, ra));
|
||||
// save cachedslot to accelerate future lookups; patches currently executing instruction since pc-2 rolls back two pc++
|
||||
|
@ -18,6 +18,7 @@ LUAU_FASTFLAGVARIABLE(DebugLuauAbortingChecks, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauReuseHashSlots2, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauKeepVmapLinear, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauMergeTagLoads, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauReuseArrSlots, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -174,14 +175,32 @@ struct ConstPropState
|
||||
}
|
||||
}
|
||||
|
||||
// Value propagation extends the live range of an SSA register
|
||||
// In some cases we can't propagate earlier values because we can't guarantee that we will be able to find a storage/restore location
|
||||
// As an example, when Luau call is performed, both volatile registers and stack slots might be overwritten
|
||||
void invalidateValuePropagation()
|
||||
{
|
||||
valueMap.clear();
|
||||
tryNumToIndexCache.clear();
|
||||
}
|
||||
|
||||
// If table memory has changed, we can't reuse previously computed and validated table slot lookups
|
||||
// Same goes for table array elements as well
|
||||
void invalidateHeapTableData()
|
||||
{
|
||||
getSlotNodeCache.clear();
|
||||
checkSlotMatchCache.clear();
|
||||
|
||||
getArrAddrCache.clear();
|
||||
checkArraySizeCache.clear();
|
||||
}
|
||||
|
||||
void invalidateHeap()
|
||||
{
|
||||
for (int i = 0; i <= maxReg; ++i)
|
||||
invalidateHeap(regs[i]);
|
||||
|
||||
// If table memory has changed, we can't reuse previously computed and validated table slot lookups
|
||||
getSlotNodeCache.clear();
|
||||
checkSlotMatchCache.clear();
|
||||
invalidateHeapTableData();
|
||||
}
|
||||
|
||||
void invalidateHeap(RegisterInfo& reg)
|
||||
@ -203,9 +222,7 @@ struct ConstPropState
|
||||
for (int i = 0; i <= maxReg; ++i)
|
||||
invalidateTableArraySize(regs[i]);
|
||||
|
||||
// If table memory has changed, we can't reuse previously computed and validated table slot lookups
|
||||
getSlotNodeCache.clear();
|
||||
checkSlotMatchCache.clear();
|
||||
invalidateHeapTableData();
|
||||
}
|
||||
|
||||
void invalidateTableArraySize(RegisterInfo& reg)
|
||||
@ -389,9 +406,9 @@ struct ConstPropState
|
||||
checkedGc = false;
|
||||
|
||||
instLink.clear();
|
||||
valueMap.clear();
|
||||
getSlotNodeCache.clear();
|
||||
checkSlotMatchCache.clear();
|
||||
|
||||
invalidateValuePropagation();
|
||||
invalidateHeapTableData();
|
||||
}
|
||||
|
||||
IrFunction& function;
|
||||
@ -410,8 +427,15 @@ struct ConstPropState
|
||||
|
||||
DenseHashMap<IrInst, uint32_t, IrInstHash, IrInstEq> valueMap;
|
||||
|
||||
std::vector<uint32_t> getSlotNodeCache;
|
||||
std::vector<uint32_t> checkSlotMatchCache;
|
||||
// Some instruction re-uses can't be stored in valueMap because of extra requirements
|
||||
std::vector<uint32_t> tryNumToIndexCache; // Fallback block argument might be different
|
||||
|
||||
// Heap changes might affect table state
|
||||
std::vector<uint32_t> getSlotNodeCache; // Additionally, pcpos argument might be different
|
||||
std::vector<uint32_t> checkSlotMatchCache; // Additionally, fallback block argument might be different
|
||||
|
||||
std::vector<uint32_t> getArrAddrCache;
|
||||
std::vector<uint32_t> checkArraySizeCache; // Additionally, fallback block argument might be different
|
||||
};
|
||||
|
||||
static void handleBuiltinEffects(ConstPropState& state, LuauBuiltinFunction bfid, uint32_t firstReturnReg, int nresults)
|
||||
@ -873,7 +897,24 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
|
||||
// These instructions don't have an effect on register/memory state we are tracking
|
||||
case IrCmd::NOP:
|
||||
case IrCmd::LOAD_ENV:
|
||||
break;
|
||||
case IrCmd::GET_ARR_ADDR:
|
||||
if (!FFlag::LuauReuseArrSlots)
|
||||
break;
|
||||
|
||||
for (uint32_t prevIdx : state.getArrAddrCache)
|
||||
{
|
||||
const IrInst& prev = function.instructions[prevIdx];
|
||||
|
||||
if (prev.a == inst.a && prev.b == inst.b)
|
||||
{
|
||||
substitute(function, inst, IrOp{IrOpKind::Inst, prevIdx});
|
||||
return; // Break out from both the loop and the switch
|
||||
}
|
||||
}
|
||||
|
||||
if (int(state.getArrAddrCache.size()) < FInt::LuauCodeGenReuseSlotLimit)
|
||||
state.getArrAddrCache.push_back(index);
|
||||
break;
|
||||
case IrCmd::GET_SLOT_NODE_ADDR:
|
||||
if (!FFlag::LuauReuseHashSlots2)
|
||||
@ -929,7 +970,25 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
|
||||
case IrCmd::STRING_LEN:
|
||||
case IrCmd::NEW_TABLE:
|
||||
case IrCmd::DUP_TABLE:
|
||||
break;
|
||||
case IrCmd::TRY_NUM_TO_INDEX:
|
||||
if (!FFlag::LuauReuseArrSlots)
|
||||
break;
|
||||
|
||||
for (uint32_t prevIdx : state.tryNumToIndexCache)
|
||||
{
|
||||
const IrInst& prev = function.instructions[prevIdx];
|
||||
|
||||
if (prev.a == inst.a)
|
||||
{
|
||||
substitute(function, inst, IrOp{IrOpKind::Inst, prevIdx});
|
||||
return; // Break out from both the loop and the switch
|
||||
}
|
||||
}
|
||||
|
||||
if (int(state.tryNumToIndexCache.size()) < FInt::LuauCodeGenReuseSlotLimit)
|
||||
state.tryNumToIndexCache.push_back(index);
|
||||
break;
|
||||
case IrCmd::TRY_CALL_FASTGETTM:
|
||||
break;
|
||||
case IrCmd::INT_TO_NUM:
|
||||
@ -967,8 +1026,42 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
|
||||
{
|
||||
replace(function, block, index, {IrCmd::JUMP, inst.c});
|
||||
}
|
||||
|
||||
return; // Break out from both the loop and the switch
|
||||
}
|
||||
}
|
||||
|
||||
if (!FFlag::LuauReuseArrSlots)
|
||||
break;
|
||||
|
||||
for (uint32_t prevIdx : state.checkArraySizeCache)
|
||||
{
|
||||
const IrInst& prev = function.instructions[prevIdx];
|
||||
|
||||
if (prev.a != inst.a)
|
||||
continue;
|
||||
|
||||
bool sameBoundary = prev.b == inst.b;
|
||||
|
||||
// If arguments are different, in case they are both constant, we can check if a larger bound was already tested
|
||||
if (!sameBoundary && inst.b.kind == IrOpKind::Constant && prev.b.kind == IrOpKind::Constant &&
|
||||
function.intOp(inst.b) < function.intOp(prev.b))
|
||||
sameBoundary = true;
|
||||
|
||||
if (sameBoundary)
|
||||
{
|
||||
if (FFlag::DebugLuauAbortingChecks)
|
||||
replace(function, inst.c, build.undef());
|
||||
else
|
||||
kill(function, inst);
|
||||
return; // Break out from both the loop and the switch
|
||||
}
|
||||
|
||||
// TODO: it should be possible to update previous check with a higher bound if current and previous checks are against a constant
|
||||
}
|
||||
|
||||
if (int(state.checkArraySizeCache.size()) < FInt::LuauCodeGenReuseSlotLimit)
|
||||
state.checkArraySizeCache.push_back(index);
|
||||
break;
|
||||
}
|
||||
case IrCmd::CHECK_SLOT_MATCH:
|
||||
@ -1053,9 +1146,8 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
|
||||
replace(function, inst.f, build.constUint(info->knownTableArraySize));
|
||||
|
||||
// TODO: this can be relaxed when x64 emitInstSetList becomes aware of register allocator
|
||||
state.valueMap.clear();
|
||||
state.getSlotNodeCache.clear();
|
||||
state.checkSlotMatchCache.clear();
|
||||
state.invalidateValuePropagation();
|
||||
state.invalidateHeapTableData();
|
||||
break;
|
||||
case IrCmd::CALL:
|
||||
state.invalidateRegistersFrom(vmRegOp(inst.a));
|
||||
@ -1064,15 +1156,14 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
|
||||
// We cannot guarantee right now that all live values can be rematerialized from non-stack memory locations
|
||||
// To prevent earlier values from being propagated to after the call, we have to clear the map
|
||||
// TODO: remove only the values that don't have a guaranteed restore location
|
||||
state.valueMap.clear();
|
||||
state.invalidateValuePropagation();
|
||||
break;
|
||||
case IrCmd::FORGLOOP:
|
||||
state.invalidateRegistersFrom(vmRegOp(inst.a) + 2); // Rn and Rn+1 are not modified
|
||||
|
||||
// TODO: this can be relaxed when x64 emitInstForGLoop becomes aware of register allocator
|
||||
state.valueMap.clear();
|
||||
state.getSlotNodeCache.clear();
|
||||
state.checkSlotMatchCache.clear();
|
||||
state.invalidateValuePropagation();
|
||||
state.invalidateHeapTableData();
|
||||
break;
|
||||
case IrCmd::FORGLOOP_FALLBACK:
|
||||
state.invalidateRegistersFrom(vmRegOp(inst.a) + 2); // Rn and Rn+1 are not modified
|
||||
@ -1139,11 +1230,10 @@ static void constPropInBlock(IrBuilder& build, IrBlock& block, ConstPropState& s
|
||||
if (!FFlag::LuauKeepVmapLinear)
|
||||
{
|
||||
// Value numbering and load/store propagation is not performed between blocks
|
||||
state.valueMap.clear();
|
||||
state.invalidateValuePropagation();
|
||||
|
||||
// Same for table slot data propagation
|
||||
state.getSlotNodeCache.clear();
|
||||
state.checkSlotMatchCache.clear();
|
||||
state.invalidateHeapTableData();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1168,11 +1258,10 @@ static void constPropInBlockChain(IrBuilder& build, std::vector<uint8_t>& visite
|
||||
{
|
||||
// Value numbering and load/store propagation is not performed between blocks right now
|
||||
// This is because cross-block value uses limit creation of linear block (restriction in collectDirectBlockJumpPath)
|
||||
state.valueMap.clear();
|
||||
state.invalidateValuePropagation();
|
||||
|
||||
// Same for table slot data propagation
|
||||
state.getSlotNodeCache.clear();
|
||||
state.checkSlotMatchCache.clear();
|
||||
state.invalidateHeapTableData();
|
||||
}
|
||||
|
||||
// Blocks in a chain are guaranteed to follow each other
|
||||
|
@ -178,6 +178,7 @@ target_sources(Luau.Analysis PRIVATE
|
||||
Analysis/include/Luau/Metamethods.h
|
||||
Analysis/include/Luau/Module.h
|
||||
Analysis/include/Luau/ModuleResolver.h
|
||||
Analysis/include/Luau/NonStrictTypeChecker.h
|
||||
Analysis/include/Luau/Normalize.h
|
||||
Analysis/include/Luau/Predicate.h
|
||||
Analysis/include/Luau/Quantify.h
|
||||
@ -235,6 +236,7 @@ target_sources(Luau.Analysis PRIVATE
|
||||
Analysis/src/Linter.cpp
|
||||
Analysis/src/LValue.cpp
|
||||
Analysis/src/Module.cpp
|
||||
Analysis/src/NonStrictTypeChecker.cpp
|
||||
Analysis/src/Normalize.cpp
|
||||
Analysis/src/Quantify.cpp
|
||||
Analysis/src/Refinement.cpp
|
||||
@ -398,6 +400,7 @@ if(TARGET Luau.UnitTest)
|
||||
tests/LValue.test.cpp
|
||||
tests/Module.test.cpp
|
||||
tests/NonstrictMode.test.cpp
|
||||
tests/NonStrictTypeChecker.test.cpp
|
||||
tests/Normalize.test.cpp
|
||||
tests/NotNull.test.cpp
|
||||
tests/Parser.test.cpp
|
||||
|
@ -22,27 +22,6 @@ static time_t timegm(struct tm* timep)
|
||||
{
|
||||
return _mkgmtime(timep);
|
||||
}
|
||||
#elif defined(__FreeBSD__)
|
||||
static tm* gmtime_r(const time_t* timep, tm* result)
|
||||
{
|
||||
// Note: return is reversed from Windows (0 is success on Windows, but 0/null is failure elsewhere)
|
||||
// Windows https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/gmtime-s-gmtime32-s-gmtime64-s?view=msvc-170#return-value
|
||||
// Everyone else https://en.cppreference.com/w/c/chrono/gmtime
|
||||
return gmtime_s(timep, result);
|
||||
}
|
||||
|
||||
static tm* localtime_r(const time_t* timep, tm* result)
|
||||
{
|
||||
// Note: return is reversed from Windows (0 is success on Windows, but 0/null is failure elsewhere)
|
||||
// Windows https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/localtime-s-localtime32-s-localtime64-s?view=msvc-170#return-value
|
||||
// Everyone else https://en.cppreference.com/w/c/chrono/localtime
|
||||
return localtime_s(timep, result);
|
||||
}
|
||||
|
||||
static time_t timegm(struct tm* timep)
|
||||
{
|
||||
return mktime(timep);
|
||||
}
|
||||
#endif
|
||||
|
||||
static int os_clock(lua_State* L)
|
||||
|
6
extern/doctest.h
vendored
6
extern/doctest.h
vendored
@ -3139,7 +3139,11 @@ DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN
|
||||
#include <unordered_set>
|
||||
#include <exception>
|
||||
#include <stdexcept>
|
||||
|
||||
#if !defined(DOCTEST_CONFIG_NO_POSIX_SIGNALS)
|
||||
#include <csignal>
|
||||
#endif
|
||||
|
||||
#include <cfloat>
|
||||
#include <cctype>
|
||||
#include <cstdint>
|
||||
@ -5667,6 +5671,8 @@ namespace {
|
||||
std::tm timeInfo;
|
||||
#ifdef DOCTEST_PLATFORM_WINDOWS
|
||||
gmtime_s(&timeInfo, &rawtime);
|
||||
#elif defined(DOCTEST_CONFIG_USE_GMTIME_S)
|
||||
gmtime_s(&rawtime, &timeInfo);
|
||||
#else // DOCTEST_PLATFORM_WINDOWS
|
||||
gmtime_r(&rawtime, &timeInfo);
|
||||
#endif // DOCTEST_PLATFORM_WINDOWS
|
||||
|
@ -2162,6 +2162,9 @@ local fp: @1= f
|
||||
|
||||
auto ac = autocomplete('1');
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
REQUIRE_EQ("({ x: number, y: number }) -> number", toString(requireType("f")));
|
||||
else
|
||||
REQUIRE_EQ("({| x: number, y: number |}) -> number", toString(requireType("f")));
|
||||
CHECK(ac.entryMap.count("({ x: number, y: number }) -> number"));
|
||||
}
|
||||
|
@ -274,6 +274,9 @@ constexpr X64::RegisterX64 rNonVol4 = X64::r14;
|
||||
|
||||
TEST_CASE("GeneratedCodeExecutionX64")
|
||||
{
|
||||
if (!Luau::CodeGen::isSupported())
|
||||
return;
|
||||
|
||||
using namespace X64;
|
||||
|
||||
AssemblyBuilderX64 build(/* logText= */ false);
|
||||
@ -315,6 +318,9 @@ static void nonthrowing(int64_t arg)
|
||||
|
||||
TEST_CASE("GeneratedCodeExecutionWithThrowX64")
|
||||
{
|
||||
if (!Luau::CodeGen::isSupported())
|
||||
return;
|
||||
|
||||
using namespace X64;
|
||||
|
||||
AssemblyBuilderX64 build(/* logText= */ false);
|
||||
@ -513,6 +519,9 @@ TEST_CASE("GeneratedCodeExecutionWithThrowX64Simd")
|
||||
|
||||
TEST_CASE("GeneratedCodeExecutionMultipleFunctionsWithThrowX64")
|
||||
{
|
||||
if (!Luau::CodeGen::isSupported())
|
||||
return;
|
||||
|
||||
using namespace X64;
|
||||
|
||||
AssemblyBuilderX64 build(/* logText= */ false);
|
||||
@ -650,6 +659,9 @@ TEST_CASE("GeneratedCodeExecutionMultipleFunctionsWithThrowX64")
|
||||
|
||||
TEST_CASE("GeneratedCodeExecutionWithThrowOutsideTheGateX64")
|
||||
{
|
||||
if (!Luau::CodeGen::isSupported())
|
||||
return;
|
||||
|
||||
using namespace X64;
|
||||
|
||||
AssemblyBuilderX64 build(/* logText= */ false);
|
||||
|
@ -21,7 +21,7 @@ void ConstraintGraphBuilderFixture::generateConstraints(const std::string& code)
|
||||
dfg = std::make_unique<DataFlowGraph>(DataFlowGraphBuilder::build(root, NotNull{&ice}));
|
||||
cgb = std::make_unique<ConstraintGraphBuilder>(mainModule, NotNull{&normalizer}, NotNull(&moduleResolver), builtinTypes, NotNull(&ice),
|
||||
frontend.globals.globalScope, /*prepareModuleScope*/ nullptr, &logger, NotNull{dfg.get()}, std::vector<RequireCycle>());
|
||||
cgb->visit(root);
|
||||
cgb->visitModuleRoot(root);
|
||||
rootScope = cgb->rootScope;
|
||||
constraints = Luau::borrowConstraints(cgb->constraints);
|
||||
}
|
||||
|
@ -657,6 +657,10 @@ TEST_CASE_FIXTURE(DifferFixture, "function_table_self_referential_cyclic")
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
compareTypesNe("foo", "almostFoo",
|
||||
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol>.Ret[1].bar.Ret[1] has type t1 where t1 = { bar: () -> t1 }, while the right type at <unlabeled-symbol>.Ret[1].bar.Ret[1] has type t1 where t1 = () -> t1)");
|
||||
else
|
||||
compareTypesNe("foo", "almostFoo",
|
||||
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol>.Ret[1].bar.Ret[1] has type t1 where t1 = {| bar: () -> t1 |}, while the right type at <unlabeled-symbol>.Ret[1].bar.Ret[1] has type t1 where t1 = () -> t1)");
|
||||
}
|
||||
@ -812,6 +816,10 @@ TEST_CASE_FIXTURE(DifferFixture, "union_missing")
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
compareTypesNe("foo", "almostFoo",
|
||||
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol> is a union containing type { baz: boolean, rot: "singleton" }, while the right type at <unlabeled-symbol> is a union missing type { baz: boolean, rot: "singleton" })");
|
||||
else
|
||||
compareTypesNe("foo", "almostFoo",
|
||||
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol> is a union containing type {| baz: boolean, rot: "singleton" |}, while the right type at <unlabeled-symbol> is a union missing type {| baz: boolean, rot: "singleton" |})");
|
||||
}
|
||||
@ -848,6 +856,10 @@ TEST_CASE_FIXTURE(DifferFixture, "intersection_tables_missing_right")
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
compareTypesNe("foo", "almostFoo",
|
||||
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol> is an intersection containing type { x: number }, while the right type at <unlabeled-symbol> is an intersection missing type { x: number })");
|
||||
else
|
||||
compareTypesNe("foo", "almostFoo",
|
||||
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol> is an intersection containing type {| x: number |}, while the right type at <unlabeled-symbol> is an intersection missing type {| x: number |})");
|
||||
}
|
||||
@ -860,6 +872,10 @@ TEST_CASE_FIXTURE(DifferFixture, "intersection_tables_missing_left")
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
compareTypesNe("foo", "almostFoo",
|
||||
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol> is an intersection missing type { z: boolean }, while the right type at <unlabeled-symbol> is an intersection containing type { z: boolean })");
|
||||
else
|
||||
compareTypesNe("foo", "almostFoo",
|
||||
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol> is an intersection missing type {| z: boolean |}, while the right type at <unlabeled-symbol> is an intersection containing type {| z: boolean |})");
|
||||
}
|
||||
|
@ -12,6 +12,8 @@
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
@ -160,6 +162,9 @@ TEST_CASE_FIXTURE(FrontendFixture, "automatically_check_dependent_scripts")
|
||||
auto bExports = first(bModule->returnType);
|
||||
REQUIRE(!!bExports);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("{ b_value: number }", toString(*bExports));
|
||||
else
|
||||
CHECK_EQ("{| b_value: number |}", toString(*bExports));
|
||||
}
|
||||
|
||||
@ -302,6 +307,10 @@ TEST_CASE_FIXTURE(FrontendFixture, "nocheck_cycle_used_by_checked")
|
||||
|
||||
std::optional<TypeId> cExports = first(cModule->returnType);
|
||||
REQUIRE(bool(cExports));
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("{ a: any, b: any }", toString(*cExports));
|
||||
else
|
||||
CHECK_EQ("{| a: any, b: any |}", toString(*cExports));
|
||||
}
|
||||
|
||||
@ -473,9 +482,15 @@ return {mod_b = 2}
|
||||
LUAU_REQUIRE_ERRORS(resultB);
|
||||
|
||||
TypeId tyB = requireExportedType("game/B", "btype");
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ(toString(tyB, opts), "{ x: number }");
|
||||
else
|
||||
CHECK_EQ(toString(tyB, opts), "{| x: number |}");
|
||||
|
||||
TypeId tyA = requireExportedType("game/A", "atype");
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ(toString(tyA, opts), "{ x: any }");
|
||||
else
|
||||
CHECK_EQ(toString(tyA, opts), "{| x: any |}");
|
||||
|
||||
frontend.markDirty("game/B");
|
||||
@ -483,9 +498,15 @@ return {mod_b = 2}
|
||||
LUAU_REQUIRE_ERRORS(resultB);
|
||||
|
||||
tyB = requireExportedType("game/B", "btype");
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ(toString(tyB, opts), "{ x: number }");
|
||||
else
|
||||
CHECK_EQ(toString(tyB, opts), "{| x: number |}");
|
||||
|
||||
tyA = requireExportedType("game/A", "atype");
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ(toString(tyA, opts), "{ x: any }");
|
||||
else
|
||||
CHECK_EQ(toString(tyA, opts), "{| x: any |}");
|
||||
}
|
||||
|
||||
@ -559,6 +580,9 @@ TEST_CASE_FIXTURE(FrontendFixture, "recheck_if_dependent_script_is_dirty")
|
||||
auto bExports = first(bModule->returnType);
|
||||
REQUIRE(!!bExports);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("{ b_value: string }", toString(*bExports));
|
||||
else
|
||||
CHECK_EQ("{| b_value: string |}", toString(*bExports));
|
||||
}
|
||||
|
||||
@ -883,6 +907,10 @@ TEST_CASE_FIXTURE(FrontendFixture, "it_should_be_safe_to_stringify_errors_when_f
|
||||
// When this test fails, it is because the TypeIds needed by the error have been deallocated.
|
||||
// It is thus basically impossible to predict what will happen when this assert is evaluated.
|
||||
// It could segfault, or you could see weird type names like the empty string or <VALUELESS BY EXCEPTION>
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
REQUIRE_EQ(
|
||||
"Table type 'a' not compatible with type '{ Count: number }' because the former is missing field 'Count'", toString(result.errors[0]));
|
||||
else
|
||||
REQUIRE_EQ(
|
||||
"Table type 'a' not compatible with type '{| Count: number |}' because the former is missing field 'Count'", toString(result.errors[0]));
|
||||
}
|
||||
|
@ -2060,6 +2060,244 @@ bb_fallback_1:
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateArrayElemChecksSameIndex")
|
||||
{
|
||||
ScopedFastFlag luauReuseHashSlots{"LuauReuseArrSlots", true};
|
||||
|
||||
IrOp block = build.block(IrBlockKind::Internal);
|
||||
IrOp fallback = build.block(IrBlockKind::Fallback);
|
||||
|
||||
build.beginBlock(block);
|
||||
|
||||
// This roughly corresponds to 'return t[1] + t[1]'
|
||||
IrOp table1 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(1));
|
||||
build.inst(IrCmd::CHECK_ARRAY_SIZE, table1, build.constInt(0), fallback);
|
||||
IrOp elem1 = build.inst(IrCmd::GET_ARR_ADDR, table1, build.constInt(0));
|
||||
IrOp value1 = build.inst(IrCmd::LOAD_TVALUE, elem1, build.constInt(0));
|
||||
build.inst(IrCmd::STORE_TVALUE, build.vmReg(3), value1);
|
||||
|
||||
build.inst(IrCmd::CHECK_ARRAY_SIZE, table1, build.constInt(0), fallback); // This will be removed
|
||||
IrOp elem2 = build.inst(IrCmd::GET_ARR_ADDR, table1, build.constInt(0)); // And this will be substituted
|
||||
IrOp value1b = build.inst(IrCmd::LOAD_TVALUE, elem2, build.constInt(0));
|
||||
build.inst(IrCmd::STORE_TVALUE, build.vmReg(4), value1b);
|
||||
|
||||
IrOp a = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(3));
|
||||
IrOp b = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(4));
|
||||
IrOp sum = build.inst(IrCmd::ADD_NUM, a, b);
|
||||
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(2), sum);
|
||||
|
||||
build.inst(IrCmd::RETURN, build.vmReg(2), build.constUint(1));
|
||||
|
||||
build.beginBlock(fallback);
|
||||
build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
|
||||
// In the future, we might even see duplicate identical TValue loads go away
|
||||
// In the future, we might even see loads of different VM regs with the same value go away
|
||||
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
|
||||
bb_0:
|
||||
%0 = LOAD_POINTER R1
|
||||
CHECK_ARRAY_SIZE %0, 0i, bb_fallback_1
|
||||
%2 = GET_ARR_ADDR %0, 0i
|
||||
%3 = LOAD_TVALUE %2, 0i
|
||||
STORE_TVALUE R3, %3
|
||||
%7 = LOAD_TVALUE %2, 0i
|
||||
STORE_TVALUE R4, %7
|
||||
%9 = LOAD_DOUBLE R3
|
||||
%10 = LOAD_DOUBLE R4
|
||||
%11 = ADD_NUM %9, %10
|
||||
STORE_DOUBLE R2, %11
|
||||
RETURN R2, 1u
|
||||
|
||||
bb_fallback_1:
|
||||
RETURN R0, 1u
|
||||
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateArrayElemChecksLowerIndex")
|
||||
{
|
||||
ScopedFastFlag luauReuseHashSlots{"LuauReuseArrSlots", true};
|
||||
|
||||
IrOp block = build.block(IrBlockKind::Internal);
|
||||
IrOp fallback = build.block(IrBlockKind::Fallback);
|
||||
|
||||
build.beginBlock(block);
|
||||
|
||||
// This roughly corresponds to 'return t[i] + t[i]'
|
||||
IrOp table1 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(1));
|
||||
IrOp index = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(2));
|
||||
IrOp validIndex = build.inst(IrCmd::TRY_NUM_TO_INDEX, index, fallback);
|
||||
IrOp validOffset = build.inst(IrCmd::SUB_INT, validIndex, build.constInt(1));
|
||||
build.inst(IrCmd::CHECK_ARRAY_SIZE, table1, validOffset, fallback);
|
||||
IrOp elem1 = build.inst(IrCmd::GET_ARR_ADDR, table1, build.constInt(0));
|
||||
IrOp value1 = build.inst(IrCmd::LOAD_TVALUE, elem1, build.constInt(0));
|
||||
build.inst(IrCmd::STORE_TVALUE, build.vmReg(3), value1);
|
||||
|
||||
IrOp validIndex2 = build.inst(IrCmd::TRY_NUM_TO_INDEX, index, fallback);
|
||||
IrOp validOffset2 = build.inst(IrCmd::SUB_INT, validIndex2, build.constInt(1));
|
||||
build.inst(IrCmd::CHECK_ARRAY_SIZE, table1, validOffset2, fallback); // This will be removed
|
||||
IrOp elem2 = build.inst(IrCmd::GET_ARR_ADDR, table1, build.constInt(0)); // And this will be substituted
|
||||
IrOp value1b = build.inst(IrCmd::LOAD_TVALUE, elem2, build.constInt(0));
|
||||
build.inst(IrCmd::STORE_TVALUE, build.vmReg(4), value1b);
|
||||
|
||||
IrOp a = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(3));
|
||||
IrOp b = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(4));
|
||||
IrOp sum = build.inst(IrCmd::ADD_NUM, a, b);
|
||||
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(2), sum);
|
||||
|
||||
build.inst(IrCmd::RETURN, build.vmReg(2), build.constUint(1));
|
||||
|
||||
build.beginBlock(fallback);
|
||||
build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
|
||||
// In the future, we might even see duplicate identical TValue loads go away
|
||||
// In the future, we might even see loads of different VM regs with the same value go away
|
||||
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
|
||||
bb_0:
|
||||
%0 = LOAD_POINTER R1
|
||||
%1 = LOAD_DOUBLE R2
|
||||
%2 = TRY_NUM_TO_INDEX %1, bb_fallback_1
|
||||
%3 = SUB_INT %2, 1i
|
||||
CHECK_ARRAY_SIZE %0, %3, bb_fallback_1
|
||||
%5 = GET_ARR_ADDR %0, 0i
|
||||
%6 = LOAD_TVALUE %5, 0i
|
||||
STORE_TVALUE R3, %6
|
||||
%12 = LOAD_TVALUE %5, 0i
|
||||
STORE_TVALUE R4, %12
|
||||
%14 = LOAD_DOUBLE R3
|
||||
%15 = LOAD_DOUBLE R4
|
||||
%16 = ADD_NUM %14, %15
|
||||
STORE_DOUBLE R2, %16
|
||||
RETURN R2, 1u
|
||||
|
||||
bb_fallback_1:
|
||||
RETURN R0, 1u
|
||||
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateArrayElemChecksSameValue")
|
||||
{
|
||||
ScopedFastFlag luauReuseHashSlots{"LuauReuseArrSlots", true};
|
||||
|
||||
IrOp block = build.block(IrBlockKind::Internal);
|
||||
IrOp fallback = build.block(IrBlockKind::Fallback);
|
||||
|
||||
build.beginBlock(block);
|
||||
|
||||
// This roughly corresponds to 'return t[2] + t[1]'
|
||||
IrOp table1 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(1));
|
||||
build.inst(IrCmd::CHECK_ARRAY_SIZE, table1, build.constInt(1), fallback);
|
||||
IrOp elem1 = build.inst(IrCmd::GET_ARR_ADDR, table1, build.constInt(1));
|
||||
IrOp value1 = build.inst(IrCmd::LOAD_TVALUE, elem1, build.constInt(0));
|
||||
build.inst(IrCmd::STORE_TVALUE, build.vmReg(3), value1);
|
||||
|
||||
build.inst(IrCmd::CHECK_ARRAY_SIZE, table1, build.constInt(0), fallback); // This will be removed
|
||||
IrOp elem2 = build.inst(IrCmd::GET_ARR_ADDR, table1, build.constInt(0));
|
||||
IrOp value1b = build.inst(IrCmd::LOAD_TVALUE, elem2, build.constInt(0));
|
||||
build.inst(IrCmd::STORE_TVALUE, build.vmReg(4), value1b);
|
||||
|
||||
IrOp a = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(3));
|
||||
IrOp b = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(4));
|
||||
IrOp sum = build.inst(IrCmd::ADD_NUM, a, b);
|
||||
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(2), sum);
|
||||
|
||||
build.inst(IrCmd::RETURN, build.vmReg(2), build.constUint(1));
|
||||
|
||||
build.beginBlock(fallback);
|
||||
build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
|
||||
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
|
||||
bb_0:
|
||||
%0 = LOAD_POINTER R1
|
||||
CHECK_ARRAY_SIZE %0, 1i, bb_fallback_1
|
||||
%2 = GET_ARR_ADDR %0, 1i
|
||||
%3 = LOAD_TVALUE %2, 0i
|
||||
STORE_TVALUE R3, %3
|
||||
%6 = GET_ARR_ADDR %0, 0i
|
||||
%7 = LOAD_TVALUE %6, 0i
|
||||
STORE_TVALUE R4, %7
|
||||
%9 = LOAD_DOUBLE R3
|
||||
%10 = LOAD_DOUBLE R4
|
||||
%11 = ADD_NUM %9, %10
|
||||
STORE_DOUBLE R2, %11
|
||||
RETURN R2, 1u
|
||||
|
||||
bb_fallback_1:
|
||||
RETURN R0, 1u
|
||||
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateArrayElemChecksInvalidations")
|
||||
{
|
||||
ScopedFastFlag luauReuseHashSlots{"LuauReuseArrSlots", true};
|
||||
|
||||
IrOp block = build.block(IrBlockKind::Internal);
|
||||
IrOp fallback = build.block(IrBlockKind::Fallback);
|
||||
|
||||
build.beginBlock(block);
|
||||
|
||||
// This roughly corresponds to 'return t[1] + t[1]' with a strange table.insert in the middle
|
||||
IrOp table1 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(1));
|
||||
build.inst(IrCmd::CHECK_ARRAY_SIZE, table1, build.constInt(0), fallback);
|
||||
IrOp elem1 = build.inst(IrCmd::GET_ARR_ADDR, table1, build.constInt(0));
|
||||
IrOp value1 = build.inst(IrCmd::LOAD_TVALUE, elem1, build.constInt(0));
|
||||
build.inst(IrCmd::STORE_TVALUE, build.vmReg(3), value1);
|
||||
|
||||
build.inst(IrCmd::TABLE_SETNUM, table1, build.constInt(2));
|
||||
|
||||
build.inst(IrCmd::CHECK_ARRAY_SIZE, table1, build.constInt(0), fallback); // This will be removed
|
||||
IrOp elem2 = build.inst(IrCmd::GET_ARR_ADDR, table1, build.constInt(0)); // And this will be substituted
|
||||
IrOp value1b = build.inst(IrCmd::LOAD_TVALUE, elem2, build.constInt(0));
|
||||
build.inst(IrCmd::STORE_TVALUE, build.vmReg(4), value1b);
|
||||
|
||||
IrOp a = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(3));
|
||||
IrOp b = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(4));
|
||||
IrOp sum = build.inst(IrCmd::ADD_NUM, a, b);
|
||||
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(2), sum);
|
||||
|
||||
build.inst(IrCmd::RETURN, build.vmReg(2), build.constUint(1));
|
||||
|
||||
build.beginBlock(fallback);
|
||||
build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
|
||||
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
|
||||
bb_0:
|
||||
%0 = LOAD_POINTER R1
|
||||
CHECK_ARRAY_SIZE %0, 0i, bb_fallback_1
|
||||
%2 = GET_ARR_ADDR %0, 0i
|
||||
%3 = LOAD_TVALUE %2, 0i
|
||||
STORE_TVALUE R3, %3
|
||||
%5 = TABLE_SETNUM %0, 2i
|
||||
CHECK_ARRAY_SIZE %0, 0i, bb_fallback_1
|
||||
%7 = GET_ARR_ADDR %0, 0i
|
||||
%8 = LOAD_TVALUE %7, 0i
|
||||
STORE_TVALUE R4, %8
|
||||
%10 = LOAD_DOUBLE R3
|
||||
%11 = LOAD_DOUBLE R4
|
||||
%12 = ADD_NUM %10, %11
|
||||
STORE_DOUBLE R2, %12
|
||||
RETURN R2, 1u
|
||||
|
||||
bb_fallback_1:
|
||||
RETURN R0, 1u
|
||||
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
||||
TEST_SUITE_BEGIN("Analysis");
|
||||
|
@ -489,15 +489,20 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "do_not_clone_types_of_reexported_values")
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
ModulePtr modA = frontend.moduleResolver.getModule("Module/A");
|
||||
ModulePtr modB = frontend.moduleResolver.getModule("Module/B");
|
||||
REQUIRE(modA);
|
||||
ModulePtr modB = frontend.moduleResolver.getModule("Module/B");
|
||||
REQUIRE(modB);
|
||||
|
||||
std::optional<TypeId> typeA = first(modA->returnType);
|
||||
std::optional<TypeId> typeB = first(modB->returnType);
|
||||
REQUIRE(typeA);
|
||||
std::optional<TypeId> typeB = first(modB->returnType);
|
||||
REQUIRE(typeB);
|
||||
|
||||
TableType* tableA = getMutable<TableType>(*typeA);
|
||||
REQUIRE_MESSAGE(tableA, "Expected a table, but got " << toString(*typeA));
|
||||
TableType* tableB = getMutable<TableType>(*typeB);
|
||||
REQUIRE_MESSAGE(tableB, "Expected a table, but got " << toString(*typeB));
|
||||
|
||||
CHECK(tableA->props["a"].type() == tableB->props["b"].type());
|
||||
}
|
||||
|
||||
|
17
tests/NonStrictTypeChecker.test.cpp
Normal file
17
tests/NonStrictTypeChecker.test.cpp
Normal file
@ -0,0 +1,17 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/NonStrictTypeChecker.h"
|
||||
|
||||
#include "Fixture.h"
|
||||
|
||||
#include "doctest.h"
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
TEST_SUITE_BEGIN("NonStrictTypeCheckerTest");
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "basic")
|
||||
{
|
||||
Luau::checkNonStrict(builtinTypes, nullptr);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
@ -797,6 +797,9 @@ TEST_CASE_FIXTURE(NormalizeFixture, "narrow_union_of_classes_with_intersection")
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "intersection_of_metatables_where_the_metatable_is_top_or_bottom")
|
||||
{
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK("{ @metatable *error-type*, { } }" == toString(normal("Mt<{}, any> & Mt<{}, err>")));
|
||||
else
|
||||
CHECK("{ @metatable *error-type*, {| |} }" == toString(normal("Mt<{}, any> & Mt<{}, err>")));
|
||||
}
|
||||
|
||||
@ -863,6 +866,9 @@ TEST_CASE_FIXTURE(NormalizeFixture, "classes_and_never")
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "top_table_type")
|
||||
{
|
||||
CHECK("table" == toString(normal("{} | tbl")));
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK("{ }" == toString(normal("{} & tbl")));
|
||||
else
|
||||
CHECK("{| |}" == toString(normal("{} & tbl")));
|
||||
CHECK("never" == toString(normal("number & tbl")));
|
||||
}
|
||||
|
@ -394,8 +394,8 @@ TEST_CASE_FIXTURE(SimplifyFixture, "table_with_a_tag")
|
||||
TypeId t1 = mkTable({{"tag", stringTy}, {"prop", numberTy}});
|
||||
TypeId t2 = mkTable({{"tag", helloTy}});
|
||||
|
||||
CHECK("{| prop: number, tag: string |} & {| tag: \"hello\" |}" == intersectStr(t1, t2));
|
||||
CHECK("{| prop: number, tag: string |} & {| tag: \"hello\" |}" == intersectStr(t2, t1));
|
||||
CHECK("{ prop: number, tag: string } & { tag: \"hello\" }" == intersectStr(t1, t2));
|
||||
CHECK("{ prop: number, tag: string } & { tag: \"hello\" }" == intersectStr(t2, t1));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(SimplifyFixture, "nested_table_tag_test")
|
||||
|
@ -50,6 +50,9 @@ TEST_CASE_FIXTURE(Fixture, "cyclic_table")
|
||||
TableType* tableOne = getMutable<TableType>(&cyclicTable);
|
||||
tableOne->props["self"] = {&cyclicTable};
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("t1 where t1 = {| self: t1 |}", toString(&cyclicTable));
|
||||
else
|
||||
CHECK_EQ("t1 where t1 = { self: t1 }", toString(&cyclicTable));
|
||||
}
|
||||
|
||||
@ -68,11 +71,17 @@ TEST_CASE_FIXTURE(Fixture, "empty_table")
|
||||
local a: {}
|
||||
)");
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("{ }", toString(requireType("a")));
|
||||
else
|
||||
CHECK_EQ("{| |}", toString(requireType("a")));
|
||||
|
||||
// Should stay the same with useLineBreaks enabled
|
||||
ToStringOptions opts;
|
||||
opts.useLineBreaks = true;
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("{ }", toString(requireType("a"), opts));
|
||||
else
|
||||
CHECK_EQ("{| |}", toString(requireType("a"), opts));
|
||||
}
|
||||
|
||||
@ -86,6 +95,14 @@ TEST_CASE_FIXTURE(Fixture, "table_respects_use_line_break")
|
||||
opts.useLineBreaks = true;
|
||||
|
||||
//clang-format off
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("{\n"
|
||||
" anotherProp: number,\n"
|
||||
" prop: string,\n"
|
||||
" thirdProp: boolean\n"
|
||||
"}",
|
||||
toString(requireType("a"), opts));
|
||||
else
|
||||
CHECK_EQ("{|\n"
|
||||
" anotherProp: number,\n"
|
||||
" prop: string,\n"
|
||||
@ -122,6 +139,9 @@ TEST_CASE_FIXTURE(Fixture, "metatable")
|
||||
Type table{TypeVariant(TableType())};
|
||||
Type metatable{TypeVariant(TableType())};
|
||||
Type mtv{TypeVariant(MetatableType{&table, &metatable})};
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("{ @metatable {| |}, {| |} }", toString(&mtv));
|
||||
else
|
||||
CHECK_EQ("{ @metatable { }, { } }", toString(&mtv));
|
||||
}
|
||||
|
||||
@ -258,6 +278,9 @@ TEST_CASE_FIXTURE(Fixture, "quit_stringifying_table_type_when_length_is_exceeded
|
||||
ToStringOptions o;
|
||||
o.exhaustive = false;
|
||||
o.maxTableLength = 40;
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ(toString(&tv, o), "{| a: number, b: number, c: number, d: number, e: number, ... 10 more ... |}");
|
||||
else
|
||||
CHECK_EQ(toString(&tv, o), "{ a: number, b: number, c: number, d: number, e: number, ... 10 more ... }");
|
||||
}
|
||||
|
||||
@ -272,6 +295,9 @@ TEST_CASE_FIXTURE(Fixture, "stringifying_table_type_is_still_capped_when_exhaust
|
||||
ToStringOptions o;
|
||||
o.exhaustive = true;
|
||||
o.maxTableLength = 40;
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ(toString(&tv, o), "{| a: number, b: number, c: number, d: number, e: number, ... 2 more ... |}");
|
||||
else
|
||||
CHECK_EQ(toString(&tv, o), "{ a: number, b: number, c: number, d: number, e: number, ... 2 more ... }");
|
||||
}
|
||||
|
||||
@ -346,6 +372,9 @@ TEST_CASE_FIXTURE(Fixture, "stringifying_table_type_correctly_use_matching_table
|
||||
|
||||
ToStringOptions o;
|
||||
o.maxTableLength = 40;
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ(toString(&tv, o), "{ a: number, b: number, c: number, d: number, e: number, ... 5 more ... }");
|
||||
else
|
||||
CHECK_EQ(toString(&tv, o), "{| a: number, b: number, c: number, d: number, e: number, ... 5 more ... |}");
|
||||
}
|
||||
|
||||
@ -377,6 +406,9 @@ TEST_CASE_FIXTURE(Fixture, "stringifying_array_uses_array_syntax")
|
||||
CHECK_EQ("{string}", toString(Type{ttv}));
|
||||
|
||||
ttv.props["A"] = {builtinTypes->numberType};
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("{ [number]: string, A: number }", toString(Type{ttv}));
|
||||
else
|
||||
CHECK_EQ("{| [number]: string, A: number |}", toString(Type{ttv}));
|
||||
|
||||
ttv.props.clear();
|
||||
@ -576,9 +608,18 @@ TEST_CASE_FIXTURE(Fixture, "toString_the_boundTo_table_type_contained_within_a_T
|
||||
|
||||
TypePackVar tpv2{TypePack{{&tv2}}};
|
||||
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("{ hello: number, world: number }", toString(&tpv1));
|
||||
CHECK_EQ("{ hello: number, world: number }", toString(&tpv2));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("{| hello: number, world: number |}", toString(&tpv1));
|
||||
CHECK_EQ("{| hello: number, world: number |}", toString(&tpv2));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "no_parentheses_around_return_type_if_pack_has_an_empty_head_link")
|
||||
{
|
||||
@ -846,7 +887,24 @@ TEST_CASE_FIXTURE(Fixture, "tostring_error_mismatch")
|
||||
end
|
||||
|
||||
)");
|
||||
std::string expected = R"(Type
|
||||
//clang-format off
|
||||
std::string expected =
|
||||
(FFlag::DebugLuauDeferredConstraintResolution) ?
|
||||
R"(Type
|
||||
'{| a: number, b: string, c: {| d: string |} |}'
|
||||
could not be converted into
|
||||
'{ a: number, b: string, c: { d: number } }'
|
||||
caused by:
|
||||
Property 'c' is not compatible.
|
||||
Type
|
||||
'{| d: string |}'
|
||||
could not be converted into
|
||||
'{ d: number }'
|
||||
caused by:
|
||||
Property 'd' is not compatible.
|
||||
Type 'string' could not be converted into 'number' in an invariant context)"
|
||||
:
|
||||
R"(Type
|
||||
'{ a: number, b: string, c: { d: string } }'
|
||||
could not be converted into
|
||||
'{| a: number, b: string, c: {| d: number |} |}'
|
||||
@ -859,9 +917,12 @@ could not be converted into
|
||||
caused by:
|
||||
Property 'd' is not compatible.
|
||||
Type 'string' could not be converted into 'number' in an invariant context)";
|
||||
//clang-format on
|
||||
//
|
||||
std::string actual = toString(result.errors[0]);
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
CHECK(expected == actual);
|
||||
}
|
||||
TEST_SUITE_END();
|
||||
|
@ -223,7 +223,7 @@ TEST_CASE_FIXTURE(Fixture, "dependent_generic_aliases")
|
||||
const std::string expected = R"(Type 'bad' could not be converted into 'U<number>'
|
||||
caused by:
|
||||
Property 't' is not compatible.
|
||||
Type '{ v: string }' could not be converted into 'T<number>'
|
||||
Type '{| v: string |}' could not be converted into 'T<number>'
|
||||
caused by:
|
||||
Property 'v' is not compatible.
|
||||
Type 'string' could not be converted into 'number' in an invariant context)";
|
||||
@ -332,6 +332,9 @@ TEST_CASE_FIXTURE(Fixture, "stringify_type_alias_of_recursive_template_table_typ
|
||||
|
||||
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
|
||||
REQUIRE(tm);
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("t1 where t1 = ({ a: t1 }) -> string", toString(tm->wantedType));
|
||||
else
|
||||
CHECK_EQ("t1 where t1 = ({| a: t1 |}) -> string", toString(tm->wantedType));
|
||||
CHECK_EQ(builtinTypes->numberType, tm->givenType);
|
||||
}
|
||||
@ -815,6 +818,9 @@ TEST_CASE_FIXTURE(Fixture, "forward_declared_alias_is_not_clobbered_by_prior_uni
|
||||
local d: FutureType = { smth = true } -- missing error, 'd' is resolved to 'any'
|
||||
)");
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("{ foo: number }", toString(requireType("d"), {true}));
|
||||
else
|
||||
CHECK_EQ("{| foo: number |}", toString(requireType("d"), {true}));
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
@ -398,6 +398,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_pack")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("{ [number]: boolean | number | string, n: number }", toString(requireType("t")));
|
||||
else
|
||||
CHECK_EQ("{| [number]: boolean | number | string, n: number |}", toString(requireType("t")));
|
||||
}
|
||||
|
||||
@ -413,6 +416,9 @@ local t = table.pack(f())
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("{ [number]: number | string, n: number }", toString(requireType("t")));
|
||||
else
|
||||
CHECK_EQ("{| [number]: number | string, n: number |}", toString(requireType("t")));
|
||||
}
|
||||
|
||||
@ -423,6 +429,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_pack_reduce")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("{ [number]: boolean | number, n: number }", toString(requireType("t")));
|
||||
else
|
||||
CHECK_EQ("{| [number]: boolean | number, n: number |}", toString(requireType("t")));
|
||||
|
||||
result = check(R"(
|
||||
@ -430,6 +439,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_pack_reduce")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("{ [number]: string, n: number }", toString(requireType("t")));
|
||||
else
|
||||
CHECK_EQ("{| [number]: string, n: number |}", toString(requireType("t")));
|
||||
}
|
||||
|
||||
|
@ -2077,7 +2077,7 @@ TEST_CASE_FIXTURE(Fixture, "attempt_to_call_an_intersection_of_tables")
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ(toString(result.errors[0]), "Cannot call non-function {| x: number |} & {| y: string |}");
|
||||
CHECK_EQ(toString(result.errors[0]), "Cannot call non-function { x: number } & { y: string }");
|
||||
else
|
||||
CHECK_EQ(toString(result.errors[0]), "Cannot call non-function {| x: number |}");
|
||||
}
|
||||
|
@ -164,7 +164,7 @@ TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_with_property_guarante
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK("{| y: number |}" == toString(requireType("r")));
|
||||
CHECK("{ y: number }" == toString(requireType("r")));
|
||||
else
|
||||
CHECK("{| y: number |} & {| y: number |}" == toString(requireType("r")));
|
||||
}
|
||||
@ -513,7 +513,13 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
const std::string expected = R"(Type
|
||||
const std::string expected =
|
||||
(FFlag::DebugLuauDeferredConstraintResolution) ?
|
||||
"Type "
|
||||
"'{ p: number?, q: number?, r: number? } & { p: number?, q: string? }'"
|
||||
" could not be converted into "
|
||||
"'{ p: nil }'; none of the intersection parts are compatible" :
|
||||
R"(Type
|
||||
'{| p: number?, q: number?, r: number? |} & {| p: number?, q: string? |}'
|
||||
could not be converted into
|
||||
'{| p: nil |}'; none of the intersection parts are compatible)";
|
||||
@ -581,7 +587,13 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_returning_intersections")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
const std::string expected = R"(Type
|
||||
const std::string expected =
|
||||
(FFlag::DebugLuauDeferredConstraintResolution) ?
|
||||
R"(Type
|
||||
'((number?) -> { p: number } & { q: number }) & ((string?) -> { p: number } & { r: number })'
|
||||
could not be converted into
|
||||
'(number?) -> { p: number, q: number, r: number }'; none of the intersection parts are compatible)" :
|
||||
R"(Type
|
||||
'((number?) -> {| p: number |} & {| q: number |}) & ((string?) -> {| p: number |} & {| r: number |})'
|
||||
could not be converted into
|
||||
'(number?) -> {| p: number, q: number, r: number |}'; none of the intersection parts are compatible)";
|
||||
@ -933,7 +945,7 @@ TEST_CASE_FIXTURE(Fixture, "less_greedy_unification_with_intersection_types_2")
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("({| x: number |} & {| x: string |}) -> never", toString(requireType("f")));
|
||||
CHECK_EQ("({ x: number } & { x: string }) -> never", toString(requireType("f")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "index_property_table_intersection_1")
|
||||
|
@ -225,6 +225,9 @@ local tbl: string = require(game.A)
|
||||
|
||||
CheckResult result = frontend.check("game/B");
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("Type '{ def: number }' could not be converted into 'string'", toString(result.errors[0]));
|
||||
else
|
||||
CHECK_EQ("Type '{| def: number |}' could not be converted into 'string'", toString(result.errors[0]));
|
||||
}
|
||||
|
||||
|
@ -410,7 +410,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "cycle_between_object_constructor_and_alias")
|
||||
CHECK_MESSAGE(get<MetatableType>(follow(aliasType)), "Expected metatable type but got: " << toString(aliasType));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "promise_type_error_too_complex" * doctest::timeout(0.5))
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "promise_type_error_too_complex" * doctest::timeout(2))
|
||||
{
|
||||
// TODO: LTI changes to function call resolution have rendered this test impossibly slow
|
||||
// shared self should fix it, but there may be other mitigations possible as well
|
||||
|
@ -571,6 +571,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "typecheck_unary_len_error")
|
||||
CHECK_EQ("number", toString(requireType("a")));
|
||||
|
||||
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
|
||||
REQUIRE_MESSAGE(tm, "Expected a TypeMismatch but got " << result.errors[0]);
|
||||
|
||||
REQUIRE_EQ(*tm->wantedType, *builtinTypes->numberType);
|
||||
REQUIRE_EQ(*tm->givenType, *builtinTypes->stringType);
|
||||
}
|
||||
|
@ -251,12 +251,24 @@ TEST_CASE_FIXTURE(Fixture, "discriminate_from_x_not_equal_to_nil")
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
|
||||
CHECK_EQ("{ x: string, y: number }", toString(requireTypeAtPosition({5, 28})));
|
||||
|
||||
// Should be { x: nil, y: nil }
|
||||
CHECK_EQ("{ x: nil, y: nil } | { x: string, y: number }", toString(requireTypeAtPosition({7, 28})));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("{| x: string, y: number |}", toString(requireTypeAtPosition({5, 28})));
|
||||
|
||||
// Should be {| x: nil, y: nil |}
|
||||
CHECK_EQ("{| x: nil, y: nil |} | {| x: string, y: number |}", toString(requireTypeAtPosition({7, 28})));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "bail_early_if_unification_is_too_complicated" * doctest::timeout(0.5))
|
||||
{
|
||||
ScopedFastInt sffi{"LuauTarjanChildLimit", 1};
|
||||
|
@ -535,6 +535,9 @@ TEST_CASE_FIXTURE(Fixture, "unknown_lvalue_is_not_synonymous_with_other_on_not_e
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "any"); // a ~= b
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "{ x: number }?"); // a ~= b
|
||||
else
|
||||
CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "{| x: number |}?"); // a ~= b
|
||||
}
|
||||
|
||||
@ -658,6 +661,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_narrows_for_table")
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("{ x: number } | { y: boolean }", toString(requireTypeAtPosition({3, 28}))); // type(x) == "table"
|
||||
else
|
||||
CHECK_EQ("{| x: number |} | {| y: boolean |}", toString(requireTypeAtPosition({3, 28}))); // type(x) == "table"
|
||||
CHECK_EQ("string", toString(requireTypeAtPosition({5, 28}))); // type(x) ~= "table"
|
||||
}
|
||||
@ -697,6 +703,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_guard_can_filter_for_intersection_of_ta
|
||||
|
||||
ToStringOptions opts;
|
||||
opts.exhaustive = true;
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("{ x: number } & { y: number }", toString(requireTypeAtPosition({4, 28}), opts));
|
||||
else
|
||||
CHECK_EQ("{| x: number |} & {| y: number |}", toString(requireTypeAtPosition({4, 28}), opts));
|
||||
CHECK_EQ("nil", toString(requireTypeAtPosition({6, 28})));
|
||||
}
|
||||
@ -1216,9 +1225,18 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "discriminate_from_isa_of_x")
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ(R"({ tag: "Part", x: Part })", toString(requireTypeAtPosition({5, 28})));
|
||||
CHECK_EQ(R"({ tag: "Folder", x: Folder })", toString(requireTypeAtPosition({7, 28})));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ(R"({| tag: "Part", x: Part |})", toString(requireTypeAtPosition({5, 28})));
|
||||
CHECK_EQ(R"({| tag: "Folder", x: Folder |})", toString(requireTypeAtPosition({7, 28})));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(RefinementClassFixture, "typeguard_cast_free_table_to_vector")
|
||||
{
|
||||
|
@ -91,6 +91,9 @@ TEST_CASE_FIXTURE(Fixture, "cannot_augment_sealed_table")
|
||||
|
||||
// TODO: better, more robust comparison of type vars
|
||||
auto s = toString(error->tableType, ToStringOptions{/*exhaustive*/ true});
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ(s, "{ prop: number }");
|
||||
else
|
||||
CHECK_EQ(s, "{| prop: number |}");
|
||||
CHECK_EQ(error->prop, "foo");
|
||||
CHECK_EQ(error->context, CannotExtendTable::Property);
|
||||
@ -733,6 +736,9 @@ TEST_CASE_FIXTURE(Fixture, "infer_indexer_from_value_property_in_literal")
|
||||
CHECK(bool(retType->indexer));
|
||||
|
||||
const TableIndexer& indexer = *retType->indexer;
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("{ __name: string }", toString(indexer.indexType));
|
||||
else
|
||||
CHECK_EQ("{| __name: string |}", toString(indexer.indexType));
|
||||
}
|
||||
|
||||
@ -775,6 +781,9 @@ TEST_CASE_FIXTURE(Fixture, "indexer_mismatch")
|
||||
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
|
||||
REQUIRE(tm != nullptr);
|
||||
CHECK(toString(tm->wantedType) == "{number}");
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK(toString(tm->givenType) == "{ [string]: string }");
|
||||
else
|
||||
CHECK(toString(tm->givenType) == "{| [string]: string |}");
|
||||
|
||||
CHECK_NE(*t1, *t2);
|
||||
@ -1564,9 +1573,17 @@ TEST_CASE_FIXTURE(Fixture, "casting_sealed_tables_with_props_into_table_with_ind
|
||||
ToStringOptions o{/* exhaustive= */ true};
|
||||
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
|
||||
REQUIRE(tm);
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("{ [string]: string }", toString(tm->wantedType, o));
|
||||
CHECK_EQ("{ foo: number }", toString(tm->givenType, o));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("{| [string]: string |}", toString(tm->wantedType, o));
|
||||
CHECK_EQ("{| foo: number |}", toString(tm->givenType, o));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "casting_tables_with_props_into_table_with_indexer2")
|
||||
{
|
||||
@ -1803,9 +1820,17 @@ TEST_CASE_FIXTURE(Fixture, "hide_table_error_properties")
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("Cannot add property 'a' to table '{ x: number }'", toString(result.errors[0]));
|
||||
CHECK_EQ("Cannot add property 'b' to table '{ x: number }'", toString(result.errors[1]));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("Cannot add property 'a' to table '{| x: number |}'", toString(result.errors[0]));
|
||||
CHECK_EQ("Cannot add property 'b' to table '{| x: number |}'", toString(result.errors[1]));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "builtin_table_names")
|
||||
{
|
||||
@ -2969,6 +2994,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "access_index_metamethod_that_returns_variadi
|
||||
|
||||
ToStringOptions o;
|
||||
o.exhaustive = true;
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("{ x: string }", toString(requireType("foo"), o));
|
||||
else
|
||||
CHECK_EQ("{| x: string |}", toString(requireType("foo"), o));
|
||||
}
|
||||
|
||||
@ -3029,6 +3057,9 @@ TEST_CASE_FIXTURE(Fixture, "accidentally_checked_prop_in_opposite_branch")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("Value of type '{ x: number? }?' could be nil", toString(result.errors[0]));
|
||||
else
|
||||
CHECK_EQ("Value of type '{| x: number? |}?' could be nil", toString(result.errors[0]));
|
||||
CHECK_EQ("boolean", toString(requireType("u")));
|
||||
}
|
||||
@ -3260,6 +3291,9 @@ TEST_CASE_FIXTURE(Fixture, "prop_access_on_unions_of_indexers_where_key_whose_ty
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("Type '{ [boolean]: number } | {number}' does not have key 'x'", toString(result.errors[0]));
|
||||
else
|
||||
CHECK_EQ("Type '{number} | {| [boolean]: number |}' does not have key 'x'", toString(result.errors[0]));
|
||||
}
|
||||
|
||||
@ -3824,4 +3858,24 @@ TEST_CASE_FIXTURE(Fixture, "cli_84607_missing_prop_in_array_or_dict")
|
||||
CHECK_EQ("prop", error2->properties[0]);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "simple_method_definition")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local T = {}
|
||||
|
||||
function T:m()
|
||||
return 5
|
||||
end
|
||||
|
||||
return T
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("{ m: (unknown) -> number }", toString(getMainModule()->returnType, ToStringOptions{true}));
|
||||
else
|
||||
CHECK_EQ("{| m: <a>(a) -> number |}", toString(getMainModule()->returnType, ToStringOptions{true}));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -69,6 +69,18 @@ TEST_CASE_FIXTURE(Fixture, "infer_locals_with_nil_value")
|
||||
CHECK_EQ(getPrimitiveType(ty), PrimitiveType::String);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "infer_locals_with_nil_value_2")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local a = 2
|
||||
local b = a,nil
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK_EQ("number", toString(requireType("a")));
|
||||
CHECK_EQ("number", toString(requireType("b")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "infer_locals_via_assignment_from_its_call_site")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
@ -1168,6 +1180,28 @@ TEST_CASE_FIXTURE(Fixture, "bidirectional_checking_of_higher_order_function")
|
||||
CHECK(location.end.line == 4);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "bidirectional_checking_of_callback_property")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local print: (number) -> ()
|
||||
|
||||
type Point = {x: number, y: number}
|
||||
local T : {callback: ((Point) -> ())?} = {}
|
||||
|
||||
T.callback = function(p) -- No error here
|
||||
print(p.z) -- error here. Point has no property z
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
CHECK_MESSAGE(get<UnknownProperty>(result.errors[0]), "Expected UnknownProperty but got " << result.errors[0]);
|
||||
|
||||
Location location = result.errors[0].location;
|
||||
CHECK(location.begin.line == 7);
|
||||
CHECK(location.end.line == 7);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "it_is_ok_to_have_inconsistent_number_of_return_values_in_nonstrict")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
|
@ -373,12 +373,22 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "metatables_unify_against_shape_of_free_table
|
||||
state.log.commit();
|
||||
|
||||
REQUIRE_EQ(state.errors.size(), 1);
|
||||
const std::string expected = R"(Type
|
||||
// clang-format off
|
||||
const std::string expected =
|
||||
(FFlag::DebugLuauDeferredConstraintResolution) ?
|
||||
R"(Type
|
||||
'{ @metatable { __index: { foo: string } }, {| |} }'
|
||||
could not be converted into
|
||||
'{- foo: number -}'
|
||||
caused by:
|
||||
Type 'number' could not be converted into 'string')" :
|
||||
R"(Type
|
||||
'{ @metatable {| __index: {| foo: string |} |}, { } }'
|
||||
could not be converted into
|
||||
'{- foo: number -}'
|
||||
caused by:
|
||||
Type 'number' could not be converted into 'string')";
|
||||
// clang-format on
|
||||
CHECK_EQ(expected, toString(state.errors[0]));
|
||||
}
|
||||
|
||||
|
@ -308,11 +308,17 @@ local c: Packed<string, number, boolean>
|
||||
tf = lookupType("Packed");
|
||||
REQUIRE(tf);
|
||||
CHECK_EQ(toString(*tf), "Packed<T, U...>");
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ(toString(*tf, {true}), "{ f: (T, U...) -> (T, U...) }");
|
||||
else
|
||||
CHECK_EQ(toString(*tf, {true}), "{| f: (T, U...) -> (T, U...) |}");
|
||||
|
||||
auto ttvA = get<TableType>(requireType("a"));
|
||||
REQUIRE(ttvA);
|
||||
CHECK_EQ(toString(requireType("a")), "Packed<number>");
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ(toString(requireType("a"), {true}), "{ f: (number) -> number }");
|
||||
else
|
||||
CHECK_EQ(toString(requireType("a"), {true}), "{| f: (number) -> number |}");
|
||||
REQUIRE(ttvA->instantiatedTypeParams.size() == 1);
|
||||
REQUIRE(ttvA->instantiatedTypePackParams.size() == 1);
|
||||
@ -322,6 +328,9 @@ local c: Packed<string, number, boolean>
|
||||
auto ttvB = get<TableType>(requireType("b"));
|
||||
REQUIRE(ttvB);
|
||||
CHECK_EQ(toString(requireType("b")), "Packed<string, number>");
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ(toString(requireType("b"), {true}), "{ f: (string, number) -> (string, number) }");
|
||||
else
|
||||
CHECK_EQ(toString(requireType("b"), {true}), "{| f: (string, number) -> (string, number) |}");
|
||||
REQUIRE(ttvB->instantiatedTypeParams.size() == 1);
|
||||
REQUIRE(ttvB->instantiatedTypePackParams.size() == 1);
|
||||
@ -331,6 +340,9 @@ local c: Packed<string, number, boolean>
|
||||
auto ttvC = get<TableType>(requireType("c"));
|
||||
REQUIRE(ttvC);
|
||||
CHECK_EQ(toString(requireType("c")), "Packed<string, number, boolean>");
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ(toString(requireType("c"), {true}), "{ f: (string, number, boolean) -> (string, number, boolean) }");
|
||||
else
|
||||
CHECK_EQ(toString(requireType("c"), {true}), "{| f: (string, number, boolean) -> (string, number, boolean) |}");
|
||||
REQUIRE(ttvC->instantiatedTypeParams.size() == 1);
|
||||
REQUIRE(ttvC->instantiatedTypePackParams.size() == 1);
|
||||
@ -360,6 +372,18 @@ local d: { a: typeof(c) }
|
||||
auto tf = lookupImportedType("Import", "Packed");
|
||||
REQUIRE(tf);
|
||||
CHECK_EQ(toString(*tf), "Packed<T, U...>");
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ(toString(*tf, {true}), "{ a: T, b: (U...) -> () }");
|
||||
|
||||
CHECK_EQ(toString(requireType("a"), {true}), "{ a: number, b: () -> () }");
|
||||
CHECK_EQ(toString(requireType("b"), {true}), "{ a: string, b: (number) -> () }");
|
||||
CHECK_EQ(toString(requireType("c"), {true}), "{ a: string, b: (number, boolean) -> () }");
|
||||
CHECK_EQ(toString(requireType("d")), "{ a: Packed<string, number, boolean> }");
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ(toString(*tf, {true}), "{| a: T, b: (U...) -> () |}");
|
||||
|
||||
CHECK_EQ(toString(requireType("a"), {true}), "{| a: number, b: () -> () |}");
|
||||
@ -367,6 +391,7 @@ local d: { a: typeof(c) }
|
||||
CHECK_EQ(toString(requireType("c"), {true}), "{| a: string, b: (number, boolean) -> () |}");
|
||||
CHECK_EQ(toString(requireType("d")), "{| a: Packed<string, number, boolean> |}");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "type_pack_type_parameters")
|
||||
{
|
||||
@ -388,18 +413,30 @@ type C<X...> = Import.Packed<string, (number, X...)>
|
||||
auto tf = lookupType("Alias");
|
||||
REQUIRE(tf);
|
||||
CHECK_EQ(toString(*tf), "Alias<S, T, R...>");
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ(toString(*tf, {true}), "{ a: S, b: (T, R...) -> () }");
|
||||
else
|
||||
CHECK_EQ(toString(*tf, {true}), "{| a: S, b: (T, R...) -> () |}");
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ(toString(requireType("a"), {true}), "{ a: string, b: (number, boolean) -> () }");
|
||||
else
|
||||
CHECK_EQ(toString(requireType("a"), {true}), "{| a: string, b: (number, boolean) -> () |}");
|
||||
|
||||
tf = lookupType("B");
|
||||
REQUIRE(tf);
|
||||
CHECK_EQ(toString(*tf), "B<X...>");
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ(toString(*tf, {true}), "{ a: string, b: (X...) -> () }");
|
||||
else
|
||||
CHECK_EQ(toString(*tf, {true}), "{| a: string, b: (X...) -> () |}");
|
||||
|
||||
tf = lookupType("C");
|
||||
REQUIRE(tf);
|
||||
CHECK_EQ(toString(*tf), "C<X...>");
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ(toString(*tf, {true}), "{ a: string, b: (number, X...) -> () }");
|
||||
else
|
||||
CHECK_EQ(toString(*tf, {true}), "{| a: string, b: (number, X...) -> () |}");
|
||||
}
|
||||
|
||||
@ -867,6 +904,9 @@ type R = { m: F<R> }
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ(toString(*lookupType("R"), {true}), "t1 where t1 = { m: (t1) -> (t1) -> () }");
|
||||
else
|
||||
CHECK_EQ(toString(*lookupType("R"), {true}), "t1 where t1 = {| m: (t1) -> (t1) -> () |}");
|
||||
}
|
||||
|
||||
|
@ -356,6 +356,9 @@ a.x = 2
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
auto s = toString(result.errors[0]);
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("Value of type '({ x: number } & { y: number })?' could be nil", s);
|
||||
else
|
||||
CHECK_EQ("Value of type '({| x: number |} & {| y: number |})?' could be nil", s);
|
||||
}
|
||||
|
||||
@ -471,11 +474,20 @@ local b: { w: number } = a
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
const std::string expected = R"(Type 'X | Y | Z' could not be converted into '{| w: number |}'
|
||||
caused by:
|
||||
Not all union options are compatible.
|
||||
Table type 'X' not compatible with type '{| w: number |}' because the former is missing field 'w')";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
|
||||
REQUIRE(tm);
|
||||
|
||||
CHECK_EQ(tm->reason, "Not all union options are compatible.");
|
||||
|
||||
CHECK_EQ("X | Y | Z", toString(tm->givenType));
|
||||
|
||||
const TableType* expected = get<TableType>(tm->wantedType);
|
||||
REQUIRE(expected);
|
||||
CHECK_EQ(TableState::Sealed, expected->state);
|
||||
CHECK_EQ(1, expected->props.size());
|
||||
auto propW = expected->props.find("w");
|
||||
REQUIRE_NE(expected->props.end(), propW);
|
||||
CHECK_EQ("number", toString(propW->second.type()));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "error_detailed_union_all")
|
||||
@ -744,6 +756,9 @@ TEST_CASE_FIXTURE(Fixture, "less_greedy_unification_with_union_types_2")
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("({ x: number } | { x: string }) -> number | string", toString(requireType("f")));
|
||||
else
|
||||
CHECK_EQ("({| x: number |} | {| x: string |}) -> number | string", toString(requireType("f")));
|
||||
}
|
||||
|
||||
|
@ -298,6 +298,9 @@ TEST_CASE_FIXTURE(Fixture, "substitution_skip_failure")
|
||||
|
||||
REQUIRE(!anyification.normalizationTooComplex);
|
||||
REQUIRE(any.has_value());
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("{ f: t1 } where t1 = () -> { f: () -> { f: ({ f: t1 }) -> (), signal: { f: (any) -> () } } }", toString(*any));
|
||||
else
|
||||
CHECK_EQ("{| f: t1 |} where t1 = () -> {| f: () -> {| f: ({| f: t1 |}) -> (), signal: {| f: (any) -> () |} |} |}", toString(*any));
|
||||
}
|
||||
|
||||
|
@ -38,6 +38,11 @@ struct Unifier2Fixture
|
||||
{
|
||||
return ::Luau::toString(ty, opts);
|
||||
}
|
||||
|
||||
std::string toString(TypePackId ty)
|
||||
{
|
||||
return ::Luau::toString(ty, opts);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_SUITE_BEGIN("Unifier2");
|
||||
@ -99,6 +104,32 @@ TEST_CASE_FIXTURE(Unifier2Fixture, "(string) -> () <: (X) -> Y...")
|
||||
CHECK(!yPack->tail);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Unifier2Fixture, "unify_binds_free_subtype_tail_pack")
|
||||
{
|
||||
TypePackId numberPack = arena.addTypePack({builtinTypes.numberType});
|
||||
|
||||
TypePackId freeTail = arena.freshTypePack(&scope);
|
||||
TypeId freeHead = arena.addType(FreeType{&scope, builtinTypes.neverType, builtinTypes.unknownType});
|
||||
TypePackId freeAndFree = arena.addTypePack({freeHead}, freeTail);
|
||||
|
||||
u2.unify(freeAndFree, numberPack);
|
||||
|
||||
CHECK("('a <: number)" == toString(freeAndFree));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Unifier2Fixture, "unify_binds_free_supertype_tail_pack")
|
||||
{
|
||||
TypePackId numberPack = arena.addTypePack({builtinTypes.numberType});
|
||||
|
||||
TypePackId freeTail = arena.freshTypePack(&scope);
|
||||
TypeId freeHead = arena.addType(FreeType{&scope, builtinTypes.neverType, builtinTypes.unknownType});
|
||||
TypePackId freeAndFree = arena.addTypePack({freeHead}, freeTail);
|
||||
|
||||
u2.unify(numberPack, freeAndFree);
|
||||
|
||||
CHECK("(number <: 'a)" == toString(freeAndFree));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Unifier2Fixture, "generalize_a_type_that_is_bounded_by_another_generalizable_type")
|
||||
{
|
||||
auto [t1, ft1] = freshType();
|
||||
|
@ -18,6 +18,11 @@ assert(os.date(string.rep("%d", 1000), t) ==
|
||||
assert(os.date(string.rep("%", 200)) == string.rep("%", 100))
|
||||
assert(os.date("", -1) == nil)
|
||||
|
||||
assert(os.time({ year = 1969, month = 12, day = 31, hour = 23, min = 59, sec = 59}) == nil) -- just before start
|
||||
assert(os.time({ year = 1970, month = 1, day = 1, hour = 0, min = 0, sec = 0}) == 0) -- start
|
||||
assert(os.time({ year = 3000, month = 12, day = 31, hour = 23, min = 59, sec = 59}) == 32535215999) -- just before Windows max range
|
||||
assert(os.time({ year = 1970, month = 1, day = 1, hour = 0, min = 0, sec = -1}) == nil) -- going before using time fields
|
||||
|
||||
local function checkDateTable (t)
|
||||
local D = os.date("!*t", t)
|
||||
assert(os.time(D) == t)
|
||||
|
@ -171,4 +171,38 @@ end
|
||||
|
||||
nilInvalidatesSlot()
|
||||
|
||||
local function arraySizeOpt1(a)
|
||||
a[1] += 2
|
||||
a[1] *= 3
|
||||
|
||||
table.insert(a, 3)
|
||||
table.insert(a, 4)
|
||||
table.insert(a, 5)
|
||||
table.insert(a, 6)
|
||||
|
||||
a[1] += 4
|
||||
a[1] *= 5
|
||||
|
||||
return a[1] + a[5]
|
||||
end
|
||||
|
||||
assert(arraySizeOpt1({1}) == 71)
|
||||
|
||||
local function arraySizeOpt2(a, i)
|
||||
a[i] += 2
|
||||
a[i] *= 3
|
||||
|
||||
table.insert(a, 3)
|
||||
table.insert(a, 4)
|
||||
table.insert(a, 5)
|
||||
table.insert(a, 6)
|
||||
|
||||
a[i] += 4
|
||||
a[i] *= 5
|
||||
|
||||
return a[i] + a[5]
|
||||
end
|
||||
|
||||
assert(arraySizeOpt1({1}, 1) == 71)
|
||||
|
||||
return('OK')
|
||||
|
@ -94,6 +94,8 @@ static int testAssertionHandler(const char* expr, const char* file, int line, co
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
struct BoostLikeReporter : doctest::IReporter
|
||||
{
|
||||
const doctest::TestCaseData* currentTest = nullptr;
|
||||
@ -255,6 +257,8 @@ int main(int argc, char** argv)
|
||||
{
|
||||
Luau::assertHandler() = testAssertionHandler;
|
||||
|
||||
|
||||
|
||||
doctest::registerReporter<BoostLikeReporter>("boost", 0, true);
|
||||
|
||||
doctest::Context context;
|
||||
|
@ -14,9 +14,12 @@ AutocompleteTest.type_correct_expected_argument_type_pack_suggestion
|
||||
AutocompleteTest.type_correct_expected_argument_type_suggestion
|
||||
AutocompleteTest.type_correct_expected_argument_type_suggestion_optional
|
||||
AutocompleteTest.type_correct_expected_argument_type_suggestion_self
|
||||
AutocompleteTest.type_correct_expected_return_type_pack_suggestion
|
||||
AutocompleteTest.type_correct_expected_return_type_suggestion
|
||||
AutocompleteTest.type_correct_function_no_parenthesis
|
||||
AutocompleteTest.type_correct_function_return_types
|
||||
AutocompleteTest.type_correct_keywords
|
||||
AutocompleteTest.type_correct_suggestion_for_overloads
|
||||
AutocompleteTest.type_correct_suggestion_in_argument
|
||||
AutocompleteTest.unsealed_table_2
|
||||
BuiltinTests.aliased_string_format
|
||||
@ -55,8 +58,35 @@ BuiltinTests.table_dot_remove_optionally_returns_generic
|
||||
BuiltinTests.table_freeze_is_generic
|
||||
BuiltinTests.table_insert_correctly_infers_type_of_array_2_args_overload
|
||||
BuiltinTests.table_insert_correctly_infers_type_of_array_3_args_overload
|
||||
BuiltinTests.table_pack_variadic
|
||||
BuiltinTests.trivial_select
|
||||
BuiltinTests.xpcall
|
||||
ControlFlowAnalysis.for_record_do_if_not_x_break
|
||||
ControlFlowAnalysis.for_record_do_if_not_x_continue
|
||||
ControlFlowAnalysis.if_not_x_break
|
||||
ControlFlowAnalysis.if_not_x_break_elif_not_y_break
|
||||
ControlFlowAnalysis.if_not_x_break_elif_not_y_continue
|
||||
ControlFlowAnalysis.if_not_x_break_elif_not_y_fallthrough_elif_not_z_break
|
||||
ControlFlowAnalysis.if_not_x_break_elif_rand_break_elif_not_y_break
|
||||
ControlFlowAnalysis.if_not_x_break_elif_rand_break_elif_not_y_fallthrough
|
||||
ControlFlowAnalysis.if_not_x_break_if_not_y_break
|
||||
ControlFlowAnalysis.if_not_x_break_if_not_y_continue
|
||||
ControlFlowAnalysis.if_not_x_continue
|
||||
ControlFlowAnalysis.if_not_x_continue_elif_not_y_continue
|
||||
ControlFlowAnalysis.if_not_x_continue_elif_not_y_fallthrough_elif_not_z_continue
|
||||
ControlFlowAnalysis.if_not_x_continue_elif_not_y_throw_elif_not_z_fallthrough
|
||||
ControlFlowAnalysis.if_not_x_continue_elif_rand_continue_elif_not_y_continue
|
||||
ControlFlowAnalysis.if_not_x_continue_elif_rand_continue_elif_not_y_fallthrough
|
||||
ControlFlowAnalysis.if_not_x_continue_if_not_y_continue
|
||||
ControlFlowAnalysis.if_not_x_continue_if_not_y_throw
|
||||
ControlFlowAnalysis.if_not_x_return_elif_not_y_break
|
||||
ControlFlowAnalysis.if_not_x_return_elif_not_y_fallthrough_elif_not_z_break
|
||||
ControlFlowAnalysis.prototyping_and_visiting_alias_has_the_same_scope_breaking
|
||||
ControlFlowAnalysis.prototyping_and_visiting_alias_has_the_same_scope_continuing
|
||||
ControlFlowAnalysis.tagged_unions_breaking
|
||||
ControlFlowAnalysis.tagged_unions_continuing
|
||||
ControlFlowAnalysis.type_alias_does_not_leak_out_breaking
|
||||
ControlFlowAnalysis.type_alias_does_not_leak_out_continuing
|
||||
DefinitionTests.class_definition_indexer
|
||||
DefinitionTests.class_definition_overload_metamethods
|
||||
DefinitionTests.class_definition_string_props
|
||||
@ -81,7 +111,6 @@ GenericsTests.generic_argument_count_too_many
|
||||
GenericsTests.generic_functions_dont_cache_type_parameters
|
||||
GenericsTests.generic_functions_should_be_memory_safe
|
||||
GenericsTests.generic_type_pack_parentheses
|
||||
GenericsTests.generic_type_pack_unification2
|
||||
GenericsTests.higher_rank_polymorphism_should_not_accept_instantiated_arguments
|
||||
GenericsTests.hof_subtype_instantiation_regression
|
||||
GenericsTests.infer_generic_function_function_argument
|
||||
@ -90,9 +119,6 @@ GenericsTests.infer_generic_function_function_argument_3
|
||||
GenericsTests.infer_generic_function_function_argument_overloaded
|
||||
GenericsTests.infer_generic_lib_function_function_argument
|
||||
GenericsTests.infer_generic_property
|
||||
GenericsTests.instantiate_cyclic_generic_function
|
||||
GenericsTests.instantiate_generic_function_in_assignments
|
||||
GenericsTests.instantiate_generic_function_in_assignments2
|
||||
GenericsTests.instantiated_function_argument_names
|
||||
GenericsTests.mutable_state_polymorphism
|
||||
GenericsTests.no_stack_overflow_from_quantifying
|
||||
@ -135,7 +161,6 @@ RefinementTest.isa_type_refinement_must_be_known_ahead_of_time
|
||||
RefinementTest.narrow_property_of_a_bounded_variable
|
||||
RefinementTest.nonoptional_type_can_narrow_to_nil_if_sense_is_true
|
||||
RefinementTest.not_t_or_some_prop_of_t
|
||||
RefinementTest.refine_a_param_that_got_resolved_during_constraint_solving_stage_2
|
||||
RefinementTest.refine_a_property_of_some_global
|
||||
RefinementTest.truthy_constraint_on_properties
|
||||
RefinementTest.type_narrow_to_vector
|
||||
@ -193,9 +218,9 @@ TableTests.shared_selfs
|
||||
TableTests.shared_selfs_from_free_param
|
||||
TableTests.shared_selfs_through_metatables
|
||||
TableTests.table_call_metamethod_basic
|
||||
TableTests.table_call_metamethod_generic
|
||||
TableTests.table_param_width_subtyping_1
|
||||
TableTests.table_param_width_subtyping_2
|
||||
TableTests.table_param_width_subtyping_3
|
||||
TableTests.table_simple_call
|
||||
TableTests.table_subtyping_with_extra_props_dont_report_multiple_errors
|
||||
TableTests.table_subtyping_with_missing_props_dont_report_multiple_errors
|
||||
@ -213,9 +238,7 @@ ToString.named_metatable_toStringNamedFunction
|
||||
ToString.pick_distinct_names_for_mixed_explicit_and_implicit_generics
|
||||
ToString.toStringDetailed2
|
||||
ToString.toStringErrorPack
|
||||
ToString.toStringGenericPack
|
||||
ToString.toStringNamedFunction_generic_pack
|
||||
ToString.toStringNamedFunction_map
|
||||
TryUnifyTests.members_of_failed_typepack_unification_are_unified_with_errorType
|
||||
TryUnifyTests.result_of_failed_typepack_unification_is_constrained
|
||||
TryUnifyTests.typepack_unification_should_trim_free_tails
|
||||
@ -239,8 +262,7 @@ TypeFamilyTests.function_internal_families
|
||||
TypeFamilyTests.internal_families_raise_errors
|
||||
TypeFamilyTests.table_internal_families
|
||||
TypeFamilyTests.unsolvable_family
|
||||
TypeInfer.be_sure_to_use_active_txnlog_when_evaluating_a_variadic_overload
|
||||
TypeInfer.bidirectional_checking_of_higher_order_function
|
||||
TypeInfer.bidirectional_checking_of_callback_property
|
||||
TypeInfer.check_expr_recursion_limit
|
||||
TypeInfer.check_type_infer_recursion_count
|
||||
TypeInfer.cli_39932_use_unifier_in_ensure_methods
|
||||
@ -249,14 +271,12 @@ TypeInfer.dont_report_type_errors_within_an_AstExprError
|
||||
TypeInfer.dont_report_type_errors_within_an_AstStatError
|
||||
TypeInfer.fuzz_free_table_type_change_during_index_check
|
||||
TypeInfer.infer_assignment_value_types_mutable_lval
|
||||
TypeInfer.infer_locals_via_assignment_from_its_call_site
|
||||
TypeInfer.interesting_local_type_inference_case
|
||||
TypeInfer.no_stack_overflow_from_isoptional
|
||||
TypeInfer.promote_tail_type_packs
|
||||
TypeInfer.recursive_function_that_invokes_itself_with_a_refinement_of_its_parameter
|
||||
TypeInfer.recursive_function_that_invokes_itself_with_a_refinement_of_its_parameter_2
|
||||
TypeInfer.tc_after_error_recovery_no_replacement_name_in_error
|
||||
TypeInfer.type_infer_cache_limit_normalizer
|
||||
TypeInfer.tc_if_else_expressions_expected_type_3
|
||||
TypeInfer.type_infer_recursion_limit_no_ice
|
||||
TypeInfer.type_infer_recursion_limit_normalizer
|
||||
TypeInferAnyError.can_subscript_any
|
||||
@ -274,7 +294,6 @@ TypeInferClasses.class_type_mismatch_with_name_conflict
|
||||
TypeInferClasses.detailed_class_unification_error
|
||||
TypeInferClasses.index_instance_property
|
||||
TypeInferClasses.table_class_unification_reports_sane_errors_for_missing_properties
|
||||
TypeInferClasses.table_indexers_are_invariant
|
||||
TypeInferFunctions.apply_of_lambda_with_inferred_and_explicit_types
|
||||
TypeInferFunctions.cannot_hoist_interior_defns_into_signature
|
||||
TypeInferFunctions.dont_assert_when_the_tarjan_limit_is_exceeded_during_generalization
|
||||
@ -295,7 +314,6 @@ TypeInferFunctions.infer_anonymous_function_arguments
|
||||
TypeInferFunctions.infer_generic_function_function_argument
|
||||
TypeInferFunctions.infer_generic_function_function_argument_overloaded
|
||||
TypeInferFunctions.infer_generic_lib_function_function_argument
|
||||
TypeInferFunctions.infer_higher_order_function
|
||||
TypeInferFunctions.infer_return_type_from_selected_overload
|
||||
TypeInferFunctions.infer_that_function_does_not_return_a_table
|
||||
TypeInferFunctions.it_is_ok_to_oversaturate_a_higher_order_function_argument
|
||||
@ -336,6 +354,7 @@ TypeInferLoops.properly_infer_iteratee_is_a_free_table
|
||||
TypeInferLoops.unreachable_code_after_infinite_loop
|
||||
TypeInferLoops.varlist_declared_by_for_in_loop_should_be_free
|
||||
TypeInferModules.bound_free_table_export_is_ok
|
||||
TypeInferModules.do_not_modify_imported_types_4
|
||||
TypeInferModules.do_not_modify_imported_types_5
|
||||
TypeInferModules.module_type_conflict
|
||||
TypeInferModules.module_type_conflict_instantiated
|
||||
@ -358,6 +377,7 @@ TypeInferOperators.concat_op_on_string_lhs_and_free_rhs
|
||||
TypeInferOperators.disallow_string_and_types_without_metatables_from_arithmetic_binary_ops
|
||||
TypeInferOperators.luau_polyfill_is_array
|
||||
TypeInferOperators.operator_eq_completely_incompatible
|
||||
TypeInferOperators.reducing_and
|
||||
TypeInferOperators.strict_binary_op_where_lhs_unknown
|
||||
TypeInferOperators.typecheck_overloaded_multiply_that_is_an_intersection
|
||||
TypeInferOperators.typecheck_overloaded_multiply_that_is_an_intersection_on_rhs
|
||||
@ -370,7 +390,7 @@ TypeInferPrimitives.CheckMethodsOfNumber
|
||||
TypeInferPrimitives.string_index
|
||||
TypeInferUnknownNever.length_of_never
|
||||
TypeInferUnknownNever.math_operators_and_never
|
||||
TypePackTests.higher_order_function
|
||||
TypePackTests.fuzz_typepack_iter_follow_2
|
||||
TypePackTests.pack_tail_unification_check
|
||||
TypePackTests.type_alias_backwards_compatible
|
||||
TypePackTests.type_alias_default_type_errors
|
||||
@ -378,6 +398,7 @@ TypePackTests.type_packs_with_tails_in_vararg_adjustment
|
||||
TypeSingletons.function_args_infer_singletons
|
||||
TypeSingletons.function_call_with_singletons
|
||||
TypeSingletons.function_call_with_singletons_mismatch
|
||||
TypeSingletons.overloaded_function_call_with_singletons
|
||||
TypeSingletons.return_type_of_f_is_not_widened
|
||||
TypeSingletons.table_properties_type_error_escapes
|
||||
TypeSingletons.widen_the_supertype_if_it_is_free_and_subtype_has_singleton
|
||||
@ -385,3 +406,4 @@ TypeSingletons.widening_happens_almost_everywhere
|
||||
UnionTypes.index_on_a_union_type_with_missing_property
|
||||
UnionTypes.less_greedy_unification_with_union_types
|
||||
UnionTypes.table_union_write_indirect
|
||||
UnionTypes.unify_unsealed_table_union_check
|
||||
|
Loading…
Reference in New Issue
Block a user