Sync to upstream/release/562 (#828)

* Fixed rare use-after-free in analysis during table unification

A lot of work these past months went into two new Luau components:
* A near full rewrite of the typechecker using a new deferred constraint
resolution system
* Native code generation for AoT/JiT compilation of VM bytecode into x64
(avx)/arm64 instructions

Both of these components are far from finished and we don't provide
documentation on building and using them at this point.
However, curious community members expressed interest in learning about
changes that go into these components each week, so we are now listing
them here in the 'sync' pull request descriptions.

---
New typechecker can be enabled by setting
DebugLuauDeferredConstraintResolution flag to 'true'.
It is considered unstable right now, so try it at your own risk.
Even though it already provides better type inference than the current
one in some cases, our main goal right now is to reach feature parity
with current typechecker.
Features which improve over the capabilities of the current typechecker
are marked as '(NEW)'.

Changes to new typechecker:
* Regular for loop index and parameters are now typechecked
* Invalid type annotations on local variables are ignored to improve
autocomplete
* Fixed missing autocomplete type suggestions for function arguments
* Type reduction is now performed to produce simpler types to be
presented to the user (error messages, custom LSPs)
* Internally, complex types like '((number | string) & ~(false?)) |
string' can be produced, which is just 'string | number' when simplified
* Fixed spots where support for unknown and never types was missing
* (NEW) Length operator '#' is now valid to use on top table type, this
type comes up when doing typeof(x) == "table" guards and isn't available
in current typechecker

---
Changes to native code generation:
* Additional math library fast calls are now lowered to x64: math.ldexp,
math.round, math.frexp, math.modf, math.sign and math.clamp
This commit is contained in:
vegorov-rbx 2023-02-03 21:26:13 +02:00 committed by GitHub
parent f763f4c948
commit 62483d40f0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
67 changed files with 1382 additions and 1203 deletions

View file

@ -1,70 +0,0 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "Luau/Def.h"
#include "Luau/TypedAllocator.h"
#include "Luau/Variant.h"
#include <memory>
namespace Luau
{
struct Type;
using TypeId = const Type*;
struct Negation;
struct Conjunction;
struct Disjunction;
struct Equivalence;
struct Proposition;
using Connective = Variant<Negation, Conjunction, Disjunction, Equivalence, Proposition>;
using ConnectiveId = Connective*; // Can and most likely is nullptr.
struct Negation
{
ConnectiveId connective;
};
struct Conjunction
{
ConnectiveId lhs;
ConnectiveId rhs;
};
struct Disjunction
{
ConnectiveId lhs;
ConnectiveId rhs;
};
struct Equivalence
{
ConnectiveId lhs;
ConnectiveId rhs;
};
struct Proposition
{
DefId def;
TypeId discriminantTy;
};
template<typename T>
const T* get(ConnectiveId connective)
{
return get_if<T>(connective);
}
struct ConnectiveArena
{
TypedAllocator<Connective> allocator;
ConnectiveId negation(ConnectiveId connective);
ConnectiveId conjunction(ConnectiveId lhs, ConnectiveId rhs);
ConnectiveId disjunction(ConnectiveId lhs, ConnectiveId rhs);
ConnectiveId equivalence(ConnectiveId lhs, ConnectiveId rhs);
ConnectiveId proposition(DefId def, TypeId discriminantTy);
};
} // namespace Luau

View file

@ -71,9 +71,9 @@ struct BinaryConstraint
// When we dispatch this constraint, we update the key at this map to record
// the overload that we selected.
const void* astFragment;
DenseHashMap<const void*, TypeId>* astOriginalCallTypes;
DenseHashMap<const void*, TypeId>* astOverloadResolvedTypes;
const AstNode* astFragment;
DenseHashMap<const AstNode*, TypeId>* astOriginalCallTypes;
DenseHashMap<const AstNode*, TypeId>* astOverloadResolvedTypes;
};
// iteratee is iterable

View file

@ -2,7 +2,7 @@
#pragma once
#include "Luau/Ast.h"
#include "Luau/Connective.h"
#include "Luau/Refinement.h"
#include "Luau/Constraint.h"
#include "Luau/DataFlowGraph.h"
#include "Luau/Module.h"
@ -27,13 +27,13 @@ struct DcrLogger;
struct Inference
{
TypeId ty = nullptr;
ConnectiveId connective = nullptr;
RefinementId refinement = nullptr;
Inference() = default;
explicit Inference(TypeId ty, ConnectiveId connective = nullptr)
explicit Inference(TypeId ty, RefinementId refinement = nullptr)
: ty(ty)
, connective(connective)
, refinement(refinement)
{
}
};
@ -41,13 +41,13 @@ struct Inference
struct InferencePack
{
TypePackId tp = nullptr;
std::vector<ConnectiveId> connectives;
std::vector<RefinementId> refinements;
InferencePack() = default;
explicit InferencePack(TypePackId tp, const std::vector<ConnectiveId>& connectives = {})
explicit InferencePack(TypePackId tp, const std::vector<RefinementId>& refinements = {})
: tp(tp)
, connectives(connectives)
, refinements(refinements)
{
}
};
@ -74,35 +74,11 @@ struct ConstraintGraphBuilder
// will enqueue them during solving.
std::vector<ConstraintPtr> unqueuedConstraints;
// A mapping of AST node to TypeId.
DenseHashMap<const AstExpr*, TypeId> astTypes{nullptr};
// A mapping of AST node to TypePackId.
DenseHashMap<const AstExpr*, TypePackId> astTypePacks{nullptr};
DenseHashMap<const AstExpr*, TypeId> astExpectedTypes{nullptr};
// If the node was applied as a function, this is the unspecialized type of
// that expression.
DenseHashMap<const void*, TypeId> astOriginalCallTypes{nullptr};
// If overload resolution was performed on this element, this is the
// overload that was selected.
DenseHashMap<const void*, TypeId> astOverloadResolvedTypes{nullptr};
// Types resolved from type annotations. Analogous to astTypes.
DenseHashMap<const AstType*, TypeId> astResolvedTypes{nullptr};
// Type packs resolved from type annotations. Analogous to astTypePacks.
DenseHashMap<const AstTypePack*, TypePackId> astResolvedTypePacks{nullptr};
// Defining scopes for AST nodes.
// The private scope of type aliases for which the type parameters belong to.
DenseHashMap<const AstStatTypeAlias*, ScopePtr> astTypeAliasDefiningScopes{nullptr};
NotNull<const DataFlowGraph> dfg;
ConnectiveArena connectiveArena;
RefinementArena refinementArena;
int recursionCount = 0;
@ -156,7 +132,7 @@ struct ConstraintGraphBuilder
*/
NotNull<Constraint> addConstraint(const ScopePtr& scope, std::unique_ptr<Constraint> c);
void applyRefinements(const ScopePtr& scope, Location location, ConnectiveId connective);
void applyRefinements(const ScopePtr& scope, Location location, RefinementId refinement);
/**
* The entry point to the ConstraintGraphBuilder. This will construct a set
@ -213,7 +189,7 @@ struct ConstraintGraphBuilder
Inference check(const ScopePtr& scope, AstExprTypeAssertion* typeAssert);
Inference check(const ScopePtr& scope, AstExprInterpString* interpString);
Inference check(const ScopePtr& scope, AstExprTable* expr, std::optional<TypeId> expectedType);
std::tuple<TypeId, TypeId, ConnectiveId> checkBinary(const ScopePtr& scope, AstExprBinary* binary, std::optional<TypeId> expectedType);
std::tuple<TypeId, TypeId, RefinementId> checkBinary(const ScopePtr& scope, AstExprBinary* binary, std::optional<TypeId> expectedType);
TypePackId checkLValues(const ScopePtr& scope, AstArray<AstExpr*> exprs);

View file

@ -74,14 +74,13 @@ struct Module
DenseHashMap<const AstExpr*, TypePackId> astTypePacks{nullptr};
DenseHashMap<const AstExpr*, TypeId> astExpectedTypes{nullptr};
// Pointers are either AstExpr or AstStat.
DenseHashMap<const void*, TypeId> astOriginalCallTypes{nullptr};
// Pointers are either AstExpr or AstStat.
DenseHashMap<const void*, TypeId> astOverloadResolvedTypes{nullptr};
DenseHashMap<const AstNode*, TypeId> astOriginalCallTypes{nullptr};
DenseHashMap<const AstNode*, TypeId> astOverloadResolvedTypes{nullptr};
DenseHashMap<const AstType*, TypeId> astResolvedTypes{nullptr};
DenseHashMap<const AstType*, TypeId> astOriginalResolvedTypes{nullptr};
DenseHashMap<const AstTypePack*, TypePackId> astResolvedTypePacks{nullptr};
// Map AST nodes to the scope they create. Cannot be NotNull<Scope> because we need a sentinel value for the map.
DenseHashMap<const AstNode*, Scope*> astScopes{nullptr};

View file

@ -0,0 +1,68 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "Luau/Def.h"
#include "Luau/TypedAllocator.h"
#include "Luau/Variant.h"
namespace Luau
{
struct Type;
using TypeId = const Type*;
struct Negation;
struct Conjunction;
struct Disjunction;
struct Equivalence;
struct Proposition;
using Refinement = Variant<Negation, Conjunction, Disjunction, Equivalence, Proposition>;
using RefinementId = Refinement*; // Can and most likely is nullptr.
struct Negation
{
RefinementId refinement;
};
struct Conjunction
{
RefinementId lhs;
RefinementId rhs;
};
struct Disjunction
{
RefinementId lhs;
RefinementId rhs;
};
struct Equivalence
{
RefinementId lhs;
RefinementId rhs;
};
struct Proposition
{
DefId def;
TypeId discriminantTy;
};
template<typename T>
const T* get(RefinementId refinement)
{
return get_if<T>(refinement);
}
struct RefinementArena
{
TypedAllocator<Refinement> allocator;
RefinementId negation(RefinementId refinement);
RefinementId conjunction(RefinementId lhs, RefinementId rhs);
RefinementId disjunction(RefinementId lhs, RefinementId rhs);
RefinementId equivalence(RefinementId lhs, RefinementId rhs);
RefinementId proposition(DefId def, TypeId discriminantTy);
};
} // namespace Luau

View file

@ -3,7 +3,7 @@
#include "Luau/Ast.h"
#include "Luau/Common.h"
#include "Luau/Connective.h"
#include "Luau/Refinement.h"
#include "Luau/DataFlowGraph.h"
#include "Luau/DenseHash.h"
#include "Luau/Def.h"
@ -266,12 +266,12 @@ struct MagicRefinementContext
ScopePtr scope;
NotNull<struct ConstraintGraphBuilder> cgb;
NotNull<const DataFlowGraph> dfg;
NotNull<ConnectiveArena> connectiveArena;
std::vector<ConnectiveId> argumentConnectives;
NotNull<RefinementArena> refinementArena;
std::vector<RefinementId> argumentRefinements;
const class AstExprCall* callSite;
};
using DcrMagicRefinement = std::vector<ConnectiveId> (*)(const MagicRefinementContext&);
using DcrMagicRefinement = std::vector<RefinementId> (*)(const MagicRefinementContext&);
struct FunctionType
{

View file

@ -32,11 +32,23 @@ struct TypeReduction
explicit TypeReduction(
NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, NotNull<InternalErrorReporter> handle, const TypeReductionOptions& opts = {});
TypeReduction(const TypeReduction&) = delete;
TypeReduction& operator=(const TypeReduction&) = delete;
TypeReduction(TypeReduction&&) = default;
TypeReduction& operator=(TypeReduction&&) = default;
std::optional<TypeId> reduce(TypeId ty);
std::optional<TypePackId> reduce(TypePackId tp);
std::optional<TypeFun> reduce(const TypeFun& fun);
/// Creating a child TypeReduction will allow the parent TypeReduction to share its memoization with the child TypeReductions.
/// This is safe as long as the parent's TypeArena continues to outlive both TypeReduction memoization.
TypeReduction fork(NotNull<TypeArena> arena, const TypeReductionOptions& opts = {}) const;
private:
const TypeReduction* parent = nullptr;
NotNull<TypeArena> arena;
NotNull<BuiltinTypes> builtinTypes;
NotNull<struct InternalErrorReporter> handle;
@ -50,6 +62,9 @@ private:
bool hasExceededCartesianProductLimit(TypeId ty) const;
bool hasExceededCartesianProductLimit(TypePackId tp) const;
std::optional<TypeId> memoizedof(TypeId ty) const;
std::optional<TypePackId> memoizedof(TypePackId tp) const;
};
} // namespace Luau

View file

@ -95,8 +95,7 @@ private:
void tryUnifyScalarShape(TypeId subTy, TypeId superTy, bool reversed);
void tryUnifyWithMetatable(TypeId subTy, TypeId superTy, bool reversed);
void tryUnifyWithClass(TypeId subTy, TypeId superTy, bool reversed);
void tryUnifyTypeWithNegation(TypeId subTy, TypeId superTy);
void tryUnifyNegationWithType(TypeId subTy, TypeId superTy);
void tryUnifyNegations(TypeId subTy, TypeId superTy);
TypePackId tryApplyOverloadedFunction(TypeId function, const NormalizedFunctionType& overloads, TypePackId args);

View file

@ -7,13 +7,13 @@
#include "Luau/ToString.h"
#include "Luau/TypeInfer.h"
#include "Luau/TypePack.h"
#include "Luau/TypeReduction.h"
#include <algorithm>
#include <unordered_set>
#include <utility>
LUAU_FASTFLAGVARIABLE(LuauCompleteTableKeysBetter, false);
LUAU_FASTFLAGVARIABLE(LuauFixAutocompleteInIf, false);
LUAU_FASTFLAGVARIABLE(LuauFixAutocompleteInWhile, false);
LUAU_FASTFLAGVARIABLE(LuauFixAutocompleteInFor, false);
LUAU_FASTFLAGVARIABLE(LuauAutocompleteStringContent, false);
@ -1534,9 +1534,7 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
}
else if (AstStatIf* statIf = extractStat<AstStatIf>(ancestry);
statIf && (!statIf->thenLocation || statIf->thenLocation->containsClosed(position)) &&
(!FFlag::LuauFixAutocompleteInIf || (statIf->condition && !statIf->condition->location.containsClosed(position))))
{
if (FFlag::LuauFixAutocompleteInIf)
(statIf->condition && !statIf->condition->location.containsClosed(position)))
{
AutocompleteEntryMap ret;
ret["then"] = {AutocompleteEntryKind::Keyword};
@ -1544,11 +1542,6 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
ret["or"] = {AutocompleteEntryKind::Keyword};
return {std::move(ret), ancestry, AutocompleteContext::Keyword};
}
else
{
return {{{"then", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword};
}
}
else if (AstStatRepeat* statRepeat = node->as<AstStatRepeat>(); statRepeat && statRepeat->condition->is<AstExprError>())
return autocompleteExpression(sourceModule, *module, builtinTypes, typeArena, ancestry, position);
else if (AstStatRepeat* statRepeat = extractStat<AstStatRepeat>(ancestry); statRepeat)
@ -1671,7 +1664,6 @@ AutocompleteResult autocomplete(Frontend& frontend, const ModuleName& moduleName
return {};
ModulePtr module = frontend.moduleResolverForAutocomplete.getModule(moduleName);
if (!module)
return {};

View file

@ -42,7 +42,7 @@ static bool dcrMagicFunctionSelect(MagicFunctionCallContext context);
static bool dcrMagicFunctionRequire(MagicFunctionCallContext context);
static bool dcrMagicFunctionPack(MagicFunctionCallContext context);
static std::vector<ConnectiveId> dcrMagicRefinementAssert(const MagicRefinementContext& context);
static std::vector<RefinementId> dcrMagicRefinementAssert(const MagicRefinementContext& context);
TypeId makeUnion(TypeArena& arena, std::vector<TypeId>&& types)
{
@ -624,12 +624,12 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionAssert(
return WithPredicate<TypePackId>{arena.addTypePack(TypePack{std::move(head), tail})};
}
static std::vector<ConnectiveId> dcrMagicRefinementAssert(const MagicRefinementContext& ctx)
static std::vector<RefinementId> dcrMagicRefinementAssert(const MagicRefinementContext& ctx)
{
if (ctx.argumentConnectives.empty())
if (ctx.argumentRefinements.empty())
return {};
ctx.cgb->applyRefinements(ctx.scope, ctx.callSite->location, ctx.argumentConnectives[0]);
ctx.cgb->applyRefinements(ctx.scope, ctx.callSite->location, ctx.argumentRefinements[0]);
return {};
}

View file

@ -191,16 +191,16 @@ static void unionRefinements(const std::unordered_map<DefId, TypeId>& lhs, const
}
}
static void computeRefinement(const ScopePtr& scope, ConnectiveId connective, std::unordered_map<DefId, TypeId>* refis, bool sense,
static void computeRefinement(const ScopePtr& scope, RefinementId refinement, std::unordered_map<DefId, TypeId>* refis, bool sense,
NotNull<TypeArena> arena, bool eq, std::vector<ConstraintV>* constraints)
{
using RefinementMap = std::unordered_map<DefId, TypeId>;
if (!connective)
if (!refinement)
return;
else if (auto negation = get<Negation>(connective))
return computeRefinement(scope, negation->connective, refis, !sense, arena, eq, constraints);
else if (auto conjunction = get<Conjunction>(connective))
else if (auto negation = get<Negation>(refinement))
return computeRefinement(scope, negation->refinement, refis, !sense, arena, eq, constraints);
else if (auto conjunction = get<Conjunction>(refinement))
{
RefinementMap lhsRefis;
RefinementMap rhsRefis;
@ -211,7 +211,7 @@ static void computeRefinement(const ScopePtr& scope, ConnectiveId connective, st
if (!sense)
unionRefinements(lhsRefis, rhsRefis, *refis, arena);
}
else if (auto disjunction = get<Disjunction>(connective))
else if (auto disjunction = get<Disjunction>(refinement))
{
RefinementMap lhsRefis;
RefinementMap rhsRefis;
@ -222,12 +222,12 @@ static void computeRefinement(const ScopePtr& scope, ConnectiveId connective, st
if (sense)
unionRefinements(lhsRefis, rhsRefis, *refis, arena);
}
else if (auto equivalence = get<Equivalence>(connective))
else if (auto equivalence = get<Equivalence>(refinement))
{
computeRefinement(scope, equivalence->lhs, refis, sense, arena, true, constraints);
computeRefinement(scope, equivalence->rhs, refis, sense, arena, true, constraints);
}
else if (auto proposition = get<Proposition>(connective))
else if (auto proposition = get<Proposition>(refinement))
{
TypeId discriminantTy = proposition->discriminantTy;
if (!sense && !eq)
@ -264,14 +264,14 @@ static std::pair<DefId, TypeId> computeDiscriminantType(NotNull<TypeArena> arena
return {def, discriminantTy};
}
void ConstraintGraphBuilder::applyRefinements(const ScopePtr& scope, Location location, ConnectiveId connective)
void ConstraintGraphBuilder::applyRefinements(const ScopePtr& scope, Location location, RefinementId refinement)
{
if (!connective)
if (!refinement)
return;
std::unordered_map<DefId, TypeId> refinements;
std::vector<ConstraintV> constraints;
computeRefinement(scope, connective, &refinements, /*sense*/ true, arena, /*eq*/ false, &constraints);
computeRefinement(scope, refinement, &refinements, /*sense*/ true, arena, /*eq*/ false, &constraints);
for (auto [def, discriminantTy] : refinements)
{
@ -559,7 +559,10 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocal* local)
void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFor* for_)
{
auto checkNumber = [&](AstExpr* expr) {
if (for_->var->annotation)
resolveType(scope, for_->var->annotation, /* inTypeArguments */ false);
auto inferNumber = [&](AstExpr* expr) {
if (!expr)
return;
@ -567,9 +570,9 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFor* for_)
addConstraint(scope, expr->location, SubtypeConstraint{t, builtinTypes->numberType});
};
checkNumber(for_->from);
checkNumber(for_->to);
checkNumber(for_->step);
inferNumber(for_->from);
inferNumber(for_->to);
inferNumber(for_->step);
ScopePtr forScope = childScope(for_, scope);
forScope->bindings[for_->var] = Binding{builtinTypes->numberType, for_->var->location};
@ -770,23 +773,23 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatCompoundAssign*
TypeId resultType = arena->addType(BlockedType{});
addConstraint(scope, assign->location,
BinaryConstraint{assign->op, varId, valueInf.ty, resultType, assign, &astOriginalCallTypes, &astOverloadResolvedTypes});
BinaryConstraint{assign->op, varId, valueInf.ty, resultType, assign, &module->astOriginalCallTypes, &module->astOverloadResolvedTypes});
addConstraint(scope, assign->location, SubtypeConstraint{resultType, varId});
}
void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatIf* ifStatement)
{
ScopePtr condScope = childScope(ifStatement->condition, scope);
auto [_, connective] = check(condScope, ifStatement->condition, std::nullopt);
auto [_, refinement] = check(condScope, ifStatement->condition, std::nullopt);
ScopePtr thenScope = childScope(ifStatement->thenbody, scope);
applyRefinements(thenScope, Location{}, connective);
applyRefinements(thenScope, Location{}, refinement);
visit(thenScope, ifStatement->thenbody);
if (ifStatement->elsebody)
{
ScopePtr elseScope = childScope(ifStatement->elsebody, scope);
applyRefinements(elseScope, Location{}, connectiveArena.negation(connective));
applyRefinements(elseScope, Location{}, refinementArena.negation(refinement));
visit(elseScope, ifStatement->elsebody);
}
}
@ -1049,7 +1052,7 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExpr*
}
LUAU_ASSERT(result.tp);
astTypePacks[expr] = result.tp;
module->astTypePacks[expr] = result.tp;
return result;
}
@ -1096,7 +1099,7 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCa
std::vector<TypeId> args;
std::optional<TypePackId> argTail;
std::vector<ConnectiveId> argumentConnectives;
std::vector<RefinementId> argumentRefinements;
Checkpoint argCheckpoint = checkpoint(this);
@ -1113,7 +1116,7 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCa
// computing fnType. If computing that did not cause us to exceed a
// recursion limit, we can fetch it from astTypes rather than
// recomputing it.
TypeId* selfTy = astTypes.find(exprArgs[0]);
TypeId* selfTy = module->astTypes.find(exprArgs[0]);
if (selfTy)
args.push_back(*selfTy);
else
@ -1121,9 +1124,9 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCa
}
else if (i < exprArgs.size() - 1 || !(arg->is<AstExprCall>() || arg->is<AstExprVarargs>()))
{
auto [ty, connective] = check(scope, arg, expectedType);
auto [ty, refinement] = check(scope, arg, expectedType);
args.push_back(ty);
argumentConnectives.push_back(connective);
argumentRefinements.push_back(refinement);
}
else
argTail = checkPack(scope, arg, {}).tp; // FIXME? not sure about expectedTypes here
@ -1137,11 +1140,11 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCa
constraint->dependencies.push_back(extractArgsConstraint);
});
std::vector<ConnectiveId> returnConnectives;
std::vector<RefinementId> returnRefinements;
if (auto ftv = get<FunctionType>(follow(fnType)); ftv && ftv->dcrMagicRefinement)
{
MagicRefinementContext ctx{scope, NotNull{this}, dfg, NotNull{&connectiveArena}, std::move(argumentConnectives), call};
returnConnectives = ftv->dcrMagicRefinement(ctx);
MagicRefinementContext ctx{scope, NotNull{this}, dfg, NotNull{&refinementArena}, std::move(argumentRefinements), call};
returnRefinements = ftv->dcrMagicRefinement(ctx);
}
if (matchSetmetatable(*call))
@ -1169,11 +1172,11 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCa
}
return InferencePack{arena->addTypePack({resultTy}), std::move(returnConnectives)};
return InferencePack{arena->addTypePack({resultTy}), std::move(returnRefinements)};
}
else
{
astOriginalCallTypes[call->func] = fnType;
module->astOriginalCallTypes[call->func] = fnType;
TypeId instantiatedType = arena->addType(BlockedType{});
// TODO: How do expectedTypes play into this? Do they?
@ -1208,7 +1211,7 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCa
fcc->dependencies.emplace_back(constraint.get());
});
return InferencePack{rets, std::move(returnConnectives)};
return InferencePack{rets, std::move(returnRefinements)};
}
}
@ -1261,7 +1264,7 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExpr* expr, st
gc->dependencies.emplace_back(constraint.get());
});
return Inference{generalizedTy};
result = Inference{generalizedTy};
}
else if (auto indexName = expr->as<AstExprIndexName>())
result = check(scope, indexName);
@ -1294,9 +1297,9 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExpr* expr, st
}
LUAU_ASSERT(result.ty);
astTypes[expr] = result.ty;
module->astTypes[expr] = result.ty;
if (expectedType)
astExpectedTypes[expr] = *expectedType;
module->astExpectedTypes[expr] = *expectedType;
return result;
}
@ -1366,7 +1369,7 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprLocal* loc
return Inference{builtinTypes->errorRecoveryType()}; // TODO: replace with ice, locals should never exist before its definition.
if (def)
return Inference{*resultTy, connectiveArena.proposition(*def, builtinTypes->truthyType)};
return Inference{*resultTy, refinementArena.proposition(*def, builtinTypes->truthyType)};
else
return Inference{*resultTy};
}
@ -1456,7 +1459,7 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIndexName*
if (def)
{
if (auto ty = scope->lookup(*def))
return Inference{*ty, connectiveArena.proposition(*def, builtinTypes->truthyType)};
return Inference{*ty, refinementArena.proposition(*def, builtinTypes->truthyType)};
else
scope->dcrRefinements[*def] = result;
}
@ -1470,7 +1473,7 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIndexName*
addConstraint(scope, indexName->expr->location, SubtypeConstraint{obj, expectedTableType});
if (def)
return Inference{result, connectiveArena.proposition(*def, builtinTypes->truthyType)};
return Inference{result, refinementArena.proposition(*def, builtinTypes->truthyType)};
else
return Inference{result};
}
@ -1492,48 +1495,40 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIndexExpr*
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprUnary* unary)
{
auto [operandType, connective] = check(scope, unary->expr);
auto [operandType, refinement] = check(scope, unary->expr);
TypeId resultType = arena->addType(BlockedType{});
addConstraint(scope, unary->location, UnaryConstraint{unary->op, operandType, resultType});
if (unary->op == AstExprUnary::Not)
return Inference{resultType, connectiveArena.negation(connective)};
return Inference{resultType, refinementArena.negation(refinement)};
else
return Inference{resultType};
}
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprBinary* binary, std::optional<TypeId> expectedType)
{
auto [leftType, rightType, connective] = checkBinary(scope, binary, expectedType);
auto [leftType, rightType, refinement] = checkBinary(scope, binary, expectedType);
TypeId resultType = arena->addType(BlockedType{});
addConstraint(scope, binary->location,
BinaryConstraint{binary->op, leftType, rightType, resultType, binary, &astOriginalCallTypes, &astOverloadResolvedTypes});
return Inference{resultType, std::move(connective)};
BinaryConstraint{binary->op, leftType, rightType, resultType, binary, &module->astOriginalCallTypes, &module->astOverloadResolvedTypes});
return Inference{resultType, std::move(refinement)};
}
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIfElse* ifElse, std::optional<TypeId> expectedType)
{
ScopePtr condScope = childScope(ifElse->condition, scope);
auto [_, connective] = check(scope, ifElse->condition);
auto [_, refinement] = check(scope, ifElse->condition);
ScopePtr thenScope = childScope(ifElse->trueExpr, scope);
applyRefinements(thenScope, ifElse->trueExpr->location, connective);
applyRefinements(thenScope, ifElse->trueExpr->location, refinement);
TypeId thenType = check(thenScope, ifElse->trueExpr, expectedType).ty;
ScopePtr elseScope = childScope(ifElse->falseExpr, scope);
applyRefinements(elseScope, ifElse->falseExpr->location, connectiveArena.negation(connective));
applyRefinements(elseScope, ifElse->falseExpr->location, refinementArena.negation(refinement));
TypeId elseType = check(elseScope, ifElse->falseExpr, expectedType).ty;
if (ifElse->hasElse)
{
TypeId resultType = expectedType ? *expectedType : freshType(scope);
addConstraint(scope, ifElse->trueExpr->location, SubtypeConstraint{thenType, resultType});
addConstraint(scope, ifElse->falseExpr->location, SubtypeConstraint{elseType, resultType});
return Inference{resultType};
}
return Inference{thenType};
return Inference{expectedType ? *expectedType : arena->addType(UnionType{{thenType, elseType}})};
}
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprTypeAssertion* typeAssert)
@ -1550,28 +1545,28 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprInterpStri
return Inference{builtinTypes->stringType};
}
std::tuple<TypeId, TypeId, ConnectiveId> ConstraintGraphBuilder::checkBinary(
std::tuple<TypeId, TypeId, RefinementId> ConstraintGraphBuilder::checkBinary(
const ScopePtr& scope, AstExprBinary* binary, std::optional<TypeId> expectedType)
{
if (binary->op == AstExprBinary::And)
{
auto [leftType, leftConnective] = check(scope, binary->left, expectedType);
auto [leftType, leftRefinement] = check(scope, binary->left, expectedType);
ScopePtr rightScope = childScope(binary->right, scope);
applyRefinements(rightScope, binary->right->location, leftConnective);
auto [rightType, rightConnective] = check(rightScope, binary->right, expectedType);
applyRefinements(rightScope, binary->right->location, leftRefinement);
auto [rightType, rightRefinement] = check(rightScope, binary->right, expectedType);
return {leftType, rightType, connectiveArena.conjunction(leftConnective, rightConnective)};
return {leftType, rightType, refinementArena.conjunction(leftRefinement, rightRefinement)};
}
else if (binary->op == AstExprBinary::Or)
{
auto [leftType, leftConnective] = check(scope, binary->left, expectedType);
auto [leftType, leftRefinement] = check(scope, binary->left, expectedType);
ScopePtr rightScope = childScope(binary->right, scope);
applyRefinements(rightScope, binary->right->location, connectiveArena.negation(leftConnective));
auto [rightType, rightConnective] = check(rightScope, binary->right, expectedType);
applyRefinements(rightScope, binary->right->location, refinementArena.negation(leftRefinement));
auto [rightType, rightRefinement] = check(rightScope, binary->right, expectedType);
return {leftType, rightType, connectiveArena.disjunction(leftConnective, rightConnective)};
return {leftType, rightType, refinementArena.disjunction(leftRefinement, rightRefinement)};
}
else if (auto typeguard = matchTypeGuard(binary))
{
@ -1613,11 +1608,11 @@ std::tuple<TypeId, TypeId, ConnectiveId> ConstraintGraphBuilder::checkBinary(
discriminantTy = ty;
}
ConnectiveId proposition = connectiveArena.proposition(*def, discriminantTy);
RefinementId proposition = refinementArena.proposition(*def, discriminantTy);
if (binary->op == AstExprBinary::CompareEq)
return {leftType, rightType, proposition};
else if (binary->op == AstExprBinary::CompareNe)
return {leftType, rightType, connectiveArena.negation(proposition)};
return {leftType, rightType, refinementArena.negation(proposition)};
else
ice->ice("matchTypeGuard should only return a Some under `==` or `~=`!");
}
@ -1626,21 +1621,21 @@ std::tuple<TypeId, TypeId, ConnectiveId> ConstraintGraphBuilder::checkBinary(
TypeId leftType = check(scope, binary->left, expectedType, true).ty;
TypeId rightType = check(scope, binary->right, expectedType, true).ty;
ConnectiveId leftConnective = nullptr;
RefinementId leftRefinement = nullptr;
if (auto def = dfg->getDef(binary->left))
leftConnective = connectiveArena.proposition(*def, rightType);
leftRefinement = refinementArena.proposition(*def, rightType);
ConnectiveId rightConnective = nullptr;
RefinementId rightRefinement = nullptr;
if (auto def = dfg->getDef(binary->right))
rightConnective = connectiveArena.proposition(*def, leftType);
rightRefinement = refinementArena.proposition(*def, leftType);
if (binary->op == AstExprBinary::CompareNe)
{
leftConnective = connectiveArena.negation(leftConnective);
rightConnective = connectiveArena.negation(rightConnective);
leftRefinement = refinementArena.negation(leftRefinement);
rightRefinement = refinementArena.negation(rightRefinement);
}
return {leftType, rightType, connectiveArena.equivalence(leftConnective, rightConnective)};
return {leftType, rightType, refinementArena.equivalence(leftRefinement, rightRefinement)};
}
else
{
@ -1737,13 +1732,13 @@ TypeId ConstraintGraphBuilder::checkLValue(const ScopePtr& scope, AstExpr* expr)
for (size_t i = 0; i < segments.size(); ++i)
{
TypeId segmentTy = arena->addType(BlockedType{});
astTypes[exprs[i]] = segmentTy;
module->astTypes[exprs[i]] = segmentTy;
addConstraint(scope, expr->location, HasPropConstraint{segmentTy, prevSegmentTy, segments[i]});
prevSegmentTy = segmentTy;
}
astTypes[expr] = prevSegmentTy;
astTypes[e] = updatedType;
module->astTypes[expr] = prevSegmentTy;
module->astTypes[e] = updatedType;
// astTypes[expr] = propTy;
return propTy;
@ -1895,6 +1890,10 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS
if (local->annotation)
{
annotationTy = resolveType(signatureScope, local->annotation, /* inTypeArguments */ false);
// If we provide an annotation that is wrong, type inference should ignore the annotation
// and try to infer a fresh type, like in the old solver
if (get<ErrorType>(follow(annotationTy)))
annotationTy = freshType(signatureScope);
addConstraint(signatureScope, local->annotation->location, SubtypeConstraint{t, annotationTy});
}
else if (i < expectedArgPack.head.size())
@ -1964,7 +1963,7 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS
TypeId actualFunctionType = arena->addType(std::move(actualFunction));
LUAU_ASSERT(actualFunctionType);
astTypes[fn] = actualFunctionType;
module->astTypes[fn] = actualFunctionType;
if (expectedType && get<FreeType>(*expectedType))
{
@ -2214,7 +2213,7 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, b
result = builtinTypes->errorRecoveryType();
}
astResolvedTypes[ty] = result;
module->astResolvedTypes[ty] = result;
return result;
}
@ -2248,7 +2247,7 @@ TypePackId ConstraintGraphBuilder::resolveTypePack(const ScopePtr& scope, AstTyp
result = builtinTypes->errorRecoveryTypePack();
}
astResolvedTypePacks[tp] = result;
module->astResolvedTypePacks[tp] = result;
return result;
}
@ -2307,13 +2306,13 @@ std::vector<std::pair<Name, GenericTypePackDefinition>> ConstraintGraphBuilder::
Inference ConstraintGraphBuilder::flattenPack(const ScopePtr& scope, Location location, InferencePack pack)
{
const auto& [tp, connectives] = pack;
ConnectiveId connective = nullptr;
if (!connectives.empty())
connective = connectives[0];
const auto& [tp, refinements] = pack;
RefinementId refinement = nullptr;
if (!refinements.empty())
refinement = refinements[0];
if (auto f = first(tp))
return Inference{*f, connective};
return Inference{*f, refinement};
TypeId typeResult = freshType(scope);
TypePack onePack{{typeResult}, freshTypePack(scope)};
@ -2321,7 +2320,7 @@ Inference ConstraintGraphBuilder::flattenPack(const ScopePtr& scope, Location lo
addConstraint(scope, location, PackSubtypeConstraint{tp, oneTypePack});
return Inference{typeResult, connective};
return Inference{typeResult, refinement};
}
void ConstraintGraphBuilder::reportError(Location location, TypeErrorData err)

View file

@ -528,7 +528,7 @@ bool ConstraintSolver::tryDispatch(const UnaryConstraint& c, NotNull<const Const
}
case AstExprUnary::Minus:
{
if (isNumber(operandType) || get<AnyType>(operandType) || get<ErrorType>(operandType))
if (isNumber(operandType) || get<AnyType>(operandType) || get<ErrorType>(operandType) || get<NeverType>(operandType))
{
asMutable(c.resultType)->ty.emplace<BoundType>(c.operandType);
}
@ -1415,7 +1415,7 @@ bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull<const Con
bind(c.resultType, subjectType);
return true;
}
else if (get<AnyType>(subjectType) || get<ErrorType>(subjectType))
else if (get<AnyType>(subjectType) || get<ErrorType>(subjectType) || get<NeverType>(subjectType))
{
bind(c.resultType, subjectType);
return true;

View file

@ -579,6 +579,7 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
module->astOriginalCallTypes.clear();
module->astOverloadResolvedTypes.clear();
module->astResolvedTypes.clear();
module->astOriginalResolvedTypes.clear();
module->astResolvedTypePacks.clear();
module->astScopes.clear();
@ -591,6 +592,7 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
module->astOriginalCallTypes.clear();
module->astResolvedTypes.clear();
module->astResolvedTypePacks.clear();
module->astOriginalResolvedTypes.clear();
module->scopes.resize(1);
}
}
@ -922,23 +924,22 @@ ModulePtr Frontend::check(
for (TypeError& e : cs.errors)
result->errors.emplace_back(std::move(e));
result->scopes = std::move(cgb.scopes);
result->astTypes = std::move(cgb.astTypes);
result->astTypePacks = std::move(cgb.astTypePacks);
result->astExpectedTypes = std::move(cgb.astExpectedTypes);
result->astOriginalCallTypes = std::move(cgb.astOriginalCallTypes);
result->astOverloadResolvedTypes = std::move(cgb.astOverloadResolvedTypes);
result->astResolvedTypes = std::move(cgb.astResolvedTypes);
result->astResolvedTypePacks = std::move(cgb.astResolvedTypePacks);
result->type = sourceModule.type;
result->clonePublicInterface(builtinTypes, iceHandler);
Luau::check(builtinTypes, logger.get(), sourceModule, result.get());
// Ideally we freeze the arenas before the call into Luau::check, but TypeReduction
// needs to allocate new types while Luau::check is in progress, so here we are.
//
// It does mean that mutations to the type graph can happen after the constraints
// have been solved, which will cause hard-to-debug problems. We should revisit this.
freeze(result->internalTypes);
freeze(result->interfaceTypes);
Luau::check(builtinTypes, logger.get(), sourceModule, result.get());
if (FFlag::DebugLuauLogSolverToJson)
{
std::string output = logger->compileOutput();

View file

@ -2616,10 +2616,6 @@ private:
emitWarning(*context, LintWarning::Code_IntegerParsing, node->location,
"Hexadecimal number literal exceeded available precision and has been truncated to 2^64");
break;
case ConstantNumberParseResult::DoublePrefix:
emitWarning(*context, LintWarning::Code_IntegerParsing, node->location,
"Hexadecimal number literal has a double prefix, which will fail to parse in the future; remove the extra 0x to fix");
break;
}
return true;

View file

@ -232,9 +232,6 @@ void Module::clonePublicInterface(NotNull<BuiltinTypes> builtinTypes, InternalEr
this->returnType = moduleScope->returnType;
this->exportedTypeBindings = std::move(moduleScope->exportedTypeBindings);
}
freeze(internalTypes);
freeze(interfaceTypes);
}
bool Module::hasModuleScope() const

View file

@ -1,30 +1,30 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Connective.h"
#include "Luau/Refinement.h"
namespace Luau
{
ConnectiveId ConnectiveArena::negation(ConnectiveId connective)
RefinementId RefinementArena::negation(RefinementId refinement)
{
return NotNull{allocator.allocate(Negation{connective})};
return NotNull{allocator.allocate(Negation{refinement})};
}
ConnectiveId ConnectiveArena::conjunction(ConnectiveId lhs, ConnectiveId rhs)
RefinementId RefinementArena::conjunction(RefinementId lhs, RefinementId rhs)
{
return NotNull{allocator.allocate(Conjunction{lhs, rhs})};
}
ConnectiveId ConnectiveArena::disjunction(ConnectiveId lhs, ConnectiveId rhs)
RefinementId RefinementArena::disjunction(RefinementId lhs, RefinementId rhs)
{
return NotNull{allocator.allocate(Disjunction{lhs, rhs})};
}
ConnectiveId ConnectiveArena::equivalence(ConnectiveId lhs, ConnectiveId rhs)
RefinementId RefinementArena::equivalence(RefinementId lhs, RefinementId rhs)
{
return NotNull{allocator.allocate(Equivalence{lhs, rhs})};
}
ConnectiveId ConnectiveArena::proposition(DefId def, TypeId discriminantTy)
RefinementId RefinementArena::proposition(DefId def, TypeId discriminantTy)
{
return NotNull{allocator.allocate(Proposition{def, discriminantTy})};
}

View file

@ -414,7 +414,7 @@ bool hasLength(TypeId ty, DenseHashSet<TypeId>& seen, int* recursionCount)
if (seen.contains(ty))
return true;
if (isString(ty) || get<AnyType>(ty) || get<TableType>(ty) || get<MetatableType>(ty))
if (isString(ty) || isPrim(ty, PrimitiveType::Table) || get<AnyType>(ty) || get<TableType>(ty) || get<MetatableType>(ty))
return true;
if (auto uty = get<UnionType>(ty))

View file

@ -4,22 +4,24 @@
#include "Luau/Ast.h"
#include "Luau/AstQuery.h"
#include "Luau/Clone.h"
#include "Luau/DcrLogger.h"
#include "Luau/Error.h"
#include "Luau/Instantiation.h"
#include "Luau/Metamethods.h"
#include "Luau/Normalize.h"
#include "Luau/ToString.h"
#include "Luau/TxnLog.h"
#include "Luau/TypeUtils.h"
#include "Luau/Type.h"
#include "Luau/TypeReduction.h"
#include "Luau/TypeUtils.h"
#include "Luau/Unifier.h"
#include "Luau/ToString.h"
#include "Luau/DcrLogger.h"
#include <algorithm>
LUAU_FASTFLAG(DebugLuauLogSolverToJson);
LUAU_FASTFLAG(DebugLuauMagicTypes);
LUAU_FASTFLAG(DebugLuauLogSolverToJson)
LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAG(DebugLuauDontReduceTypes)
LUAU_FASTFLAG(LuauNegatedClassTypes)
namespace Luau
@ -223,10 +225,7 @@ struct TypeChecker2
{
auto pusher = pushStack(stat);
if (0)
{
}
else if (auto s = stat->as<AstStatBlock>())
if (auto s = stat->as<AstStatBlock>())
return visit(s);
else if (auto s = stat->as<AstStatIf>())
return visit(s);
@ -340,8 +339,7 @@ struct TypeChecker2
if (value)
visit(value, RValue);
TypeId* maybeValueType = value ? module->astTypes.find(value) : nullptr;
if (i != local->values.size - 1 || maybeValueType)
if (i != local->values.size - 1 || value)
{
AstLocal* var = i < local->vars.size ? local->vars.data[i] : nullptr;
@ -391,13 +389,26 @@ struct TypeChecker2
void visit(AstStatFor* forStatement)
{
if (forStatement->var->annotation)
visit(forStatement->var->annotation);
NotNull<Scope> scope = stack.back();
if (forStatement->var->annotation)
{
visit(forStatement->var->annotation);
reportErrors(tryUnify(scope, forStatement->var->location, builtinTypes->numberType, lookupAnnotation(forStatement->var->annotation)));
}
auto checkNumber = [this, scope](AstExpr* expr) {
if (!expr)
return;
visit(expr, RValue);
reportErrors(tryUnify(scope, expr->location, lookupType(expr), builtinTypes->numberType));
};
checkNumber(forStatement->from);
checkNumber(forStatement->to);
checkNumber(forStatement->step);
visit(forStatement->from, RValue);
visit(forStatement->to, RValue);
if (forStatement->step)
visit(forStatement->step, RValue);
visit(forStatement->body);
}
@ -543,7 +554,7 @@ struct TypeChecker2
else
reportError(GenericError{"Cannot iterate over a table without indexer"}, forInStatement->values.data[0]->location);
}
else if (get<AnyType>(iteratorTy) || get<ErrorType>(iteratorTy))
else if (get<AnyType>(iteratorTy) || get<ErrorType>(iteratorTy) || get<NeverType>(iteratorTy))
{
// nothing
}
@ -624,6 +635,9 @@ struct TypeChecker2
visit(rhs, RValue);
TypeId rhsType = lookupType(rhs);
if (get<NeverType>(lhsType))
continue;
if (!isSubtype(rhsType, lhsType, stack.back()))
{
reportError(TypeMismatch{lhsType, rhsType}, rhs->location);
@ -715,10 +729,7 @@ struct TypeChecker2
{
auto StackPusher = pushStack(expr);
if (0)
{
}
else if (auto e = expr->as<AstExprGroup>())
if (auto e = expr->as<AstExprGroup>())
return visit(e, context);
else if (auto e = expr->as<AstExprConstantNil>())
return visit(e);
@ -770,34 +781,34 @@ struct TypeChecker2
void visit(AstExprConstantNil* expr)
{
// TODO!
NotNull<Scope> scope = stack.back();
TypeId actualType = lookupType(expr);
TypeId expectedType = builtinTypes->nilType;
LUAU_ASSERT(isSubtype(actualType, expectedType, scope));
}
void visit(AstExprConstantBool* expr)
{
// TODO!
NotNull<Scope> scope = stack.back();
TypeId actualType = lookupType(expr);
TypeId expectedType = builtinTypes->booleanType;
LUAU_ASSERT(isSubtype(actualType, expectedType, scope));
}
void visit(AstExprConstantNumber* number)
void visit(AstExprConstantNumber* expr)
{
TypeId actualType = lookupType(number);
TypeId numberType = builtinTypes->numberType;
if (!isSubtype(numberType, actualType, stack.back()))
{
reportError(TypeMismatch{actualType, numberType}, number->location);
}
NotNull<Scope> scope = stack.back();
TypeId actualType = lookupType(expr);
TypeId expectedType = builtinTypes->numberType;
LUAU_ASSERT(isSubtype(actualType, expectedType, scope));
}
void visit(AstExprConstantString* string)
void visit(AstExprConstantString* expr)
{
TypeId actualType = lookupType(string);
TypeId stringType = builtinTypes->stringType;
if (!isSubtype(actualType, stringType, stack.back()))
{
reportError(TypeMismatch{actualType, stringType}, string->location);
}
NotNull<Scope> scope = stack.back();
TypeId actualType = lookupType(expr);
TypeId expectedType = builtinTypes->stringType;
LUAU_ASSERT(isSubtype(actualType, expectedType, scope));
}
void visit(AstExprLocal* expr)
@ -832,7 +843,7 @@ struct TypeChecker2
std::vector<Location> argLocs;
argLocs.reserve(call->args.size + 1);
if (get<AnyType>(functionType) || get<ErrorType>(functionType))
if (get<AnyType>(functionType) || get<ErrorType>(functionType) || get<NeverType>(functionType))
return;
else if (std::optional<TypeId> callMm = findMetatableEntry(builtinTypes, module->errors, functionType, "__call", call->func->location))
{
@ -1080,7 +1091,7 @@ struct TypeChecker2
}
}
TypeId visit(AstExprBinary* expr, void* overrideKey = nullptr)
TypeId visit(AstExprBinary* expr, AstNode* overrideKey = nullptr)
{
visit(expr->left, LValue);
visit(expr->right, LValue);
@ -1164,7 +1175,7 @@ struct TypeChecker2
if (mm)
{
void* key = expr;
AstNode* key = expr;
if (overrideKey != nullptr)
key = overrideKey;
@ -1381,19 +1392,8 @@ struct TypeChecker2
{
pack = follow(pack);
while (true)
{
auto tp = get<TypePack>(pack);
if (tp && tp->head.empty() && tp->tail)
pack = *tp->tail;
else
break;
}
if (auto ty = first(pack))
return *ty;
else if (auto vtp = get<VariadicTypePack>(pack))
return vtp->ty;
if (auto fst = first(pack, /*ignoreHiddenVariadics*/ false))
return *fst;
else if (auto ftp = get<FreeTypePack>(pack))
{
TypeId result = testArena.addType(FreeType{ftp->scope});
@ -1407,6 +1407,8 @@ struct TypeChecker2
}
else if (get<Unifiable::Error>(pack))
return builtinTypes->errorRecoveryType();
else if (finite(pack) && size(pack) == 0)
return builtinTypes->nilType; // `(f())` where `f()` returns no values is coerced into `nil`
else
ice.ice("flattenPack got a weird pack!");
}
@ -1652,6 +1654,69 @@ struct TypeChecker2
}
}
void reduceTypes()
{
if (FFlag::DebugLuauDontReduceTypes)
return;
for (auto [_, scope] : module->scopes)
{
for (auto& [_, b] : scope->bindings)
{
if (auto reduced = module->reduction->reduce(b.typeId))
b.typeId = *reduced;
}
if (auto reduced = module->reduction->reduce(scope->returnType))
scope->returnType = *reduced;
if (scope->varargPack)
{
if (auto reduced = module->reduction->reduce(*scope->varargPack))
scope->varargPack = *reduced;
}
auto reduceMap = [this](auto& map) {
for (auto& [_, tf] : map)
{
if (auto reduced = module->reduction->reduce(tf))
tf = *reduced;
}
};
reduceMap(scope->exportedTypeBindings);
reduceMap(scope->privateTypeBindings);
reduceMap(scope->privateTypePackBindings);
for (auto& [_, space] : scope->importedTypeBindings)
reduceMap(space);
}
auto reduceOrError = [this](auto& map) {
for (auto [ast, t] : map)
{
if (!t)
continue; // Reminder: this implies that the recursion limit was exceeded.
else if (auto reduced = module->reduction->reduce(t))
map[ast] = *reduced;
else
reportError(NormalizationTooComplex{}, ast->location);
}
};
module->astOriginalResolvedTypes = module->astResolvedTypes;
// Both [`Module::returnType`] and [`Module::exportedTypeBindings`] are empty here, and
// is populated by [`Module::clonePublicInterface`] in the future, so by that point these
// two aforementioned fields will only contain types that are irreducible.
reduceOrError(module->astTypes);
reduceOrError(module->astTypePacks);
reduceOrError(module->astExpectedTypes);
reduceOrError(module->astOriginalCallTypes);
reduceOrError(module->astOverloadResolvedTypes);
reduceOrError(module->astResolvedTypes);
reduceOrError(module->astResolvedTypePacks);
}
template<typename TID>
bool isSubtype(TID subTy, TID superTy, NotNull<Scope> scope)
{
@ -1797,7 +1862,7 @@ struct TypeChecker2
void check(NotNull<BuiltinTypes> builtinTypes, DcrLogger* logger, const SourceModule& sourceModule, Module* module)
{
TypeChecker2 typeChecker{builtinTypes, logger, &sourceModule, module};
typeChecker.reduceTypes();
typeChecker.visit(sourceModule.root);
unfreeze(module->interfaceTypes);

View file

@ -323,6 +323,8 @@ ModulePtr TypeChecker::checkWithoutRecursionCheck(const SourceModule& module, Mo
normalizer.arena = nullptr;
currentModule->clonePublicInterface(builtinTypes, *iceHandler);
freeze(currentModule->internalTypes);
freeze(currentModule->interfaceTypes);
// Clear unifier cache since it's keyed off internal types that get deallocated
// This avoids fake cross-module cache hits and keeps cache size at bay when typechecking large module graphs.

View file

@ -10,7 +10,7 @@
#include <deque>
LUAU_FASTINTVARIABLE(LuauTypeReductionCartesianProductLimit, 100'000)
LUAU_FASTINTVARIABLE(LuauTypeReductionRecursionLimit, 700)
LUAU_FASTINTVARIABLE(LuauTypeReductionRecursionLimit, 400)
LUAU_FASTFLAGVARIABLE(DebugLuauDontReduceTypes, false)
namespace Luau
@ -37,7 +37,7 @@ struct TypeReducer
DenseHashMap<TypeId, ReductionContext<TypeId>>* memoizedTypes;
DenseHashMap<TypePackId, ReductionContext<TypePackId>>* memoizedTypePacks;
DenseHashSet<TypeId>* cyclicTypes;
DenseHashSet<const void*>* cyclics;
int depth = 0;
@ -68,8 +68,8 @@ struct TypeReducer
return {ctx->type, getMutable<T>(ctx->type)};
TypeId copiedTy = arena->addType(*t);
(*memoizedTypes)[ty] = {copiedTy, false};
(*memoizedTypes)[copiedTy] = {copiedTy, false};
(*memoizedTypes)[ty] = {copiedTy, true};
(*memoizedTypes)[copiedTy] = {copiedTy, true};
return {copiedTy, getMutable<T>(copiedTy)};
}
@ -142,32 +142,21 @@ struct TypeReducer
std::vector<TypeId> result;
bool didReduce = false;
foldl_impl<T>(it, endIt, f, &result, &didReduce);
if (!didReduce && ty)
return *ty;
else
{
// If we've done any reduction, then we'll need to reduce it again, e.g.
// `"a" | "b" | string` is reduced into `string | string`, which is then reduced into `string`.
if (!didReduce)
return ty ? *ty : flatten<T>(std::move(result));
else
return reduce(flatten<T>(std::move(result)));
}
}
template<typename T>
TypeId apply(BinaryFold f, TypeId left, TypeId right)
{
left = follow(left);
right = follow(right);
if (get<T>(left) || get<T>(right))
{
std::vector<TypeId> types{left, right};
return foldl<T>(begin(types), end(types), std::nullopt, f);
}
else if (auto reduced = (this->*f)(left, right))
return *reduced;
else
return arena->addType(T{{left, right}});
}
template<typename Into, typename Over>
TypeId distribute(TypeIterator<Over> it, TypeIterator<Over> endIt, BinaryFold f, TypeId ty)
@ -188,8 +177,8 @@ TypeId TypeReducer::reduce(TypeId ty)
if (auto ctx = memoizedTypes->find(ty); ctx && ctx->irreducible)
return ctx->type;
else if (auto cyclicTy = cyclicTypes->find(ty))
return *cyclicTy;
else if (cyclics->contains(ty))
return ty;
RecursionLimiter rl{&depth, FInt::LuauTypeReductionRecursionLimit};
@ -216,6 +205,8 @@ TypePackId TypeReducer::reduce(TypePackId tp)
if (auto ctx = memoizedTypePacks->find(tp); ctx && ctx->irreducible)
return ctx->type;
else if (cyclics->contains(tp))
return tp;
RecursionLimiter rl{&depth, FInt::LuauTypeReductionRecursionLimit};
@ -356,9 +347,9 @@ std::optional<TypeId> TypeReducer::intersectionType(TypeId left, TypeId right)
else if (t1->state == TableState::Generic || t2->state == TableState::Generic)
return std::nullopt; // '{ x: T } & { x: U } ~ '{ x: T } & { x: U }
if (cyclicTypes->find(left))
if (cyclics->contains(left))
return std::nullopt; // (t1 where t1 = { p: t1 }) & {} ~ t1 & {}
else if (cyclicTypes->find(right))
else if (cyclics->contains(right))
return std::nullopt; // {} & (t1 where t1 = { p: t1 }) ~ {} & t1
TypeId resultTy = arena->addType(TableType{});
@ -396,10 +387,7 @@ std::optional<TypeId> TypeReducer::intersectionType(TypeId left, TypeId right)
return std::nullopt; // { [string]: _ } & { [number]: _ } ~ { [string]: _ } & { [number]: _ }
TypeId valueTy = apply<IntersectionType>(&TypeReducer::intersectionType, t1->indexer->indexResultType, t2->indexer->indexResultType);
if (get<NeverType>(valueTy))
return builtinTypes->neverType; // { [_]: string } & { [_]: number } ~ { [_]: string & number } ~ { [_]: never } ~ never
table->indexer = TableIndexer{keyTy, valueTy};
table->indexer = TableIndexer{keyTy, valueTy}; // { [string]: number } & { [string]: string } ~ { [string]: never }
}
else if (t1->indexer)
{
@ -422,6 +410,45 @@ std::optional<TypeId> TypeReducer::intersectionType(TypeId left, TypeId right)
return intersectionType(right, left); // T & M ~ M & T
else if (auto [m1, m2] = get2<MetatableType, MetatableType>(left, right); m1 && m2)
return std::nullopt; // TODO
else if (auto [nl, nr] = get2<NegationType, NegationType>(left, right); nl && nr)
{
// These should've been reduced already.
TypeId nlTy = follow(nl->ty);
TypeId nrTy = follow(nr->ty);
LUAU_ASSERT(!get<UnknownType>(nlTy) && !get<UnknownType>(nrTy));
LUAU_ASSERT(!get<NeverType>(nlTy) && !get<NeverType>(nrTy));
LUAU_ASSERT(!get<AnyType>(nlTy) && !get<AnyType>(nrTy));
LUAU_ASSERT(!get<IntersectionType>(nlTy) && !get<IntersectionType>(nrTy));
LUAU_ASSERT(!get<UnionType>(nlTy) && !get<UnionType>(nrTy));
if (auto [npl, npr] = get2<PrimitiveType, PrimitiveType>(nlTy, nrTy); npl && npr)
{
if (npl->type == npr->type)
return left; // ~P1 & ~P2 ~ ~P1 iff P1 == P2
else
return std::nullopt; // ~P1 & ~P2 ~ ~P1 & ~P2 iff P1 != P2
}
else if (auto [nsl, nsr] = get2<SingletonType, SingletonType>(nlTy, nrTy); nsl && nsr)
{
if (*nsl == *nsr)
return left; // ~"A" & ~"A" ~ ~"A"
else
return std::nullopt; // ~"A" & ~"B" ~ ~"A" & ~"B"
}
else if (auto [ns, np] = get2<SingletonType, PrimitiveType>(nlTy, nrTy); ns && np)
{
if (get<StringSingleton>(ns) && np->type == PrimitiveType::String)
return right; // ~"A" & ~string ~ ~string
else if (get<BooleanSingleton>(ns) && np->type == PrimitiveType::Boolean)
return right; // ~false & ~boolean ~ ~boolean
else
return std::nullopt; // ~"A" | ~P ~ ~"A" & ~P
}
else if (auto [np, ns] = get2<PrimitiveType, SingletonType>(nlTy, nrTy); np && ns)
return intersectionType(right, left); // ~P & ~S ~ ~S & ~P
else
return std::nullopt; // ~T & ~U ~ ~T & ~U
}
else if (auto nl = get<NegationType>(left))
{
// These should've been reduced already.
@ -477,10 +504,10 @@ std::optional<TypeId> TypeReducer::intersectionType(TypeId left, TypeId right)
}
else if (auto [nc, c] = get2<ClassType, ClassType>(nlTy, right); nc && c)
{
if (isSubclass(nc, c))
return std::nullopt; // ~Derived & Base ~ ~Derived & Base
else if (isSubclass(c, nc))
if (isSubclass(c, nc))
return builtinTypes->neverType; // ~Base & Derived ~ never
else if (isSubclass(nc, c))
return std::nullopt; // ~Derived & Base ~ ~Derived & Base
else
return right; // ~Base & Unrelated ~ Unrelated
}
@ -499,7 +526,7 @@ std::optional<TypeId> TypeReducer::intersectionType(TypeId left, TypeId right)
return right; // ~string & {} ~ {}
}
else
return std::nullopt; // TODO
return right; // ~T & U ~ U
}
else if (get<NegationType>(right))
return intersectionType(right, left); // T & ~U ~ ~U & T
@ -679,10 +706,10 @@ std::optional<TypeId> TypeReducer::unionType(TypeId left, TypeId right)
}
else if (auto [nc, c] = get2<ClassType, ClassType>(nlTy, right); nc && c)
{
if (isSubclass(nc, c))
return builtinTypes->unknownType; // ~Derived | Base ~ unknown
else if (isSubclass(c, nc))
if (isSubclass(c, nc))
return std::nullopt; // ~Base | Derived ~ ~Base | Derived
else if (isSubclass(nc, c))
return builtinTypes->unknownType; // ~Derived | Base ~ unknown
else
return left; // ~Base | Unrelated ~ ~Base
}
@ -777,22 +804,24 @@ TypeId TypeReducer::negationType(TypeId ty)
if (!n)
return arena->addType(NegationType{ty});
if (auto nn = get<NegationType>(n->ty))
TypeId negatedTy = follow(n->ty);
if (auto nn = get<NegationType>(negatedTy))
return nn->ty; // ~~T ~ T
else if (get<NeverType>(n->ty))
else if (get<NeverType>(negatedTy))
return builtinTypes->unknownType; // ~never ~ unknown
else if (get<UnknownType>(n->ty))
else if (get<UnknownType>(negatedTy))
return builtinTypes->neverType; // ~unknown ~ never
else if (get<AnyType>(n->ty))
else if (get<AnyType>(negatedTy))
return builtinTypes->anyType; // ~any ~ any
else if (auto ni = get<IntersectionType>(n->ty))
else if (auto ni = get<IntersectionType>(negatedTy))
{
std::vector<TypeId> options;
for (TypeId part : ni)
options.push_back(negationType(arena->addType(NegationType{part})));
return reduce(flatten<UnionType>(std::move(options))); // ~(T & U) ~ (~T | ~U)
}
else if (auto nu = get<UnionType>(n->ty))
else if (auto nu = get<UnionType>(negatedTy))
{
std::vector<TypeId> parts;
for (TypeId option : nu)
@ -910,16 +939,26 @@ TypePackId TypeReducer::memoize(TypePackId tp, TypePackId reducedTp)
struct MarkCycles : TypeVisitor
{
DenseHashSet<TypeId> cyclicTypes{nullptr};
DenseHashSet<const void*> cyclics{nullptr};
void cycle(TypeId ty) override
{
cyclicTypes.insert(ty);
cyclics.insert(follow(ty));
}
void cycle(TypePackId tp) override
{
cyclics.insert(follow(tp));
}
bool visit(TypeId ty) override
{
return !cyclicTypes.find(ty);
return !cyclics.find(follow(ty));
}
bool visit(TypePackId tp) override
{
return !cyclics.find(follow(tp));
}
};
@ -942,8 +981,8 @@ std::optional<TypeId> TypeReduction::reduce(TypeId ty)
return ty;
else if (!options.allowTypeReductionsFromOtherArenas && ty->owningArena != arena)
return ty;
else if (auto ctx = memoizedTypes.find(ty); ctx && ctx->irreducible)
return ctx->type;
else if (auto memoized = memoizedof(ty))
return *memoized;
else if (hasExceededCartesianProductLimit(ty))
return std::nullopt;
@ -952,7 +991,7 @@ std::optional<TypeId> TypeReduction::reduce(TypeId ty)
MarkCycles finder;
finder.traverse(ty);
TypeReducer reducer{arena, builtinTypes, handle, &memoizedTypes, &memoizedTypePacks, &finder.cyclicTypes};
TypeReducer reducer{arena, builtinTypes, handle, &memoizedTypes, &memoizedTypePacks, &finder.cyclics};
return reducer.reduce(ty);
}
catch (const RecursionLimitException&)
@ -969,8 +1008,8 @@ std::optional<TypePackId> TypeReduction::reduce(TypePackId tp)
return tp;
else if (!options.allowTypeReductionsFromOtherArenas && tp->owningArena != arena)
return tp;
else if (auto ctx = memoizedTypePacks.find(tp); ctx && ctx->irreducible)
return ctx->type;
else if (auto memoized = memoizedof(tp))
return *memoized;
else if (hasExceededCartesianProductLimit(tp))
return std::nullopt;
@ -979,7 +1018,7 @@ std::optional<TypePackId> TypeReduction::reduce(TypePackId tp)
MarkCycles finder;
finder.traverse(tp);
TypeReducer reducer{arena, builtinTypes, handle, &memoizedTypes, &memoizedTypePacks, &finder.cyclicTypes};
TypeReducer reducer{arena, builtinTypes, handle, &memoizedTypes, &memoizedTypePacks, &finder.cyclics};
return reducer.reduce(tp);
}
catch (const RecursionLimitException&)
@ -1000,6 +1039,13 @@ std::optional<TypeFun> TypeReduction::reduce(const TypeFun& fun)
return std::nullopt;
}
TypeReduction TypeReduction::fork(NotNull<TypeArena> arena, const TypeReductionOptions& opts) const
{
TypeReduction child{arena, builtinTypes, handle, opts};
child.parent = this;
return child;
}
size_t TypeReduction::cartesianProductSize(TypeId ty) const
{
ty = follow(ty);
@ -1047,4 +1093,24 @@ bool TypeReduction::hasExceededCartesianProductLimit(TypePackId tp) const
return false;
}
std::optional<TypeId> TypeReduction::memoizedof(TypeId ty) const
{
if (auto ctx = memoizedTypes.find(ty); ctx && ctx->irreducible)
return ctx->type;
else if (parent)
return parent->memoizedof(ty);
else
return std::nullopt;
}
std::optional<TypePackId> TypeReduction::memoizedof(TypePackId tp) const
{
if (auto ctx = memoizedTypePacks.find(tp); ctx && ctx->irreducible)
return ctx->type;
else if (parent)
return parent->memoizedof(tp);
else
return std::nullopt;
}
} // namespace Luau

View file

@ -22,6 +22,7 @@ LUAU_FASTFLAGVARIABLE(LuauInstantiateInSubtyping, false)
LUAU_FASTFLAGVARIABLE(LuauScalarShapeUnifyToMtOwner2, false)
LUAU_FASTFLAGVARIABLE(LuauUninhabitedSubAnything2, false)
LUAU_FASTFLAGVARIABLE(LuauMaintainScopesInUnifier, false)
LUAU_FASTFLAGVARIABLE(LuauTableUnifyInstantiationFix, false)
LUAU_FASTFLAG(LuauClassTypeVarsInSubstitution)
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
LUAU_FASTFLAG(LuauNegatedFunctionTypes)
@ -600,11 +601,8 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
else if (log.getMutable<ClassType>(subTy))
tryUnifyWithClass(subTy, superTy, /*reversed*/ true);
else if (log.get<NegationType>(superTy))
tryUnifyTypeWithNegation(subTy, superTy);
else if (log.get<NegationType>(subTy))
tryUnifyNegationWithType(subTy, superTy);
else if (log.get<NegationType>(superTy) || log.get<NegationType>(subTy))
tryUnifyNegations(subTy, superTy);
else if (FFlag::LuauUninhabitedSubAnything2 && !normalizer->isInhabited(subTy))
{
@ -857,6 +855,22 @@ void Unifier::tryUnifyTypeWithIntersection(TypeId subTy, TypeId superTy, const I
reportError(location, TypeMismatch{superTy, subTy, "Not all intersection parts are compatible.", *firstFailedOption, mismatchContext()});
}
struct NegationTypeFinder : TypeOnceVisitor
{
bool found = false;
bool visit(TypeId ty) override
{
return !found;
}
bool visit(TypeId ty, const NegationType&) override
{
found = true;
return !found;
}
};
void Unifier::tryUnifyIntersectionWithType(TypeId subTy, const IntersectionType* uv, TypeId superTy, bool cacheEnabled, bool isFunctionCall)
{
// A & B <: T if A <: T or B <: T
@ -881,6 +895,28 @@ void Unifier::tryUnifyIntersectionWithType(TypeId subTy, const IntersectionType*
}
}
if (FFlag::DebugLuauDeferredConstraintResolution && normalize)
{
// Sometimes a negation type is inside one of the types, e.g. { p: number } & { p: ~number }.
NegationTypeFinder finder;
finder.traverse(subTy);
if (finder.found)
{
// It is possible that A & B <: T even though A </: T and B </: T
// for example (string?) & ~nil <: string.
// We deal with this by type normalization.
const NormalizedType* subNorm = normalizer->normalize(subTy);
const NormalizedType* superNorm = normalizer->normalize(superTy);
if (subNorm && superNorm)
tryUnifyNormalizedTypes(subTy, superTy, *subNorm, *superNorm, "none of the intersection parts are compatible");
else
reportError(location, UnificationTooComplex{});
return;
}
}
std::vector<TxnLog> logs;
for (size_t i = 0; i < uv->parts.size(); ++i)
@ -1728,9 +1764,10 @@ struct Resetter
void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
{
TypeId activeSubTy = subTy;
TableType* superTable = log.getMutable<TableType>(superTy);
TableType* subTable = log.getMutable<TableType>(subTy);
TableType* instantiatedSubTable = subTable;
TableType* instantiatedSubTable = subTable; // TODO: remove with FFlagLuauTableUnifyInstantiationFix
if (!superTable || !subTable)
ice("passed non-table types to unifyTables");
@ -1746,9 +1783,17 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
std::optional<TypeId> instantiated = instantiation.substitute(subTy);
if (instantiated.has_value())
{
if (FFlag::LuauTableUnifyInstantiationFix)
{
activeSubTy = *instantiated;
subTable = log.getMutable<TableType>(activeSubTy);
}
else
{
subTable = log.getMutable<TableType>(*instantiated);
instantiatedSubTable = subTable;
}
if (!subTable)
ice("instantiation made a table type into a non-table type in tryUnifyTables");
@ -1838,7 +1883,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
}
else if (subTable->state == TableState::Free)
{
PendingType* pendingSub = log.queue(subTy);
PendingType* pendingSub = log.queue(activeSubTy);
TableType* ttv = getMutable<TableType>(pendingSub);
LUAU_ASSERT(ttv);
ttv->props[name] = prop;
@ -1851,12 +1896,12 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
// table. If we detect that this has happened, we start over, with the updated
// txn log.
TypeId superTyNew = FFlag::LuauScalarShapeUnifyToMtOwner2 ? log.follow(superTy) : superTy;
TypeId subTyNew = FFlag::LuauScalarShapeUnifyToMtOwner2 ? log.follow(subTy) : subTy;
TypeId subTyNew = FFlag::LuauScalarShapeUnifyToMtOwner2 ? log.follow(activeSubTy) : activeSubTy;
if (FFlag::LuauScalarShapeUnifyToMtOwner2)
{
// If one of the types stopped being a table altogether, we need to restart from the top
if ((superTy != superTyNew || subTy != subTyNew) && errors.empty())
if ((superTy != superTyNew || activeSubTy != subTyNew) && errors.empty())
return tryUnify(subTy, superTy, false, isIntersection);
}
@ -1864,7 +1909,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
TableType* newSuperTable = log.getMutable<TableType>(superTyNew);
TableType* newSubTable = log.getMutable<TableType>(subTyNew);
if (superTable != newSuperTable || (subTable != newSubTable && subTable != instantiatedSubTable))
if (superTable != newSuperTable || (subTable != newSubTable && (FFlag::LuauTableUnifyInstantiationFix || subTable != instantiatedSubTable)))
{
if (errors.empty())
return tryUnifyTables(subTy, superTy, isIntersection);
@ -1922,12 +1967,12 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
extraProperties.push_back(name);
TypeId superTyNew = FFlag::LuauScalarShapeUnifyToMtOwner2 ? log.follow(superTy) : superTy;
TypeId subTyNew = FFlag::LuauScalarShapeUnifyToMtOwner2 ? log.follow(subTy) : subTy;
TypeId subTyNew = FFlag::LuauScalarShapeUnifyToMtOwner2 ? log.follow(activeSubTy) : activeSubTy;
if (FFlag::LuauScalarShapeUnifyToMtOwner2)
{
// If one of the types stopped being a table altogether, we need to restart from the top
if ((superTy != superTyNew || subTy != subTyNew) && errors.empty())
if ((superTy != superTyNew || activeSubTy != subTyNew) && errors.empty())
return tryUnify(subTy, superTy, false, isIntersection);
}
@ -1936,7 +1981,8 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
// txn log.
TableType* newSuperTable = log.getMutable<TableType>(superTyNew);
TableType* newSubTable = log.getMutable<TableType>(subTyNew);
if (superTable != newSuperTable || (subTable != newSubTable && subTable != instantiatedSubTable))
if (superTable != newSuperTable || (subTable != newSubTable && (FFlag::LuauTableUnifyInstantiationFix || subTable != instantiatedSubTable)))
{
if (errors.empty())
return tryUnifyTables(subTy, superTy, isIntersection);
@ -1992,7 +2038,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
if (FFlag::LuauScalarShapeUnifyToMtOwner2)
{
superTable = log.getMutable<TableType>(log.follow(superTy));
subTable = log.getMutable<TableType>(log.follow(subTy));
subTable = log.getMutable<TableType>(log.follow(activeSubTy));
if (!superTable || !subTable)
return;
@ -2000,7 +2046,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
else
{
superTable = log.getMutable<TableType>(superTy);
subTable = log.getMutable<TableType>(subTy);
subTable = log.getMutable<TableType>(activeSubTy);
}
if (!missingProperties.empty())
@ -2313,11 +2359,10 @@ void Unifier::tryUnifyWithClass(TypeId subTy, TypeId superTy, bool reversed)
return fail();
}
void Unifier::tryUnifyTypeWithNegation(TypeId subTy, TypeId superTy)
void Unifier::tryUnifyNegations(TypeId subTy, TypeId superTy)
{
const NegationType* ntv = get<NegationType>(superTy);
if (!ntv)
ice("tryUnifyTypeWithNegation superTy must be a negation type");
if (!log.get<NegationType>(subTy) && !log.get<NegationType>(superTy))
ice("tryUnifyNegations superTy or subTy must be a negation type");
const NormalizedType* subNorm = normalizer->normalize(subTy);
const NormalizedType* superNorm = normalizer->normalize(superTy);
@ -2331,16 +2376,6 @@ void Unifier::tryUnifyTypeWithNegation(TypeId subTy, TypeId superTy)
reportError(location, TypeMismatch{superTy, subTy, mismatchContext()});
}
void Unifier::tryUnifyNegationWithType(TypeId subTy, TypeId superTy)
{
const NegationType* ntv = get<NegationType>(subTy);
if (!ntv)
ice("tryUnifyNegationWithType subTy must be a negation type");
// TODO: ~T </: U iff T <: U
reportError(location, TypeMismatch{superTy, subTy, mismatchContext()});
}
static void queueTypePack(std::vector<TypeId>& queue, DenseHashSet<TypePackId>& seenTypePacks, Unifier& state, TypePackId a, TypePackId anyTypePack)
{
while (true)

View file

@ -251,7 +251,6 @@ enum class ConstantNumberParseResult
Malformed,
BinOverflow,
HexOverflow,
DoublePrefix,
};
class AstExprConstantNumber : public AstExpr

View file

@ -14,15 +14,8 @@
LUAU_FASTINTVARIABLE(LuauRecursionLimit, 1000)
LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
LUAU_FASTFLAGVARIABLE(LuauErrorDoubleHexPrefix, false)
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuaReportParseIntegerIssues, false)
LUAU_FASTFLAGVARIABLE(LuauParserErrorsOnMissingDefaultTypePackArgument, false)
bool lua_telemetry_parsed_out_of_range_bin_integer = false;
bool lua_telemetry_parsed_out_of_range_hex_integer = false;
bool lua_telemetry_parsed_double_prefix_hex_integer = false;
#define ERROR_INVALID_INTERP_DOUBLE_BRACE "Double braces are not permitted within interpolated strings. Did you mean '\\{'?"
namespace Luau
@ -2093,18 +2086,8 @@ static ConstantNumberParseResult parseInteger(double& result, const char* data,
value = strtoull(data, &end, base);
if (errno == ERANGE)
{
if (DFFlag::LuaReportParseIntegerIssues)
{
if (base == 2)
lua_telemetry_parsed_out_of_range_bin_integer = true;
else
lua_telemetry_parsed_out_of_range_hex_integer = true;
}
return base == 2 ? ConstantNumberParseResult::BinOverflow : ConstantNumberParseResult::HexOverflow;
}
}
return ConstantNumberParseResult::Ok;
}
@ -2117,18 +2100,7 @@ static ConstantNumberParseResult parseDouble(double& result, const char* data)
// hexadecimal literal
if (data[0] == '0' && (data[1] == 'x' || data[1] == 'X') && data[2])
{
if (!FFlag::LuauErrorDoubleHexPrefix && data[2] == '0' && (data[3] == 'x' || data[3] == 'X'))
{
if (DFFlag::LuaReportParseIntegerIssues)
lua_telemetry_parsed_double_prefix_hex_integer = true;
ConstantNumberParseResult parseResult = parseInteger(result, data + 2, 16);
return parseResult == ConstantNumberParseResult::Malformed ? parseResult : ConstantNumberParseResult::DoublePrefix;
}
return parseInteger(result, data, 16); // pass in '0x' prefix, it's handled by 'strtoull'
}
char* end = nullptr;
double value = strtod(data, &end);

View file

@ -109,8 +109,10 @@ public:
void vdivsd(OperandX64 dst, OperandX64 src1, OperandX64 src2);
void vandpd(OperandX64 dst, OperandX64 src1, OperandX64 src2);
void vandnpd(OperandX64 dst, OperandX64 src1, OperandX64 src2);
void vxorpd(OperandX64 dst, OperandX64 src1, OperandX64 src2);
void vorpd(OperandX64 dst, OperandX64 src1, OperandX64 src2);
void vucomisd(OperandX64 src1, OperandX64 src2);
@ -137,6 +139,10 @@ public:
void vmaxsd(OperandX64 dst, OperandX64 src1, OperandX64 src2);
void vminsd(OperandX64 dst, OperandX64 src1, OperandX64 src2);
void vcmpltsd(OperandX64 dst, OperandX64 src1, OperandX64 src2);
void vblendvpd(RegisterX64 dst, RegisterX64 src1, OperandX64 mask, RegisterX64 src3);
// Run final checks
void finalize();
@ -152,6 +158,7 @@ public:
OperandX64 f32(float value);
OperandX64 f64(double value);
OperandX64 f32x4(float x, float y, float z, float w);
OperandX64 f64x2(double x, double y);
OperandX64 bytes(const void* ptr, size_t size, size_t align = 8);
void logAppend(const char* fmt, ...) LUAU_PRINTF_ATTR(2, 3);

View file

@ -3,8 +3,7 @@
#include "Luau/Common.h"
#include "Luau/Bytecode.h"
#include "IrData.h"
#include "Luau/IrData.h"
#include <vector>

View file

@ -1,7 +1,7 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "IrData.h"
#include "Luau/IrData.h"
#include <string>
#include <vector>

View file

@ -3,8 +3,7 @@
#include "Luau/Bytecode.h"
#include "Luau/Common.h"
#include "IrData.h"
#include "Luau/IrData.h"
namespace Luau
{

View file

@ -638,11 +638,21 @@ void AssemblyBuilderX64::vandpd(OperandX64 dst, OperandX64 src1, OperandX64 src2
placeAvx("vandpd", dst, src1, src2, 0x54, false, AVX_0F, AVX_66);
}
void AssemblyBuilderX64::vandnpd(OperandX64 dst, OperandX64 src1, OperandX64 src2)
{
placeAvx("vandnpd", dst, src1, src2, 0x55, false, AVX_0F, AVX_66);
}
void AssemblyBuilderX64::vxorpd(OperandX64 dst, OperandX64 src1, OperandX64 src2)
{
placeAvx("vxorpd", dst, src1, src2, 0x57, false, AVX_0F, AVX_66);
}
void AssemblyBuilderX64::vorpd(OperandX64 dst, OperandX64 src1, OperandX64 src2)
{
placeAvx("vorpd", dst, src1, src2, 0x56, false, AVX_0F, AVX_66);
}
void AssemblyBuilderX64::vucomisd(OperandX64 src1, OperandX64 src2)
{
placeAvx("vucomisd", src1, src2, 0x2e, false, AVX_0F, AVX_66);
@ -753,6 +763,17 @@ void AssemblyBuilderX64::vminsd(OperandX64 dst, OperandX64 src1, OperandX64 src2
placeAvx("vminsd", dst, src1, src2, 0x5d, false, AVX_0F, AVX_F2);
}
void AssemblyBuilderX64::vcmpltsd(OperandX64 dst, OperandX64 src1, OperandX64 src2)
{
placeAvx("vcmpltsd", dst, src1, src2, 0x01, 0xc2, false, AVX_0F, AVX_F2);
}
void AssemblyBuilderX64::vblendvpd(RegisterX64 dst, RegisterX64 src1, OperandX64 mask, RegisterX64 src3)
{
// bits [7:4] of imm8 are used to select register for operand 4
placeAvx("vblendvpd", dst, src1, mask, src3.index << 4, 0x4b, false, AVX_0F3A, AVX_66);
}
void AssemblyBuilderX64::finalize()
{
code.resize(codePos - code.data());
@ -834,6 +855,14 @@ OperandX64 AssemblyBuilderX64::f32x4(float x, float y, float z, float w)
return OperandX64(SizeX64::xmmword, noreg, 1, rip, int32_t(pos - data.size()));
}
OperandX64 AssemblyBuilderX64::f64x2(double x, double y)
{
size_t pos = allocateData(16, 16);
writef64(&data[pos], x);
writef64(&data[pos + 8], y);
return OperandX64(SizeX64::xmmword, noreg, 1, rip, int32_t(pos - data.size()));
}
OperandX64 AssemblyBuilderX64::bytes(const void* ptr, size_t size, size_t align)
{
size_t pos = allocateData(size, align);

View file

@ -5,6 +5,8 @@
#include "Luau/Common.h"
#include "Luau/CodeAllocator.h"
#include "Luau/CodeBlockUnwind.h"
#include "Luau/IrAnalysis.h"
#include "Luau/IrBuilder.h"
#include "Luau/UnwindBuilder.h"
#include "Luau/UnwindBuilderDwarf2.h"
#include "Luau/UnwindBuilderWin.h"
@ -13,8 +15,6 @@
#include "CodeGenX64.h"
#include "EmitCommonX64.h"
#include "EmitInstructionX64.h"
#include "IrAnalysis.h"
#include "IrBuilder.h"
#include "IrLoweringX64.h"
#include "NativeState.h"

View file

@ -424,6 +424,197 @@ BuiltinImplResult emitBuiltinMathLog(AssemblyBuilderX64& build, int nparams, int
return {BuiltinImplType::UsesFallback, 1};
}
BuiltinImplResult emitBuiltinMathLdexp(AssemblyBuilderX64& build, int nparams, int ra, int arg, OperandX64 args, int nresults, Label& fallback)
{
if (nparams < 2 || nresults > 1)
return {BuiltinImplType::None, -1};
if (build.logText)
build.logAppend("; inlined LBF_MATH_LDEXP\n");
jumpIfTagIsNot(build, arg, LUA_TNUMBER, fallback);
// TODO: jumpIfTagIsNot can be generalized to take OperandX64 and then we can use it here; let's wait until we see this more though
build.cmp(dword[args + offsetof(TValue, tt)], LUA_TNUMBER);
build.jcc(ConditionX64::NotEqual, fallback);
build.vmovsd(xmm0, luauRegValue(arg));
if (build.abi == ABIX64::Windows)
build.vcvttsd2si(rArg2, qword[args + offsetof(TValue, value)]);
else
build.vcvttsd2si(rArg1, qword[args + offsetof(TValue, value)]);
build.call(qword[rNativeContext + offsetof(NativeContext, libm_ldexp)]);
build.vmovsd(luauRegValue(ra), xmm0);
if (ra != arg)
build.mov(luauRegTag(ra), LUA_TNUMBER);
return {BuiltinImplType::UsesFallback, 1};
}
BuiltinImplResult emitBuiltinMathRound(AssemblyBuilderX64& build, int nparams, int ra, int arg, OperandX64 args, int nresults, Label& fallback)
{
if (nparams < 1 || nresults > 1)
return {BuiltinImplType::None, -1};
if (build.logText)
build.logAppend("; inlined LBF_MATH_ROUND\n");
jumpIfTagIsNot(build, arg, LUA_TNUMBER, fallback);
build.vmovsd(xmm0, luauRegValue(arg));
build.vandpd(xmm1, xmm0, build.f64x2(-0.0, -0.0));
build.vmovsd(xmm2, build.i64(0x3fdfffffffffffff)); // 0.49999999999999994
build.vorpd(xmm1, xmm1, xmm2);
build.vaddsd(xmm0, xmm0, xmm1);
build.vroundsd(xmm0, xmm0, xmm0, RoundingModeX64::RoundToZero);
build.vmovsd(luauRegValue(ra), xmm0);
if (ra != arg)
build.mov(luauRegTag(ra), LUA_TNUMBER);
return {BuiltinImplType::UsesFallback, 1};
}
BuiltinImplResult emitBuiltinMathFrexp(AssemblyBuilderX64& build, int nparams, int ra, int arg, OperandX64 args, int nresults, Label& fallback)
{
if (nparams < 1 || nresults > 2)
return {BuiltinImplType::None, -1};
if (build.logText)
build.logAppend("; inlined LBF_MATH_FREXP\n");
jumpIfTagIsNot(build, arg, LUA_TNUMBER, fallback);
build.vmovsd(xmm0, luauRegValue(arg));
if (build.abi == ABIX64::Windows)
build.lea(rArg2, sTemporarySlot);
else
build.lea(rArg1, sTemporarySlot);
build.call(qword[rNativeContext + offsetof(NativeContext, libm_frexp)]);
build.vmovsd(luauRegValue(ra), xmm0);
if (ra != arg)
build.mov(luauRegTag(ra), LUA_TNUMBER);
build.vcvtsi2sd(xmm0, xmm0, dword[sTemporarySlot + 0]);
build.vmovsd(luauRegValue(ra + 1), xmm0);
build.mov(luauRegTag(ra + 1), LUA_TNUMBER);
return {BuiltinImplType::UsesFallback, 2};
}
BuiltinImplResult emitBuiltinMathModf(AssemblyBuilderX64& build, int nparams, int ra, int arg, OperandX64 args, int nresults, Label& fallback)
{
if (nparams < 1 || nresults > 2)
return {BuiltinImplType::None, -1};
if (build.logText)
build.logAppend("; inlined LBF_MATH_MODF\n");
jumpIfTagIsNot(build, arg, LUA_TNUMBER, fallback);
build.vmovsd(xmm0, luauRegValue(arg));
if (build.abi == ABIX64::Windows)
build.lea(rArg2, sTemporarySlot);
else
build.lea(rArg1, sTemporarySlot);
build.call(qword[rNativeContext + offsetof(NativeContext, libm_modf)]);
build.vmovsd(xmm1, qword[sTemporarySlot + 0]);
build.vmovsd(luauRegValue(ra), xmm1);
if (ra != arg)
build.mov(luauRegTag(ra), LUA_TNUMBER);
build.vmovsd(luauRegValue(ra + 1), xmm0);
build.mov(luauRegTag(ra + 1), LUA_TNUMBER);
return {BuiltinImplType::UsesFallback, 2};
}
BuiltinImplResult emitBuiltinMathSign(AssemblyBuilderX64& build, int nparams, int ra, int arg, OperandX64 args, int nresults, Label& fallback)
{
if (nparams < 1 || nresults > 1)
return {BuiltinImplType::None, -1};
if (build.logText)
build.logAppend("; inlined LBF_MATH_SIGN\n");
jumpIfTagIsNot(build, arg, LUA_TNUMBER, fallback);
build.vmovsd(xmm0, luauRegValue(arg));
build.vxorpd(xmm1, xmm1, xmm1);
// Set xmm2 to -1 if arg < 0, else 0
build.vcmpltsd(xmm2, xmm0, xmm1);
build.vmovsd(xmm3, build.f64(-1));
build.vandpd(xmm2, xmm2, xmm3);
// Set mask bit to 1 if 0 < arg, else 0
build.vcmpltsd(xmm0, xmm1, xmm0);
// Result = (mask-bit == 1) ? 1.0 : xmm2
// If arg < 0 then xmm2 is -1 and mask-bit is 0, result is -1
// If arg == 0 then xmm2 is 0 and mask-bit is 0, result is 0
// If arg > 0 then xmm2 is 0 and mask-bit is 1, result is 1
build.vblendvpd(xmm0, xmm2, build.f64x2(1, 1), xmm0);
build.vmovsd(luauRegValue(ra), xmm0);
if (ra != arg)
build.mov(luauRegTag(ra), LUA_TNUMBER);
return {BuiltinImplType::UsesFallback, 1};
}
BuiltinImplResult emitBuiltinMathClamp(AssemblyBuilderX64& build, int nparams, int ra, int arg, OperandX64 args, int nresults, Label& fallback)
{
if (nparams < 3 || nresults > 1)
return {BuiltinImplType::None, -1};
if (build.logText)
build.logAppend("; inlined LBF_MATH_CLAMP\n");
jumpIfTagIsNot(build, arg, LUA_TNUMBER, fallback);
// TODO: jumpIfTagIsNot can be generalized to take OperandX64 and then we can use it here; let's wait until we see this more though
build.cmp(dword[args + offsetof(TValue, tt)], LUA_TNUMBER);
build.jcc(ConditionX64::NotEqual, fallback);
// TODO: jumpIfTagIsNot can be generalized to take OperandX64 and then we can use it here; let's wait until we see this more though
build.cmp(dword[args + sizeof(TValue) + offsetof(TValue, tt)], LUA_TNUMBER);
build.jcc(ConditionX64::NotEqual, fallback);
RegisterX64 min = xmm1;
RegisterX64 max = xmm2;
build.vmovsd(min, qword[args + offsetof(TValue, value)]);
build.vmovsd(max, qword[args + sizeof(TValue) + offsetof(TValue, value)]);
jumpOnNumberCmp(build, noreg, min, max, ConditionX64::NotLessEqual, fallback);
build.vmaxsd(xmm0, min, luauRegValue(arg));
build.vminsd(xmm0, max, xmm0);
build.vmovsd(luauRegValue(ra), xmm0);
if (ra != arg)
build.mov(luauRegTag(ra), LUA_TNUMBER);
return {BuiltinImplType::UsesFallback, 1};
}
BuiltinImplResult emitBuiltin(AssemblyBuilderX64& build, int bfid, int nparams, int ra, int arg, OperandX64 args, int nresults, Label& fallback)
{
switch (bfid)
@ -476,6 +667,18 @@ BuiltinImplResult emitBuiltin(AssemblyBuilderX64& build, int bfid, int nparams,
return emitBuiltinMathLog10(build, nparams, ra, arg, args, nresults, fallback);
case LBF_MATH_LOG:
return emitBuiltinMathLog(build, nparams, ra, arg, args, nresults, fallback);
case LBF_MATH_LDEXP:
return emitBuiltinMathLdexp(build, nparams, ra, arg, args, nresults, fallback);
case LBF_MATH_ROUND:
return emitBuiltinMathRound(build, nparams, ra, arg, args, nresults, fallback);
case LBF_MATH_FREXP:
return emitBuiltinMathFrexp(build, nparams, ra, arg, args, nresults, fallback);
case LBF_MATH_MODF:
return emitBuiltinMathModf(build, nparams, ra, arg, args, nresults, fallback);
case LBF_MATH_SIGN:
return emitBuiltinMathSign(build, nparams, ra, arg, args, nresults, fallback);
case LBF_MATH_CLAMP:
return emitBuiltinMathClamp(build, nparams, ra, arg, args, nresults, fallback);
default:
return {BuiltinImplType::None, -1};
}

View file

@ -38,6 +38,7 @@ constexpr unsigned kLocalsSize = 24; // 3 extra slots for our custom locals
constexpr OperandX64 sClosure = qword[rsp + kStackSize + 0]; // Closure* cl
constexpr OperandX64 sCode = qword[rsp + kStackSize + 8]; // Instruction* code
constexpr OperandX64 sTemporarySlot = addr[rsp + kStackSize + 16];
// TODO: These should be replaced with a portable call function that checks the ABI at runtime and reorders moves accordingly to avoid conflicts
#if defined(_WIN32)

View file

@ -1,8 +1,8 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "IrAnalysis.h"
#include "Luau/IrAnalysis.h"
#include "IrData.h"
#include "IrUtils.h"
#include "Luau/IrData.h"
#include "Luau/IrUtils.h"
#include <stddef.h>

View file

@ -1,11 +1,11 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "IrBuilder.h"
#include "Luau/IrBuilder.h"
#include "Luau/Common.h"
#include "Luau/IrUtils.h"
#include "CustomExecUtils.h"
#include "IrTranslation.h"
#include "IrUtils.h"
#include "lapi.h"

View file

@ -1,7 +1,7 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "IrDump.h"
#include "Luau/IrDump.h"
#include "IrUtils.h"
#include "Luau/IrUtils.h"
#include "lua.h"

View file

@ -3,11 +3,11 @@
#include "Luau/CodeGen.h"
#include "Luau/DenseHash.h"
#include "Luau/IrDump.h"
#include "Luau/IrUtils.h"
#include "EmitCommonX64.h"
#include "EmitInstructionX64.h"
#include "IrDump.h"
#include "IrUtils.h"
#include "NativeState.h"
#include "lstate.h"

View file

@ -2,8 +2,7 @@
#pragma once
#include "Luau/AssemblyBuilderX64.h"
#include "IrData.h"
#include "Luau/IrData.h"
#include <array>
#include <initializer_list>

View file

@ -2,8 +2,7 @@
#include "IrTranslation.h"
#include "Luau/Bytecode.h"
#include "IrBuilder.h"
#include "Luau/IrBuilder.h"
#include "lobject.h"
#include "ltm.h"

View file

@ -87,6 +87,10 @@ void initHelperFunctions(NativeState& data)
data.context.libm_log = log;
data.context.libm_log2 = log2;
data.context.libm_log10 = log10;
data.context.libm_ldexp = ldexp;
data.context.libm_round = round;
data.context.libm_frexp = frexp;
data.context.libm_modf = modf;
data.context.libm_asin = asin;
data.context.libm_sin = sin;

View file

@ -96,6 +96,10 @@ struct NativeContext
double (*libm_log)(double) = nullptr;
double (*libm_log2)(double) = nullptr;
double (*libm_log10)(double) = nullptr;
double (*libm_ldexp)(double, int) = nullptr;
double (*libm_round)(double) = nullptr;
double (*libm_frexp)(double, int*) = nullptr;
double (*libm_modf)(double, double*) = nullptr;
// Helper functions
bool (*forgLoopNodeIter)(lua_State* L, Table* h, int index, TValue* ra) = nullptr;

View file

@ -25,10 +25,6 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineThreshold, 25)
LUAU_FASTINTVARIABLE(LuauCompileInlineThresholdMaxBoost, 300)
LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5)
LUAU_FASTFLAGVARIABLE(LuauMultiAssignmentConflictFix, false)
LUAU_FASTFLAGVARIABLE(LuauSelfAssignmentSkip, false)
LUAU_FASTFLAGVARIABLE(LuauCompileInterpStringLimit, false)
namespace Luau
{
@ -1580,8 +1576,7 @@ struct Compiler
RegScope rs(this);
uint8_t baseReg = FFlag::LuauCompileInterpStringLimit ? allocReg(expr, unsigned(2 + expr->expressions.size))
: allocReg(expr, uint8_t(2 + expr->expressions.size));
uint8_t baseReg = allocReg(expr, unsigned(2 + expr->expressions.size));
emitLoadK(baseReg, formatStringIndex);
@ -2030,7 +2025,7 @@ struct Compiler
if (int reg = getExprLocalReg(expr); reg >= 0)
{
// Optimization: we don't need to move if target happens to be in the same register
if (!FFlag::LuauSelfAssignmentSkip || options.optimizationLevel == 0 || target != reg)
if (options.optimizationLevel == 0 || target != reg)
bytecode.emitABC(LOP_MOVE, target, uint8_t(reg), 0);
}
else
@ -2982,8 +2977,6 @@ struct Compiler
Visitor visitor(this);
if (FFlag::LuauMultiAssignmentConflictFix)
{
// mark any registers that are used *after* assignment as conflicting
// first we go through assignments to locals, since they are performed before assignments to other l-values
@ -3008,21 +3001,6 @@ struct Compiler
if (li.kind != LValue::Kind_Local && i < values.size)
values.data[i]->visit(&visitor);
}
}
else
{
// mark any registers that are used *after* assignment as conflicting
for (size_t i = 0; i < vars.size(); ++i)
{
const LValue& li = vars[i].lvalue;
if (i < values.size)
values.data[i]->visit(&visitor);
if (li.kind == LValue::Kind_Local)
visitor.assigned[li.reg] = true;
}
}
// mark any registers used in trailing expressions as conflicting as well
for (size_t i = vars.size(); i < values.size; ++i)

View file

@ -127,7 +127,7 @@ $(ISOCLINE_OBJECTS): CXXFLAGS+=-Wno-unused-function -Iextern/isocline/include
$(TESTS_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IAnalysis/include -ICodeGen/include -IVM/include -ICLI -Iextern -DDOCTEST_CONFIG_DOUBLE_STRINGIFY
$(REPL_CLI_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IVM/include -ICodeGen/include -Iextern -Iextern/isocline/include
$(ANALYZE_CLI_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -IAnalysis/include -Iextern
$(FUZZ_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IAnalysis/include -IVM/include
$(FUZZ_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IAnalysis/include -IVM/include -ICodeGen/include
$(TESTS_TARGET): LDFLAGS+=-lpthread
$(REPL_CLI_TARGET): LDFLAGS+=-lpthread
@ -192,7 +192,7 @@ $(TESTS_TARGET) $(REPL_CLI_TARGET) $(ANALYZE_CLI_TARGET):
$(CXX) $^ $(LDFLAGS) -o $@
# executable targets for fuzzing
fuzz-%: $(BUILD)/fuzz/%.cpp.o $(ANALYSIS_TARGET) $(COMPILER_TARGET) $(AST_TARGET) $(VM_TARGET)
fuzz-%: $(BUILD)/fuzz/%.cpp.o $(ANALYSIS_TARGET) $(COMPILER_TARGET) $(AST_TARGET) $(CODEGEN_TARGET) $(VM_TARGET)
$(CXX) $^ $(LDFLAGS) -o $@
fuzz-proto: $(BUILD)/fuzz/proto.cpp.o $(BUILD)/fuzz/protoprint.cpp.o $(BUILD)/fuzz/luau.pb.cpp.o $(ANALYSIS_TARGET) $(COMPILER_TARGET) $(AST_TARGET) $(VM_TARGET) | build/libprotobuf-mutator

View file

@ -63,6 +63,11 @@ target_sources(Luau.CodeGen PRIVATE
CodeGen/include/Luau/CodeGen.h
CodeGen/include/Luau/ConditionA64.h
CodeGen/include/Luau/ConditionX64.h
CodeGen/include/Luau/IrAnalysis.h
CodeGen/include/Luau/IrBuilder.h
CodeGen/include/Luau/IrDump.h
CodeGen/include/Luau/IrData.h
CodeGen/include/Luau/IrUtils.h
CodeGen/include/Luau/Label.h
CodeGen/include/Luau/OperandX64.h
CodeGen/include/Luau/RegisterA64.h
@ -100,13 +105,8 @@ target_sources(Luau.CodeGen PRIVATE
CodeGen/src/EmitInstructionX64.h
CodeGen/src/Fallbacks.h
CodeGen/src/FallbacksProlog.h
CodeGen/src/IrAnalysis.h
CodeGen/src/IrBuilder.h
CodeGen/src/IrDump.h
CodeGen/src/IrData.h
CodeGen/src/IrLoweringX64.h
CodeGen/src/IrTranslation.h
CodeGen/src/IrUtils.h
CodeGen/src/NativeState.h
)
@ -120,7 +120,7 @@ target_sources(Luau.Analysis PRIVATE
Analysis/include/Luau/BuiltinDefinitions.h
Analysis/include/Luau/Clone.h
Analysis/include/Luau/Config.h
Analysis/include/Luau/Connective.h
Analysis/include/Luau/Refinement.h
Analysis/include/Luau/Constraint.h
Analysis/include/Luau/ConstraintGraphBuilder.h
Analysis/include/Luau/ConstraintSolver.h
@ -175,7 +175,7 @@ target_sources(Luau.Analysis PRIVATE
Analysis/src/BuiltinDefinitions.cpp
Analysis/src/Clone.cpp
Analysis/src/Config.cpp
Analysis/src/Connective.cpp
Analysis/src/Refinement.cpp
Analysis/src/Constraint.cpp
Analysis/src/ConstraintGraphBuilder.cpp
Analysis/src/ConstraintSolver.cpp

View file

@ -17,6 +17,8 @@
#include <mach/mach_time.h>
#endif
#include <time.h>
static double clock_period()

View file

@ -8,8 +8,6 @@
#include <string.h>
#include <stdio.h>
LUAU_FASTFLAGVARIABLE(LuauStringFormatAnyFix, false)
// macro to `unsign' a character
#define uchar(c) ((unsigned char)(c))
@ -1039,21 +1037,11 @@ static int str_format(lua_State* L)
if (formatItemSize != 1)
luaL_error(L, "'%%*' does not take a form");
if (FFlag::LuauStringFormatAnyFix)
{
size_t length;
const char* string = luaL_tolstring(L, arg, &length);
luaL_addlstring(&b, string, length, -2);
lua_pop(L, 1);
}
else
{
size_t length;
const char* string = luaL_tolstring(L, arg, &length);
luaL_addlstring(&b, string, length, -1);
}
continue; // skip the `luaL_addlstring' at the end
}

View file

@ -4,6 +4,7 @@
#include "Luau/BuiltinDefinitions.h"
#include "Luau/BytecodeBuilder.h"
#include "Luau/CodeGen.h"
#include "Luau/Common.h"
#include "Luau/Compiler.h"
#include "Luau/Frontend.h"
@ -25,11 +26,13 @@ const bool kFuzzLinter = true;
const bool kFuzzTypeck = true;
const bool kFuzzVM = true;
const bool kFuzzTranspile = true;
const bool kFuzzCodegen = true;
// Should we generate type annotations?
const bool kFuzzTypes = true;
static_assert(!(kFuzzVM && !kFuzzCompiler), "VM requires the compiler!");
static_assert(!(kFuzzCodegen && !kFuzzVM), "Codegen requires the VM!");
std::vector<std::string> protoprint(const luau::ModuleSet& stat, bool types);
@ -83,6 +86,9 @@ lua_State* createGlobalState()
{
lua_State* L = lua_newstate(allocate, NULL);
if (kFuzzCodegen && Luau::CodeGen::isSupported())
Luau::CodeGen::create(L);
lua_callbacks(L)->interrupt = interrupt;
luaL_openlibs(L);
@ -349,11 +355,15 @@ DEFINE_PROTO_FUZZER(const luau::ModuleSet& message)
{
static lua_State* globalState = createGlobalState();
auto runCode = [](const std::string& bytecode, bool useCodegen) {
lua_State* L = lua_newthread(globalState);
luaL_sandboxthread(L);
if (luau_load(L, "=fuzz", bytecode.data(), bytecode.size(), 0) == 0)
{
if (useCodegen)
Luau::CodeGen::compile(L, -1);
interruptDeadline = std::chrono::system_clock::now() + kInterruptTimeout;
lua_resume(L, NULL, 0);
@ -364,5 +374,11 @@ DEFINE_PROTO_FUZZER(const luau::ModuleSet& message)
// we'd expect full GC to reclaim all memory allocated by the script
lua_gc(globalState, LUA_GCCOLLECT, 0);
LUAU_ASSERT(heapSize < 256 * 1024);
};
runCode(bytecode, false);
if (kFuzzCodegen && Luau::CodeGen::isSupported())
runCode(bytecode, true);
}
}

View file

@ -441,12 +441,16 @@ TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "AVXBinaryInstructionForms")
SINGLE_COMPARE(vmulsd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0x2b, 0x59, 0xc6);
SINGLE_COMPARE(vdivsd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0x2b, 0x5e, 0xc6);
SINGLE_COMPARE(vorpd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0x29, 0x56, 0xc6);
SINGLE_COMPARE(vxorpd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0x29, 0x57, 0xc6);
SINGLE_COMPARE(vandpd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0x29, 0x54, 0xc6);
SINGLE_COMPARE(vandnpd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0x29, 0x55, 0xc6);
SINGLE_COMPARE(vmaxsd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0x2b, 0x5f, 0xc6);
SINGLE_COMPARE(vminsd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0x2b, 0x5d, 0xc6);
SINGLE_COMPARE(vcmpltsd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0x2b, 0xc2, 0xc6, 0x01);
}
TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "AVXUnaryMergeInstructionForms")
@ -510,6 +514,7 @@ TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "AVXTernaryInstructionForms")
SINGLE_COMPARE(
vroundsd(xmm8, xmm13, xmmword[r13 + rdx], RoundingModeX64::RoundToPositiveInfinity), 0xc4, 0x43, 0x11, 0x0b, 0x44, 0x15, 0x00, 0x0a);
SINGLE_COMPARE(vroundsd(xmm9, xmm14, xmmword[rcx + r10], RoundingModeX64::RoundToZero), 0xc4, 0x23, 0x09, 0x0b, 0x0c, 0x11, 0x0b);
SINGLE_COMPARE(vblendvpd(xmm7, xmm12, xmmword[rcx + r10], xmm5), 0xc4, 0xa3, 0x19, 0x4b, 0x3c, 0x11, 0x50);
}
TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "MiscInstructions")
@ -621,6 +626,7 @@ TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "Constants")
build.vmovaps(xmm4, build.f32x4(1.0f, 2.0f, 4.0f, 8.0f));
char arr[16] = "hello world!123";
build.vmovupd(xmm5, build.bytes(arr, 16, 8));
build.vmovapd(xmm5, build.f64x2(5.0, 6.0));
build.ret();
},
{
@ -630,9 +636,12 @@ TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "Constants")
0xc4, 0xe1, 0x7b, 0x10, 0x1d, 0xcc, 0xff, 0xff, 0xff,
0xc4, 0xe1, 0x78, 0x28, 0x25, 0xab, 0xff, 0xff, 0xff,
0xc4, 0xe1, 0x79, 0x10, 0x2d, 0x92, 0xff, 0xff, 0xff,
0xc4, 0xe1, 0x79, 0x28, 0x2d, 0x79, 0xff, 0xff, 0xff,
0xc3
},
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x40,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x40,
'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '!', '1', '2', '3', 0x0,
0x00, 0x00, 0x80, 0x3f,
0x00, 0x00, 0x00, 0x40,

View file

@ -15,7 +15,6 @@
LUAU_FASTFLAG(LuauTraceTypesInNonstrictMode2)
LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel)
LUAU_FASTFLAG(LuauFixAutocompleteInIf)
LUAU_FASTFLAG(LuauFixAutocompleteInWhile)
LUAU_FASTFLAG(LuauFixAutocompleteInFor)
@ -859,8 +858,6 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_if_middle_keywords")
CHECK_EQ(ac2.entryMap.count("end"), 0);
CHECK_EQ(ac2.context, AutocompleteContext::Keyword);
if (FFlag::LuauFixAutocompleteInIf)
{
check(R"(
if x t@1
)");
@ -871,18 +868,6 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_if_middle_keywords")
CHECK_EQ(ac3.entryMap.count("and"), 1);
CHECK_EQ(ac3.entryMap.count("or"), 1);
CHECK_EQ(ac3.context, AutocompleteContext::Keyword);
}
else
{
check(R"(
if x t@1
)");
auto ac3 = autocomplete('1');
CHECK_EQ(1, ac3.entryMap.size());
CHECK_EQ(ac3.entryMap.count("then"), 1);
CHECK_EQ(ac3.context, AutocompleteContext::Keyword);
}
check(R"(
if x then
@ -926,8 +911,6 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_if_middle_keywords")
CHECK_EQ(ac5.entryMap.count("end"), 0);
CHECK_EQ(ac5.context, AutocompleteContext::Statement);
if (FFlag::LuauFixAutocompleteInIf)
{
check(R"(
if t@1
)");
@ -942,7 +925,6 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_if_middle_keywords")
CHECK_EQ(ac6.entryMap.count("end"), 0);
CHECK_EQ(ac6.context, AutocompleteContext::Expression);
}
}
TEST_CASE_FIXTURE(ACFixture, "autocomplete_until_in_repeat")
{
@ -3428,6 +3410,8 @@ TEST_CASE_FIXTURE(ACFixture, "globals_are_order_independent")
TEST_CASE_FIXTURE(ACFixture, "type_reduction_is_hooked_up_to_autocomplete")
{
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
check(R"(
type T = { x: (number & string)? }
@ -3447,15 +3431,13 @@ TEST_CASE_FIXTURE(ACFixture, "type_reduction_is_hooked_up_to_autocomplete")
REQUIRE(ac1.entryMap.count("x"));
std::optional<TypeId> ty1 = ac1.entryMap.at("x").type;
REQUIRE(ty1);
CHECK("(number & string)?" == toString(*ty1, opts));
// CHECK("nil" == toString(*ty1, opts));
CHECK("nil" == toString(*ty1, opts));
auto ac2 = autocomplete('2');
REQUIRE(ac2.entryMap.count("thingamabob"));
std::optional<TypeId> ty2 = ac2.entryMap.at("thingamabob").type;
REQUIRE(ty2);
CHECK("{| x: (number & string)? |}" == toString(*ty2, opts));
// CHECK("{| x: nil |}" == toString(*ty2, opts));
CHECK("{| x: nil |}" == toString(*ty2, opts));
}
TEST_CASE_FIXTURE(ACFixture, "string_contents_is_available_to_callback")

View file

@ -1025,8 +1025,6 @@ L0: RETURN R0 0
TEST_CASE("AndOr")
{
ScopedFastFlag luauSelfAssignmentSkip{"LuauSelfAssignmentSkip", true};
// codegen for constant, local, global for and
CHECK_EQ("\n" + compileFunction0("local a = 1 a = a and 2 return a"), R"(
LOADN R0 1
@ -1319,8 +1317,6 @@ RETURN R0 0
TEST_CASE("InterpStringRegisterLimit")
{
ScopedFastFlag luauCompileInterpStringLimit{"LuauCompileInterpStringLimit", true};
CHECK_THROWS_AS(compileFunction0(("local a = `" + rep("{1}", 254) + "`").c_str()), std::exception);
CHECK_THROWS_AS(compileFunction0(("local a = `" + rep("{1}", 253) + "`").c_str()), std::exception);
}
@ -2262,8 +2258,6 @@ L1: RETURN R3 -1
TEST_CASE("UpvaluesLoopsBytecode")
{
ScopedFastFlag luauSelfAssignmentSkip{"LuauSelfAssignmentSkip", true};
CHECK_EQ("\n" + compileFunction(R"(
function test()
for i=1,10 do
@ -5161,8 +5155,6 @@ RETURN R1 1
TEST_CASE("InlineMutate")
{
ScopedFastFlag luauSelfAssignmentSkip{"LuauSelfAssignmentSkip", true};
// if the argument is mutated, it gets a register even if the value is constant
CHECK_EQ("\n" + compileFunction(R"(
local function foo(a)
@ -6756,8 +6748,6 @@ MOVE R1 R3
RETURN R0 0
)");
ScopedFastFlag luauMultiAssignmentConflictFix{"LuauMultiAssignmentConflictFix", true};
// because we perform assignments to complex l-values after assignments to locals, we make sure register conflicts are tracked accordingly
CHECK_EQ("\n" + compileFunction0(R"(
local a, b = ...
@ -6795,8 +6785,6 @@ L0: RETURN R1 -1
TEST_CASE("SkipSelfAssignment")
{
ScopedFastFlag luauSelfAssignmentSkip{"LuauSelfAssignmentSkip", true};
CHECK_EQ("\n" + compileFunction0("local a a = a"), R"(
LOADNIL R0
RETURN R0 0

View file

@ -297,8 +297,6 @@ TEST_CASE("Clear")
TEST_CASE("Strings")
{
ScopedFastFlag luauStringFormatAnyFix{"LuauStringFormatAnyFix", true};
runConformance("strings.lua");
}

View file

@ -1706,23 +1706,6 @@ local _ = 0x10000000000000000
CHECK_EQ(result.warnings[1].text, "Hexadecimal number literal exceeded available precision and has been truncated to 2^64");
}
// TODO: remove with FFlagLuauErrorDoubleHexPrefix
TEST_CASE_FIXTURE(Fixture, "IntegerParsingDoublePrefix")
{
ScopedFastFlag luauErrorDoubleHexPrefix{"LuauErrorDoubleHexPrefix", false}; // Lint will be available until we start rejecting code
LintResult result = lint(R"(
local _ = 0x0x123
local _ = 0x0xffffffffffffffffffffffffffffffffff
)");
REQUIRE(2 == result.warnings.size());
CHECK_EQ(result.warnings[0].text,
"Hexadecimal number literal has a double prefix, which will fail to parse in the future; remove the extra 0x to fix");
CHECK_EQ(result.warnings[1].text,
"Hexadecimal number literal has a double prefix, which will fail to parse in the future; remove the extra 0x to fix");
}
TEST_CASE_FIXTURE(Fixture, "ComparisonPrecedence")
{
LintResult result = lint(R"(

View file

@ -2,6 +2,7 @@
#include "Fixture.h"
#include "Luau/AstQuery.h"
#include "Luau/Common.h"
#include "Luau/Type.h"
#include "doctest.h"
@ -9,6 +10,8 @@
#include "Luau/Normalize.h"
#include "Luau/BuiltinDefinitions.h"
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
using namespace Luau;
namespace
@ -377,10 +380,26 @@ struct NormalizeFixture : Fixture
normalizer.clearCaches();
CheckResult result = check("type _Res = " + annotation);
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
{
SourceModule* sourceModule = getMainSourceModule();
REQUIRE(sourceModule);
AstNode* node = findNodeAtPosition(*sourceModule, {0, 5});
REQUIRE(node);
AstStatTypeAlias* alias = node->as<AstStatTypeAlias>();
REQUIRE(alias);
TypeId* originalTy = getMainModule()->astOriginalResolvedTypes.find(alias->type);
REQUIRE(originalTy);
return normalizer.normalize(*originalTy);
}
else
{
std::optional<TypeId> ty = lookupType("_Res");
REQUIRE(ty);
return normalizer.normalize(*ty);
}
}
TypeId normal(const std::string& annotation)
{

View file

@ -683,8 +683,6 @@ TEST_CASE_FIXTURE(Fixture, "parse_numbers_binary")
TEST_CASE_FIXTURE(Fixture, "parse_numbers_error")
{
ScopedFastFlag luauErrorDoubleHexPrefix{"LuauErrorDoubleHexPrefix", true};
CHECK_EQ(getParseError("return 0b123"), "Malformed number");
CHECK_EQ(getParseError("return 123x"), "Malformed number");
CHECK_EQ(getParseError("return 0xg"), "Malformed number");
@ -693,13 +691,6 @@ TEST_CASE_FIXTURE(Fixture, "parse_numbers_error")
CHECK_EQ(getParseError("return 0x0xffffffffffffffffffffffffffff"), "Malformed number");
}
TEST_CASE_FIXTURE(Fixture, "parse_numbers_error_soft")
{
ScopedFastFlag luauErrorDoubleHexPrefix{"LuauErrorDoubleHexPrefix", false};
CHECK_EQ(getParseError("return 0x0x0x0x0x0x0x0"), "Malformed number");
}
TEST_CASE_FIXTURE(Fixture, "break_return_not_last_error")
{
CHECK_EQ(getParseError("return 0 print(5)"), "Expected <eof>, got 'print'");

View file

@ -80,14 +80,9 @@ n1 [label="AnyType 1"];
TEST_CASE_FIXTURE(Fixture, "bound")
{
CheckResult result = check(R"(
function a(): number return 444 end
local b = a()
)");
LUAU_REQUIRE_NO_ERRORS(result);
TypeArena arena;
std::optional<TypeId> ty = getType("b");
REQUIRE(bool(ty));
TypeId ty = arena.addType(BoundType{builtinTypes->numberType});
ToDotOptions opts;
opts.showPointers = false;
@ -96,7 +91,7 @@ n1 [label="BoundType 1"];
n1 -> n2;
n2 [label="number"];
})",
toDot(*ty, opts));
toDot(ty, opts));
}
TEST_CASE_FIXTURE(Fixture, "function")
@ -172,10 +167,9 @@ n3 [label="number"];
TEST_CASE_FIXTURE(Fixture, "intersection")
{
CheckResult result = check(R"(
local a: string & number -- uninhabited
)");
LUAU_REQUIRE_NO_ERRORS(result);
TypeArena arena;
TypeId ty = arena.addType(IntersectionType{{builtinTypes->stringType, builtinTypes->numberType}});
ToDotOptions opts;
opts.showPointers = false;
@ -186,7 +180,7 @@ n2 [label="string"];
n1 -> n3;
n3 [label="number"];
})",
toDot(requireType("a"), opts));
toDot(ty, opts));
}
TEST_CASE_FIXTURE(Fixture, "table")
@ -396,44 +390,25 @@ n3 [label="number"];
TEST_CASE_FIXTURE(Fixture, "bound_table")
{
CheckResult result = check(R"(
local a = {x=2}
local b
b.x = 2
b = a
)");
LUAU_REQUIRE_NO_ERRORS(result);
TypeArena arena;
std::optional<TypeId> ty = getType("b");
REQUIRE(bool(ty));
TypeId ty = arena.addType(TableType{});
getMutable<TableType>(ty)->props["x"] = {builtinTypes->numberType};
TypeId boundTy = arena.addType(TableType{});
getMutable<TableType>(boundTy)->boundTo = ty;
ToDotOptions opts;
opts.showPointers = false;
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ(R"(digraph graphname {
n1 [label="BoundType 1"];
n1 -> n2;
n2 [label="TableType 2"];
n2 -> n3 [label="boundTo"];
n3 [label="TableType a"];
n3 -> n4 [label="x"];
n4 [label="number"];
})",
toDot(*ty, opts));
}
else
{
CHECK_EQ(R"(digraph graphname {
n1 [label="TableType 1"];
n1 -> n2 [label="boundTo"];
n2 [label="TableType a"];
n2 [label="TableType 2"];
n2 -> n3 [label="x"];
n3 [label="number"];
})",
toDot(*ty, opts));
}
toDot(boundTy, opts));
}
TEST_CASE_FIXTURE(Fixture, "builtintypes")

View file

@ -291,9 +291,9 @@ TEST_CASE_FIXTURE(Fixture, "quit_stringifying_type_when_length_is_exceeded")
{
o.maxTypeLength = 30;
CHECK_EQ(toString(requireType("f0"), o), "() -> ()");
CHECK_EQ(toString(requireType("f1"), o), "<a>(a) -> (() -> ()) | (a & ~(false?))... *TRUNCATED*");
CHECK_EQ(toString(requireType("f2"), o), "<b>(b) -> (<a>(a) -> (() -> ()) | (a & ~(false?))... *TRUNCATED*");
CHECK_EQ(toString(requireType("f3"), o), "<c>(c) -> (<b>(b) -> (<a>(a) -> (() -> ()) | (a & ~(false?))... *TRUNCATED*");
CHECK_EQ(toString(requireType("f1"), o), "<a>(a) -> (() -> ()) | (a & ~false & ~nil)... *TRUNCATED*");
CHECK_EQ(toString(requireType("f2"), o), "<b>(b) -> (<a>(a) -> (() -> ()) | (a & ~false & ~nil)... *TRUNCATED*");
CHECK_EQ(toString(requireType("f3"), o), "<c>(c) -> (<b>(b) -> (<a>(a) -> (() -> ()) | (a & ~false & ~nil)... *TRUNCATED*");
}
else
{
@ -321,9 +321,9 @@ TEST_CASE_FIXTURE(Fixture, "stringifying_type_is_still_capped_when_exhaustive")
{
o.maxTypeLength = 30;
CHECK_EQ(toString(requireType("f0"), o), "() -> ()");
CHECK_EQ(toString(requireType("f1"), o), "<a>(a) -> (() -> ()) | (a & ~(false?))... *TRUNCATED*");
CHECK_EQ(toString(requireType("f2"), o), "<b>(b) -> (<a>(a) -> (() -> ()) | (a & ~(false?))... *TRUNCATED*");
CHECK_EQ(toString(requireType("f3"), o), "<c>(c) -> (<b>(b) -> (<a>(a) -> (() -> ()) | (a & ~(false?))... *TRUNCATED*");
CHECK_EQ(toString(requireType("f1"), o), "<a>(a) -> (() -> ()) | (a & ~false & ~nil)... *TRUNCATED*");
CHECK_EQ(toString(requireType("f2"), o), "<b>(b) -> (<a>(a) -> (() -> ()) | (a & ~false & ~nil)... *TRUNCATED*");
CHECK_EQ(toString(requireType("f3"), o), "<c>(c) -> (<b>(b) -> (<a>(a) -> (() -> ()) | (a & ~false & ~nil)... *TRUNCATED*");
}
else
{

View file

@ -357,8 +357,6 @@ TEST_CASE_FIXTURE(Fixture, "cli_38393_recursive_intersection_oom")
type t0<t0> = ((typeof(_))&((t0)&(((typeof(_))&(t0))->typeof(_))),{n163:any,})->(any,typeof(_))
_(_)
)");
LUAU_REQUIRE_ERRORS(result);
}
TEST_CASE_FIXTURE(Fixture, "type_alias_fwd_declaration_is_precise")

View file

@ -131,6 +131,22 @@ TEST_CASE_FIXTURE(Fixture, "should_still_pick_an_overload_whose_arguments_are_un
}
TEST_CASE_FIXTURE(Fixture, "propagates_name")
{
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CheckResult result = check(R"(
type A={a:number}
type B={b:string}
local c:A&B
local b = c
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK("{| a: number, b: string |}" == toString(requireType("b")));
}
else
{
const std::string code = R"(
type A={a:number}
@ -139,6 +155,7 @@ TEST_CASE_FIXTURE(Fixture, "propagates_name")
local c:A&B
local b = c
)";
const std::string expected = R"(
type A={a:number}
type B={b:string}
@ -149,6 +166,7 @@ TEST_CASE_FIXTURE(Fixture, "propagates_name")
CHECK_EQ(expected, decorateWithTypes(code));
}
}
TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_with_property_guaranteed_to_exist")
{
@ -161,17 +179,10 @@ TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_with_property_guarante
)");
LUAU_REQUIRE_NO_ERRORS(result);
const IntersectionType* r = get<IntersectionType>(requireType("r"));
REQUIRE(r);
TableType* a = getMutable<TableType>(r->parts[0]);
REQUIRE(a);
CHECK_EQ(typeChecker.numberType, a->props["y"].type);
TableType* b = getMutable<TableType>(r->parts[1]);
REQUIRE(b);
CHECK_EQ(typeChecker.numberType, b->props["y"].type);
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK("{| y: number |}" == toString(requireType("r")));
else
CHECK("{| y: number |} & {| y: number |}" == toString(requireType("r")));
}
TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_works_at_arbitrary_depth")
@ -207,6 +218,9 @@ TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_with_mixed_types")
)");
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ("never", toString(requireType("r")));
else
CHECK_EQ("number & string", toString(requireType("r")));
}
@ -387,6 +401,9 @@ local a: XYZ = 3
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ(toString(result.errors[0]), R"(Type 'number' could not be converted into '{| x: number, y: number, z: number |}')");
else
CHECK_EQ(toString(result.errors[0]), R"(Type 'number' could not be converted into 'X & Y & Z'
caused by:
Not all intersection parts are compatible. Type 'number' could not be converted into 'X')");
@ -404,7 +421,11 @@ local b: number = a
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ(toString(result.errors[0]), R"(Type 'X & Y & Z' could not be converted into 'number'; none of the intersection parts are compatible)");
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ(toString(result.errors[0]), R"(Type '{| x: number, y: number, z: number |}' could not be converted into 'number')");
else
CHECK_EQ(
toString(result.errors[0]), R"(Type 'X & Y & Z' could not be converted into 'number'; none of the intersection parts are compatible)");
}
TEST_CASE_FIXTURE(Fixture, "overload_is_not_a_function")
@ -444,7 +465,11 @@ TEST_CASE_FIXTURE(Fixture, "intersect_bool_and_false")
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ(toString(result.errors[0]), "Type 'boolean & false' could not be converted into 'true'; none of the intersection parts are compatible");
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ(toString(result.errors[0]), "Type 'false' could not be converted into 'true'");
else
CHECK_EQ(
toString(result.errors[0]), "Type 'boolean & false' could not be converted into 'true'; none of the intersection parts are compatible");
}
TEST_CASE_FIXTURE(Fixture, "intersect_false_and_bool_and_false")
@ -456,10 +481,15 @@ TEST_CASE_FIXTURE(Fixture, "intersect_false_and_bool_and_false")
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ(toString(result.errors[0]), "Type 'false' could not be converted into 'true'");
else
{
// TODO: odd stringification of `false & (boolean & false)`.)
CHECK_EQ(toString(result.errors[0]),
"Type 'boolean & false & false' could not be converted into 'true'; none of the intersection parts are compatible");
}
}
TEST_CASE_FIXTURE(Fixture, "intersect_saturate_overloaded_functions")
{
@ -496,9 +526,22 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables")
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ(toString(result.errors[0]), "Type '{| p: number?, q: number?, r: number? |} & {| p: number?, q: string? |}' could not be converted into "
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ(toString(result.errors[0]),
"Type '{| p: number?, q: nil, r: number? |}' could not be converted into '{| p: nil |}'\n"
"caused by:\n"
" Property 'p' is not compatible. Type 'number?' could not be converted into 'nil'\n"
"caused by:\n"
" Not all union options are compatible. Type 'number' could not be converted into 'nil' in an invariant context");
}
else
{
CHECK_EQ(toString(result.errors[0]),
"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");
}
}
TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_top_properties")
{
@ -508,10 +551,36 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_top_properties")
local z : { p : string?, q : number? } = x -- Not OK
)");
if (FFlag::DebugLuauDeferredConstraintResolution)
{
LUAU_REQUIRE_ERROR_COUNT(2, result);
CHECK_EQ(toString(result.errors[0]),
"Type '{| p: number?, q: string? |}' could not be converted into '{| p: string?, q: number? |}'\n"
"caused by:\n"
" Property 'p' is not compatible. Type 'number?' could not be converted into 'string?'\n"
"caused by:\n"
" Not all union options are compatible. Type 'number' could not be converted into 'string?'\n"
"caused by:\n"
" None of the union options are compatible. For example: Type 'number' could not be converted into 'string' in an invariant context");
CHECK_EQ(toString(result.errors[1]),
"Type '{| p: number?, q: string? |}' could not be converted into '{| p: string?, q: number? |}'\n"
"caused by:\n"
" Property 'q' is not compatible. Type 'string?' could not be converted into 'number?'\n"
"caused by:\n"
" Not all union options are compatible. Type 'string' could not be converted into 'number?'\n"
"caused by:\n"
" None of the union options are compatible. For example: Type 'string' could not be converted into 'number' in an invariant context");
}
else
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ(toString(result.errors[0]), "Type '{| p: number?, q: any |} & {| p: unknown, q: string? |}' could not be converted into '{| p: string?, "
CHECK_EQ(toString(result.errors[0]),
"Type '{| p: number?, q: any |} & {| p: unknown, q: string? |}' could not be converted into '{| p: string?, "
"q: number? |}'; none of the intersection parts are compatible");
}
}
TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_never_properties")
{
@ -537,10 +606,19 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_returning_intersections")
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ(toString(result.errors[0]),
"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");
}
else
{
CHECK_EQ(toString(result.errors[0]),
"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");
}
}
TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic")
{
@ -840,7 +918,7 @@ TEST_CASE_FIXTURE(Fixture, "less_greedy_unification_with_intersection_types")
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ("({| x: number |} & {| x: string |}) -> {| x: number |} & {| x: string |}", toString(requireType("f")));
CHECK_EQ("(never) -> never", toString(requireType("f")));
}
TEST_CASE_FIXTURE(Fixture, "less_greedy_unification_with_intersection_types_2")
@ -856,7 +934,7 @@ TEST_CASE_FIXTURE(Fixture, "less_greedy_unification_with_intersection_types_2")
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ("({| x: number |} & {| x: string |}) -> number & string", toString(requireType("f")));
CHECK_EQ("(never) -> never", toString(requireType("f")));
}
TEST_SUITE_END();

View file

@ -661,4 +661,32 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "loop_iter_metamethod_ok_with_inference")
CHECK(toString(requireType("b")) == "string");
}
TEST_CASE_FIXTURE(Fixture, "for_loop_lower_bound_is_string")
{
CheckResult result = check(R"(
for i: unknown = 1, 10 do end
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(Fixture, "for_loop_lower_bound_is_string_2")
{
CheckResult result = check(R"(
for i: never = 1, 10 do end
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ("Type 'number' could not be converted into 'never'", toString(result.errors[0]));
}
TEST_CASE_FIXTURE(Fixture, "for_loop_lower_bound_is_string_3")
{
CheckResult result = check(R"(
for i: number | string = 1, 10 do end
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_SUITE_END();

View file

@ -25,17 +25,9 @@ TEST_CASE_FIXTURE(Fixture, "or_joins_types")
local x:string|number = s
)");
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ(toString(*requireType("s")), "(string & ~(false?)) | number");
CHECK_EQ(toString(*requireType("x")), "number | string");
}
else
{
CHECK_EQ(toString(*requireType("s")), "number | string");
CHECK_EQ(toString(*requireType("x")), "number | string");
}
}
TEST_CASE_FIXTURE(Fixture, "or_joins_types_with_no_extras")
{
@ -45,17 +37,9 @@ TEST_CASE_FIXTURE(Fixture, "or_joins_types_with_no_extras")
local y = x or "s"
)");
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ(toString(*requireType("s")), "(string & ~(false?)) | number");
CHECK_EQ(toString(*requireType("y")), "((number | string) & ~(false?)) | string");
}
else
{
CHECK_EQ(toString(*requireType("s")), "number | string");
CHECK_EQ(toString(*requireType("y")), "number | string");
}
}
TEST_CASE_FIXTURE(Fixture, "or_joins_types_with_no_superfluous_union")
{
@ -78,15 +62,8 @@ TEST_CASE_FIXTURE(Fixture, "and_does_not_always_add_boolean")
local x:boolean|number = s
)");
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ(toString(*requireType("s")), "((false?) & string) | number");
}
else
{
CHECK_EQ(toString(*requireType("s")), "number");
}
}
TEST_CASE_FIXTURE(Fixture, "and_adds_boolean_no_superfluous_union")
{
@ -104,15 +81,8 @@ TEST_CASE_FIXTURE(Fixture, "and_or_ternary")
local s = (1/2) > 0.5 and "a" or 10
)");
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ(toString(*requireType("s")), "((((false?) & boolean) | string) & ~(false?)) | number");
}
else
{
CHECK_EQ(toString(*requireType("s")), "number | string");
}
}
TEST_CASE_FIXTURE(BuiltinsFixture, "primitive_arith_no_metatable")
{
@ -833,15 +803,8 @@ local b: number = 1 or a
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
REQUIRE(tm);
CHECK_EQ(typeChecker.numberType, tm->wantedType);
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ("((number & ~(false?)) | number)?", toString(tm->givenType));
}
else
{
CHECK_EQ("number?", toString(tm->givenType));
}
}
TEST_CASE_FIXTURE(Fixture, "operator_eq_verifies_types_do_intersect")
{
@ -901,15 +864,8 @@ TEST_CASE_FIXTURE(Fixture, "refine_and_or")
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ("((((false?) & ({| x: number? |}?)) | a) & ~(false?)) | number", toString(requireType("u")));
}
else
{
CHECK_EQ("number", toString(requireType("u")));
}
}
TEST_CASE_FIXTURE(Fixture, "infer_any_in_all_modes_when_lhs_is_unknown")
{
@ -1095,21 +1051,17 @@ local z = b and 1
local w = c and 1
)");
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK("((false?) & (number?)) | number" == toString(requireType("x")));
CHECK("((false?) & string) | number" == toString(requireType("y")));
CHECK("((false?) & boolean) | number" == toString(requireType("z")));
CHECK("((false?) & a) | number" == toString(requireType("w")));
}
else
{
CHECK("number?" == toString(requireType("x")));
CHECK("number" == toString(requireType("y")));
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK("false | number" == toString(requireType("z")));
else
CHECK("boolean | number" == toString(requireType("z"))); // 'false' widened to boolean
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK("((false?) & a) | number" == toString(requireType("w")));
else
CHECK("(boolean | number)?" == toString(requireType("w")));
}
}
TEST_CASE_FIXTURE(BuiltinsFixture, "reworked_or")
{
@ -1133,24 +1085,20 @@ local e1 = e or 'e'
local f1 = f or 'f'
)");
CHECK("number | string" == toString(requireType("a1")));
CHECK("number" == toString(requireType("b1")));
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK("((false | number) & ~(false?)) | string" == toString(requireType("a1")));
CHECK("((number?) & ~(false?)) | number" == toString(requireType("b1")));
CHECK("(boolean & ~(false?)) | string" == toString(requireType("c1")));
CHECK("(true & ~(false?)) | string" == toString(requireType("d1")));
CHECK("(false & ~(false?)) | string" == toString(requireType("e1")));
CHECK("(nil & ~(false?)) | string" == toString(requireType("f1")));
CHECK("string | true" == toString(requireType("c1")));
CHECK("string | true" == toString(requireType("d1")));
}
else
{
CHECK("number | string" == toString(requireType("a1")));
CHECK("number" == toString(requireType("b1")));
CHECK("boolean | string" == toString(requireType("c1"))); // 'true' widened to boolean
CHECK("boolean | string" == toString(requireType("d1"))); // 'true' widened to boolean
}
CHECK("string" == toString(requireType("e1")));
CHECK("string" == toString(requireType("f1")));
}
}
TEST_SUITE_END();

View file

@ -594,28 +594,6 @@ return wrapStrictTable(Constants, "Constants")
CHECK(get<AnyType>(*result));
}
// We need a simplification step to make this do the right thing. ("normalization-lite")
TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_with_a_singleton_argument")
{
CheckResult result = check(R"(
local function foo(t, x)
if x == "hi" or x == "bye" then
table.insert(t, x)
end
return t
end
local t = foo({}, "hi")
table.insert(t, "totally_unrelated_type" :: "totally_unrelated_type")
)");
LUAU_REQUIRE_NO_ERRORS(result);
// We'd really like for this to be {string}
CHECK_EQ("{string | string}", toString(requireType("t")));
}
namespace
{
struct IsSubtypeFixture : Fixture
@ -814,4 +792,44 @@ caused by:
}
}
TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_with_a_singleton_argument")
{
CheckResult result = check(R"(
local function foo(t, x)
if x == "hi" or x == "bye" then
table.insert(t, x)
end
return t
end
local t = foo({}, "hi")
table.insert(t, "totally_unrelated_type" :: "totally_unrelated_type")
)");
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ("{string}", toString(requireType("t")));
else
{
// We'd really like for this to be {string}
CHECK_EQ("{string | string}", toString(requireType("t")));
}
}
TEST_CASE_FIXTURE(BuiltinsFixture, "refine_unknown_to_table_then_clone_it")
{
CheckResult result = check(R"(
local function f(x: unknown)
if typeof(x) == "table" then
local cloned: {} = table.clone(x)
end
end
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
// LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_SUITE_END();

View file

@ -36,7 +36,7 @@ std::optional<WithPredicate<TypePackId>> magicFunctionInstanceIsA(
return WithPredicate<TypePackId>{booleanPack, {IsAPredicate{std::move(*lvalue), expr.location, tfun->type}}};
}
std::vector<ConnectiveId> dcrMagicRefinementInstanceIsA(const MagicRefinementContext& ctx)
std::vector<RefinementId> dcrMagicRefinementInstanceIsA(const MagicRefinementContext& ctx)
{
if (ctx.callSite->args.size != 1)
return {};
@ -54,7 +54,7 @@ std::vector<ConnectiveId> dcrMagicRefinementInstanceIsA(const MagicRefinementCon
if (!tfun)
return {};
return {ctx.connectiveArena->proposition(*def, tfun->type)};
return {ctx.refinementArena->proposition(*def, tfun->type)};
}
struct RefinementClassFixture : BuiltinsFixture
@ -122,17 +122,9 @@ TEST_CASE_FIXTURE(Fixture, "is_truthy_constraint")
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ("(string?) & ~(false?)", toString(requireTypeAtPosition({3, 26})));
CHECK_EQ("(string?) & ~~(false?)", toString(requireTypeAtPosition({5, 26})));
}
else
{
CHECK_EQ("string", toString(requireTypeAtPosition({3, 26})));
CHECK_EQ("nil", toString(requireTypeAtPosition({5, 26})));
}
}
TEST_CASE_FIXTURE(Fixture, "invert_is_truthy_constraint")
{
@ -148,17 +140,9 @@ TEST_CASE_FIXTURE(Fixture, "invert_is_truthy_constraint")
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ("(string?) & ~~(false?)", toString(requireTypeAtPosition({3, 26})));
CHECK_EQ("(string?) & ~(false?)", toString(requireTypeAtPosition({5, 26})));
}
else
{
CHECK_EQ("nil", toString(requireTypeAtPosition({3, 26})));
CHECK_EQ("string", toString(requireTypeAtPosition({5, 26})));
}
}
TEST_CASE_FIXTURE(Fixture, "parenthesized_expressions_are_followed_through")
{
@ -174,17 +158,9 @@ TEST_CASE_FIXTURE(Fixture, "parenthesized_expressions_are_followed_through")
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ("(string?) & ~~(false?)", toString(requireTypeAtPosition({3, 26})));
CHECK_EQ("(string?) & ~(false?)", toString(requireTypeAtPosition({5, 26})));
}
else
{
CHECK_EQ("nil", toString(requireTypeAtPosition({3, 26})));
CHECK_EQ("string", toString(requireTypeAtPosition({5, 26})));
}
}
TEST_CASE_FIXTURE(Fixture, "and_constraint")
{
@ -202,16 +178,8 @@ TEST_CASE_FIXTURE(Fixture, "and_constraint")
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ("(string?) & ~(false?)", toString(requireTypeAtPosition({3, 26})));
CHECK_EQ("(number?) & ~(false?)", toString(requireTypeAtPosition({4, 26})));
}
else
{
CHECK_EQ("string", toString(requireTypeAtPosition({3, 26})));
CHECK_EQ("number", toString(requireTypeAtPosition({4, 26})));
}
CHECK_EQ("string?", toString(requireTypeAtPosition({6, 26})));
CHECK_EQ("number?", toString(requireTypeAtPosition({7, 26})));
@ -236,17 +204,9 @@ TEST_CASE_FIXTURE(Fixture, "not_and_constraint")
CHECK_EQ("string?", toString(requireTypeAtPosition({3, 26})));
CHECK_EQ("number?", toString(requireTypeAtPosition({4, 26})));
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ("(string?) & ~(false?)", toString(requireTypeAtPosition({6, 26})));
CHECK_EQ("(number?) & ~(false?)", toString(requireTypeAtPosition({7, 26})));
}
else
{
CHECK_EQ("string", toString(requireTypeAtPosition({6, 26})));
CHECK_EQ("number", toString(requireTypeAtPosition({7, 26})));
}
}
TEST_CASE_FIXTURE(Fixture, "or_predicate_with_truthy_predicates")
{
@ -267,17 +227,9 @@ TEST_CASE_FIXTURE(Fixture, "or_predicate_with_truthy_predicates")
CHECK_EQ("string?", toString(requireTypeAtPosition({3, 26})));
CHECK_EQ("number?", toString(requireTypeAtPosition({4, 26})));
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ("(string?) & ~~(false?)", toString(requireTypeAtPosition({6, 26})));
CHECK_EQ("(number?) & ~~(false?)", toString(requireTypeAtPosition({7, 26})));
}
else
{
CHECK_EQ("nil", toString(requireTypeAtPosition({6, 26})));
CHECK_EQ("nil", toString(requireTypeAtPosition({7, 26})));
}
}
TEST_CASE_FIXTURE(Fixture, "a_and_b_or_a_and_c")
{
@ -297,27 +249,18 @@ TEST_CASE_FIXTURE(Fixture, "a_and_b_or_a_and_c")
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ("(string?) & (~(false?) | ~(false?))", toString(requireTypeAtPosition({3, 28})));
CHECK_EQ("number?", toString(requireTypeAtPosition({4, 28})));
CHECK_EQ("boolean", toString(requireTypeAtPosition({5, 28})));
CHECK_EQ("string?", toString(requireTypeAtPosition({7, 28})));
CHECK_EQ("number?", toString(requireTypeAtPosition({8, 28})));
CHECK_EQ("boolean", toString(requireTypeAtPosition({9, 28})));
}
else
{
CHECK_EQ("string", toString(requireTypeAtPosition({3, 28})));
CHECK_EQ("number?", toString(requireTypeAtPosition({4, 28})));
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ("boolean", toString(requireTypeAtPosition({5, 28})));
else
CHECK_EQ("true", toString(requireTypeAtPosition({5, 28}))); // oh no! :(
CHECK_EQ("string?", toString(requireTypeAtPosition({7, 28})));
CHECK_EQ("number?", toString(requireTypeAtPosition({8, 28})));
CHECK_EQ("boolean", toString(requireTypeAtPosition({9, 28})));
}
}
TEST_CASE_FIXTURE(Fixture, "type_assertion_expr_carry_its_constraints")
{
@ -357,15 +300,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_in_if_condition_position")
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ("any & number", toString(requireTypeAtPosition({3, 26})));
}
else
{
CHECK_EQ("number", toString(requireTypeAtPosition({3, 26})));
}
}
TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_in_assert_position")
{
@ -433,8 +369,8 @@ TEST_CASE_FIXTURE(Fixture, "truthy_constraint_on_properties")
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK("{| x: number? |} & {| x: ~(false?) |}" == toString(requireTypeAtPosition({4, 23})));
CHECK("(number?) & ~(false?)" == toString(requireTypeAtPosition({5, 26})));
CHECK("{| x: number |}" == toString(requireTypeAtPosition({4, 23})));
CHECK("number" == toString(requireTypeAtPosition({5, 26})));
}
CHECK_EQ("number?", toString(requireType("bar")));
@ -478,23 +414,12 @@ TEST_CASE_FIXTURE(Fixture, "lvalue_is_equal_to_another_lvalue")
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "((number | string)?) & unknown"); // a == b
CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "(boolean?) & unknown"); // a == b
CHECK_EQ(toString(requireTypeAtPosition({5, 33})), "((number | string)?) & unknown"); // a ~= b
CHECK_EQ(toString(requireTypeAtPosition({5, 36})), "(boolean?) & unknown"); // a ~= b
}
else
{
CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "(number | string)?"); // a == b
CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "boolean?"); // a == b
CHECK_EQ(toString(requireTypeAtPosition({5, 33})), "(number | string)?"); // a ~= b
CHECK_EQ(toString(requireTypeAtPosition({5, 36})), "boolean?"); // a ~= b
}
}
TEST_CASE_FIXTURE(Fixture, "lvalue_is_equal_to_a_term")
{
@ -510,17 +435,9 @@ TEST_CASE_FIXTURE(Fixture, "lvalue_is_equal_to_a_term")
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ(toString(requireTypeAtPosition({3, 28})), "((number | string)?) & unknown"); // a == 1
CHECK_EQ(toString(requireTypeAtPosition({5, 28})), "((number | string)?) & unknown"); // a ~= 1
}
else
{
CHECK_EQ(toString(requireTypeAtPosition({3, 28})), "(number | string)?"); // a == 1;
CHECK_EQ(toString(requireTypeAtPosition({5, 28})), "(number | string)?"); // a ~= 1
}
}
TEST_CASE_FIXTURE(Fixture, "term_is_equal_to_an_lvalue")
{
@ -538,8 +455,8 @@ TEST_CASE_FIXTURE(Fixture, "term_is_equal_to_an_lvalue")
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ(toString(requireTypeAtPosition({3, 28})), R"("hello" & ((number | string)?))"); // a == "hello"
CHECK_EQ(toString(requireTypeAtPosition({5, 28})), R"(((number | string)?) & ~"hello")"); // a ~= "hello"
CHECK_EQ(toString(requireTypeAtPosition({3, 28})), R"("hello")"); // a == "hello"
CHECK_EQ(toString(requireTypeAtPosition({5, 28})), R"(((string & ~"hello") | number)?)"); // a ~= "hello"
}
else
{
@ -562,17 +479,9 @@ TEST_CASE_FIXTURE(Fixture, "lvalue_is_not_nil")
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ(toString(requireTypeAtPosition({3, 28})), "((number | string)?) & ~nil"); // a ~= nil
CHECK_EQ(toString(requireTypeAtPosition({5, 28})), "((number | string)?) & unknown"); // a == nil
}
else
{
CHECK_EQ(toString(requireTypeAtPosition({3, 28})), "number | string"); // a ~= nil
CHECK_EQ(toString(requireTypeAtPosition({5, 28})), "(number | string)?"); // a == nil
}
}
TEST_CASE_FIXTURE(Fixture, "free_type_is_equal_to_an_lvalue")
{
@ -586,18 +495,9 @@ TEST_CASE_FIXTURE(Fixture, "free_type_is_equal_to_an_lvalue")
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
{
ToStringOptions opts;
CHECK_EQ(toString(requireTypeAtPosition({3, 33}), opts), "a & unknown"); // a == b
CHECK_EQ(toString(requireTypeAtPosition({3, 36}), opts), "(string?) & unknown"); // a == b
}
else
{
CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "a"); // a == b
CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "string?"); // a == b
}
}
TEST_CASE_FIXTURE(Fixture, "unknown_lvalue_is_not_synonymous_with_other_on_not_equal")
{
@ -611,17 +511,9 @@ TEST_CASE_FIXTURE(Fixture, "unknown_lvalue_is_not_synonymous_with_other_on_not_e
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "any & unknown"); // a ~= b
CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "({| x: number |}?) & unknown"); // a ~= b
}
else
{
CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "any"); // a ~= b
CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "{| x: number |}?"); // a ~= b
}
}
TEST_CASE_FIXTURE(Fixture, "string_not_equal_to_string_or_nil")
{
@ -639,23 +531,12 @@ TEST_CASE_FIXTURE(Fixture, "string_not_equal_to_string_or_nil")
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ(toString(requireTypeAtPosition({6, 29})), "string & unknown"); // a ~= b
CHECK_EQ(toString(requireTypeAtPosition({6, 32})), "(string?) & unknown"); // a ~= b
CHECK_EQ(toString(requireTypeAtPosition({8, 29})), "string & unknown"); // a == b
CHECK_EQ(toString(requireTypeAtPosition({8, 32})), "(string?) & unknown"); // a == b
}
else
{
CHECK_EQ(toString(requireTypeAtPosition({6, 29})), "string"); // a ~= b
CHECK_EQ(toString(requireTypeAtPosition({6, 32})), "string?"); // a ~= b
CHECK_EQ(toString(requireTypeAtPosition({8, 29})), "string"); // a == b
CHECK_EQ(toString(requireTypeAtPosition({8, 32})), "string?"); // a == b
}
}
TEST_CASE_FIXTURE(Fixture, "narrow_property_of_a_bounded_variable")
{
@ -729,17 +610,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_not_to_be_string")
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ("(boolean | number | string) & ~string", toString(requireTypeAtPosition({3, 28}))); // type(x) ~= "string"
CHECK_EQ("(boolean | number | string) & string", toString(requireTypeAtPosition({5, 28}))); // type(x) == "string"
}
else
{
CHECK_EQ("boolean | number", toString(requireTypeAtPosition({3, 28}))); // type(x) ~= "string"
CHECK_EQ("string", toString(requireTypeAtPosition({5, 28}))); // type(x) == "string"
}
}
TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_narrows_for_table")
{
@ -773,17 +646,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_narrows_for_functions")
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ("(((number) -> string) | string) & function", toString(requireTypeAtPosition({3, 28}))); // type(x) == "function"
CHECK_EQ("(((number) -> string) | string) & ~function", toString(requireTypeAtPosition({5, 28}))); // type(x) ~= "function"
}
else
{
CHECK_EQ("(number) -> string", toString(requireTypeAtPosition({3, 28}))); // type(x) == "function"
CHECK_EQ("string", toString(requireTypeAtPosition({5, 28}))); // type(x) ~= "function"
}
}
TEST_CASE_FIXTURE(BuiltinsFixture, "type_guard_can_filter_for_intersection_of_tables")
{
@ -821,17 +686,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_guard_can_filter_for_overloaded_functio
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ("((((number) -> string) & ((string) -> number))?) & function", toString(requireTypeAtPosition({4, 28})));
CHECK_EQ("((((number) -> string) & ((string) -> number))?) & ~function", toString(requireTypeAtPosition({6, 28})));
}
else
{
CHECK_EQ("((number) -> string) & ((string) -> number)", toString(requireTypeAtPosition({4, 28})));
CHECK_EQ("nil", toString(requireTypeAtPosition({6, 28})));
}
}
TEST_CASE_FIXTURE(BuiltinsFixture, "type_guard_narrowed_into_nothingness")
{
@ -898,17 +755,9 @@ TEST_CASE_FIXTURE(Fixture, "not_a_and_not_b")
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ("(number?) & ~~(false?)", toString(requireTypeAtPosition({3, 28})));
CHECK_EQ("(number?) & ~~(false?)", toString(requireTypeAtPosition({4, 28})));
}
else
{
CHECK_EQ("nil", toString(requireTypeAtPosition({3, 28})));
CHECK_EQ("nil", toString(requireTypeAtPosition({4, 28})));
}
}
TEST_CASE_FIXTURE(Fixture, "not_a_and_not_b2")
{
@ -923,17 +772,9 @@ TEST_CASE_FIXTURE(Fixture, "not_a_and_not_b2")
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ("(number?) & ~~(false?)", toString(requireTypeAtPosition({3, 28})));
CHECK_EQ("(number?) & ~~(false?)", toString(requireTypeAtPosition({4, 28})));
}
else
{
CHECK_EQ("nil", toString(requireTypeAtPosition({3, 28})));
CHECK_EQ("nil", toString(requireTypeAtPosition({4, 28})));
}
}
TEST_CASE_FIXTURE(BuiltinsFixture, "either_number_or_string")
{
@ -947,15 +788,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "either_number_or_string")
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ("(number | string) & any", toString(requireTypeAtPosition({3, 28})));
}
else
{
CHECK_EQ("number | string", toString(requireTypeAtPosition({3, 28})));
}
}
TEST_CASE_FIXTURE(Fixture, "not_t_or_some_prop_of_t")
{
@ -984,17 +818,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "assert_a_to_be_truthy_then_assert_a_to_be_nu
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ("((number | string)?) & ~(false?)", toString(requireTypeAtPosition({3, 18})));
CHECK_EQ("((number | string)?) & ~(false?) & number", toString(requireTypeAtPosition({5, 18})));
}
else
{
CHECK_EQ("number | string", toString(requireTypeAtPosition({3, 18})));
CHECK_EQ("number", toString(requireTypeAtPosition({5, 18})));
}
}
TEST_CASE_FIXTURE(BuiltinsFixture, "merge_should_be_fully_agnostic_of_hashmap_ordering")
{
@ -1012,15 +838,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "merge_should_be_fully_agnostic_of_hashmap_or
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ("(string | table) & (string | {| x: string |}) & string", toString(requireTypeAtPosition({6, 28})));
}
else
{
CHECK_EQ("string", toString(requireTypeAtPosition({6, 28})));
}
}
TEST_CASE_FIXTURE(BuiltinsFixture, "refine_the_correct_types_opposite_of_when_a_is_not_number_or_string")
{
@ -1036,17 +855,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "refine_the_correct_types_opposite_of_when_a_
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ("(boolean | number | string) & ~number & ~string", toString(requireTypeAtPosition({3, 28})));
CHECK_EQ("(boolean | number | string) & (number | string)", toString(requireTypeAtPosition({5, 28})));
}
else
{
CHECK_EQ("boolean", toString(requireTypeAtPosition({3, 28})));
CHECK_EQ("number | string", toString(requireTypeAtPosition({5, 28})));
}
}
TEST_CASE_FIXTURE(BuiltinsFixture, "is_truthy_constraint_ifelse_expression")
{
@ -1058,17 +869,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "is_truthy_constraint_ifelse_expression")
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ("(string?) & ~(false?)", toString(requireTypeAtPosition({2, 29})));
CHECK_EQ("(string?) & ~~(false?)", toString(requireTypeAtPosition({2, 45})));
}
else
{
CHECK_EQ("string", toString(requireTypeAtPosition({2, 29})));
CHECK_EQ("nil", toString(requireTypeAtPosition({2, 45})));
}
}
TEST_CASE_FIXTURE(BuiltinsFixture, "invert_is_truthy_constraint_ifelse_expression")
{
@ -1080,17 +883,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "invert_is_truthy_constraint_ifelse_expressio
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ("(string?) & ~~(false?)", toString(requireTypeAtPosition({2, 42})));
CHECK_EQ("(string?) & ~(false?)", toString(requireTypeAtPosition({2, 50})));
}
else
{
CHECK_EQ("nil", toString(requireTypeAtPosition({2, 42})));
CHECK_EQ("string", toString(requireTypeAtPosition({2, 50})));
}
}
TEST_CASE_FIXTURE(BuiltinsFixture, "type_comparison_ifelse_expression")
{
@ -1106,17 +901,12 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_comparison_ifelse_expression")
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ("any & number", toString(requireTypeAtPosition({6, 49})));
CHECK_EQ("any & ~number", toString(requireTypeAtPosition({6, 66})));
}
else
{
CHECK_EQ("number", toString(requireTypeAtPosition({6, 49})));
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ("~number", toString(requireTypeAtPosition({6, 66})));
else
CHECK_EQ("any", toString(requireTypeAtPosition({6, 66})));
}
}
TEST_CASE_FIXTURE(BuiltinsFixture, "correctly_lookup_a_shadowed_local_that_which_was_previously_refined")
{
@ -1196,18 +986,12 @@ TEST_CASE_FIXTURE(Fixture, "discriminate_from_truthiness_of_x")
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ(R"(({| tag: "exists", x: string |} | {| tag: "missing", x: nil |}) & {| x: ~(false?) |})", toString(requireTypeAtPosition({5, 28})));
CHECK_EQ(
R"(({| tag: "exists", x: string |} | {| tag: "missing", x: nil |}) & {| x: ~~(false?) |})", toString(requireTypeAtPosition({7, 28})));
}
else
{
CHECK_EQ(R"({| tag: "exists", x: string |})", toString(requireTypeAtPosition({5, 28})));
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ(R"({| tag: "missing", x: nil |})", toString(requireTypeAtPosition({7, 28})));
else
CHECK_EQ(R"({| tag: "exists", x: string |} | {| tag: "missing", x: nil |})", toString(requireTypeAtPosition({7, 28})));
}
}
TEST_CASE_FIXTURE(Fixture, "discriminate_tag")
{
@ -1229,8 +1013,8 @@ TEST_CASE_FIXTURE(Fixture, "discriminate_tag")
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ(R"((Cat | Dog) & {| tag: "Cat" |})", toString(requireTypeAtPosition({7, 33})));
CHECK_EQ(R"((Cat | Dog) & {| tag: ~"Cat" |} & {| tag: "Dog" |})", toString(requireTypeAtPosition({9, 33})));
CHECK_EQ(R"({| catfood: string, name: string, tag: "Cat" |})", toString(requireTypeAtPosition({7, 33})));
CHECK_EQ(R"({| dogfood: string, name: string, tag: "Dog" |})", toString(requireTypeAtPosition({9, 33})));
}
else
{
@ -1259,8 +1043,8 @@ TEST_CASE_FIXTURE(Fixture, "discriminate_tag_with_implicit_else")
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ(R"((Cat | Dog) & {| tag: "Cat" |})", toString(requireTypeAtPosition({7, 33})));
CHECK_EQ(R"((Cat | Dog) & {| tag: ~"Cat" |})", toString(requireTypeAtPosition({9, 33})));
CHECK_EQ(R"({| catfood: string, name: string, tag: "Cat" |})", toString(requireTypeAtPosition({7, 33})));
CHECK_EQ(R"({| dogfood: string, name: string, tag: "Dog" |})", toString(requireTypeAtPosition({9, 33})));
}
else
{
@ -1294,17 +1078,9 @@ TEST_CASE_FIXTURE(Fixture, "narrow_boolean_to_true_or_false")
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ("boolean & ~(false?)", toString(requireTypeAtPosition({3, 28})));
CHECK_EQ("boolean & ~~(false?)", toString(requireTypeAtPosition({5, 28})));
}
else
{
CHECK_EQ("true", toString(requireTypeAtPosition({3, 28})));
CHECK_EQ("false", toString(requireTypeAtPosition({5, 28})));
}
}
TEST_CASE_FIXTURE(Fixture, "discriminate_on_properties_of_disjoint_tables_where_that_property_is_true_or_false")
{
@ -1355,17 +1131,9 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "discriminate_from_isa_of_x")
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ(R"(({| tag: "Folder", x: Folder |} | {| tag: "Part", x: Part |}) & {| x: Part |})", toString(requireTypeAtPosition({5, 28})));
CHECK_EQ(R"(({| tag: "Folder", x: Folder |} | {| tag: "Part", x: Part |}) & {| x: ~Part |})", 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")
{
@ -1406,17 +1174,9 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "typeguard_cast_instance_or_vector3_to
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ("(Instance | Vector3) & Vector3", toString(requireTypeAtPosition({3, 28})));
CHECK_EQ("(Instance | Vector3) & ~Vector3", toString(requireTypeAtPosition({5, 28})));
}
else
{
CHECK_EQ("Vector3", toString(requireTypeAtPosition({3, 28})));
CHECK_EQ("Instance", toString(requireTypeAtPosition({5, 28})));
}
}
TEST_CASE_FIXTURE(RefinementClassFixture, "type_narrow_for_all_the_userdata")
{
@ -1452,8 +1212,8 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "type_narrow_but_the_discriminant_type
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ("(Instance | Vector3 | number | string) & never", toString(requireTypeAtPosition({3, 28})));
CHECK_EQ("(Instance | Vector3 | number | string) & ~never", toString(requireTypeAtPosition({5, 28})));
CHECK_EQ("never", toString(requireTypeAtPosition({3, 28})));
CHECK_EQ("Instance | Vector3 | number | string", toString(requireTypeAtPosition({5, 28})));
}
else
{
@ -1476,17 +1236,9 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "eliminate_subclasses_of_instance")
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ("(Folder | Part | string) & Instance", toString(requireTypeAtPosition({3, 28})));
CHECK_EQ("(Folder | Part | string) & ~Instance", toString(requireTypeAtPosition({5, 28})));
}
else
{
CHECK_EQ("Folder | Part", toString(requireTypeAtPosition({3, 28})));
CHECK_EQ("string", toString(requireTypeAtPosition({5, 28})));
}
}
TEST_CASE_FIXTURE(RefinementClassFixture, "narrow_from_subclasses_of_instance_or_string_or_vector3")
{
@ -1502,17 +1254,9 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "narrow_from_subclasses_of_instance_or
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ("(Folder | Part | Vector3 | string) & Instance", toString(requireTypeAtPosition({3, 28})));
CHECK_EQ("(Folder | Part | Vector3 | string) & ~Instance", toString(requireTypeAtPosition({5, 28})));
}
else
{
CHECK_EQ("Folder | Part", toString(requireTypeAtPosition({3, 28})));
CHECK_EQ("Vector3 | string", toString(requireTypeAtPosition({5, 28})));
}
}
TEST_CASE_FIXTURE(RefinementClassFixture, "x_as_any_if_x_is_instance_elseif_x_is_table")
{
@ -1556,17 +1300,9 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "refine_param_of_type_instance_without
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ("Folder & Instance", toString(requireTypeAtPosition({3, 28})));
CHECK_EQ("Instance & ~Folder & table", toString(requireTypeAtPosition({5, 28})));
}
else
{
CHECK_EQ("Folder", toString(requireTypeAtPosition({3, 28})));
CHECK_EQ("never", toString(requireTypeAtPosition({5, 28})));
}
}
TEST_CASE_FIXTURE(RefinementClassFixture, "refine_param_of_type_folder_or_part_without_using_typeof")
{
@ -1582,17 +1318,9 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "refine_param_of_type_folder_or_part_w
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ("(Folder | Part) & Folder", toString(requireTypeAtPosition({3, 28})));
CHECK_EQ("(Folder | Part) & ~Folder", toString(requireTypeAtPosition({5, 28})));
}
else
{
CHECK_EQ("Folder", toString(requireTypeAtPosition({3, 28})));
CHECK_EQ("Part", toString(requireTypeAtPosition({5, 28})));
}
}
TEST_CASE_FIXTURE(RefinementClassFixture, "isa_type_refinement_must_be_known_ahead_of_time")
{
@ -1610,17 +1338,9 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "isa_type_refinement_must_be_known_ahe
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ("Instance", toString(requireTypeAtPosition({3, 28})));
CHECK_EQ("Instance", toString(requireTypeAtPosition({5, 28})));
}
else
{
CHECK_EQ("Instance", toString(requireTypeAtPosition({3, 28})));
CHECK_EQ("Instance", toString(requireTypeAtPosition({5, 28})));
}
}
TEST_CASE_FIXTURE(RefinementClassFixture, "x_is_not_instance_or_else_not_part")
{
@ -1673,8 +1393,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "refine_unknowns")
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ("unknown & string", toString(requireTypeAtPosition({3, 28})));
CHECK_EQ("unknown & ~string", toString(requireTypeAtPosition({5, 28})));
CHECK_EQ("string", toString(requireTypeAtPosition({3, 28})));
CHECK_EQ("~string", toString(requireTypeAtPosition({5, 28})));
}
else
{
@ -1714,15 +1434,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "what_nonsensical_condition")
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ("a & number & string", toString(requireTypeAtPosition({3, 28})));
}
else
{
CHECK_EQ("never", toString(requireTypeAtPosition({3, 28})));
}
}
TEST_CASE_FIXTURE(Fixture, "else_with_no_explicit_expression_should_also_refine_the_tagged_union")
{
@ -1752,7 +1465,30 @@ local _ = _ ~= _ or _ or _
end
)");
if (FFlag::DebugLuauDeferredConstraintResolution)
{
// Without a realistic motivating case, it's hard to tell if it's important for this to work without errors.
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK(get<NormalizationTooComplex>(result.errors[0]));
}
else
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "refine_unknown_to_table_then_take_the_length")
{
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
CheckResult result = check(R"(
local function f(x: unknown)
if typeof(x) == "table" then
local len = #x
end
end
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ("table", toString(requireTypeAtPosition({3, 29})));
}
TEST_SUITE_END();

View file

@ -3435,4 +3435,62 @@ _ = _._
LUAU_REQUIRE_ERRORS(result);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_table_unify_instantiated_table")
{
ScopedFastFlag sff[]{
{"LuauInstantiateInSubtyping", true},
{"LuauScalarShapeUnifyToMtOwner2", true},
{"LuauTableUnifyInstantiationFix", true},
};
CheckResult result = check(R"(
function _(...)
end
local function l0():typeof(_()()[_()()[_]])
end
return _[_()()[_]] <= _
)");
LUAU_REQUIRE_ERRORS(result);
}
TEST_CASE_FIXTURE(Fixture, "fuzz_table_unify_instantiated_table_with_prop_realloc")
{
ScopedFastFlag sff[]{
{"LuauInstantiateInSubtyping", true},
{"LuauScalarShapeUnifyToMtOwner2", true},
{"LuauTableUnifyInstantiationFix", true},
};
CheckResult result = check(R"(
function _(l0,l0)
do
_ = _().n0
end
l0(_()._,_)
end
_(_,function(...)
end)
)");
LUAU_REQUIRE_ERRORS(result);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_table_unify_prop_realloc")
{
// For this test, we don't need LuauInstantiateInSubtyping
ScopedFastFlag sff[]{
{"LuauScalarShapeUnifyToMtOwner2", true},
{"LuauTableUnifyInstantiationFix", true},
};
CheckResult result = check(R"(
n3,_ = nil
_ = _[""]._,_[l0][_._][{[_]=_,_=_,}][_G].number
_ = {_,}
)");
LUAU_REQUIRE_ERRORS(result);
}
TEST_SUITE_END();

View file

@ -116,12 +116,24 @@ TEST_CASE_FIXTURE(Fixture, "type_packs_containing_never_is_itself_uninhabitable"
local x, y, z = f()
)");
if (FFlag::DebugLuauDeferredConstraintResolution)
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ("Function only returns 2 values, but 3 are required here", toString(result.errors[0]));
CHECK_EQ("string", toString(requireType("x")));
CHECK_EQ("never", toString(requireType("y")));
CHECK_EQ("*error-type*", toString(requireType("z")));
}
else
{
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ("never", toString(requireType("x")));
CHECK_EQ("never", toString(requireType("y")));
CHECK_EQ("never", toString(requireType("z")));
}
}
TEST_CASE_FIXTURE(Fixture, "type_packs_containing_never_is_itself_uninhabitable2")
{
@ -135,11 +147,21 @@ TEST_CASE_FIXTURE(Fixture, "type_packs_containing_never_is_itself_uninhabitable2
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ("string", toString(requireType("x1")));
CHECK_EQ("never", toString(requireType("x2")));
CHECK_EQ("never", toString(requireType("y1")));
CHECK_EQ("string", toString(requireType("y2")));
}
else
{
CHECK_EQ("never", toString(requireType("x1")));
CHECK_EQ("never", toString(requireType("x2")));
CHECK_EQ("never", toString(requireType("y1")));
CHECK_EQ("never", toString(requireType("y2")));
}
}
TEST_CASE_FIXTURE(Fixture, "index_on_never")
{
@ -290,9 +312,15 @@ TEST_CASE_FIXTURE(Fixture, "dont_unify_operands_if_one_of_the_operand_is_never_i
)");
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ("<a>(nil, a) -> boolean", toString(requireType("ord")));
else
{
// Widening doesn't normalize yet, so the result is a bit strange
CHECK_EQ("<a>(nil, a) -> boolean | boolean", toString(requireType("ord")));
}
}
TEST_CASE_FIXTURE(Fixture, "math_operators_and_never")
{

View file

@ -482,6 +482,24 @@ TEST_CASE_FIXTURE(ReductionFixture, "intersections_without_negations")
CHECK("{| [string]: number, p: string |}" == toStringFull(ty));
}
SUBCASE("array_number_and_array_string")
{
TypeId ty = reductionof("{number} & {string}");
CHECK("{never}" == toStringFull(ty));
}
SUBCASE("array_string_and_array_string")
{
TypeId ty = reductionof("{string} & {string}");
CHECK("{string}" == toStringFull(ty));
}
SUBCASE("array_string_or_number_and_array_string")
{
TypeId ty = reductionof("{string | number} & {string}");
CHECK("{string}" == toStringFull(ty));
}
SUBCASE("fresh_type_and_string")
{
TypeId freshTy = arena.freshType(nullptr);
@ -690,7 +708,7 @@ TEST_CASE_FIXTURE(ReductionFixture, "intersections_with_negations")
SUBCASE("string_and_not_error")
{
TypeId ty = reductionof("string & Not<err>");
CHECK("string & ~*error-type*" == toStringFull(ty));
CHECK("string" == toStringFull(ty));
}
SUBCASE("table_p_string_and_table_p_not_number")
@ -711,6 +729,12 @@ TEST_CASE_FIXTURE(ReductionFixture, "intersections_with_negations")
CHECK("{| x: {| p: string |} |}" == toStringFull(ty));
}
SUBCASE("table_or_nil_and_truthy")
{
TypeId ty = reductionof("({ x: number | string }?) & Not<false?>");
CHECK("{| x: number | string |}" == toString(ty));
}
SUBCASE("not_top_table_and_table")
{
TypeId ty = reductionof("Not<tbl> & {}");
@ -1251,6 +1275,12 @@ TEST_CASE_FIXTURE(ReductionFixture, "tables")
TypeId ty = reductionof("{ x: { y: string & number } }");
CHECK("never" == toStringFull(ty));
}
SUBCASE("array_of_never")
{
TypeId ty = reductionof("{never}");
CHECK("{never}" == toStringFull(ty));
}
}
TEST_CASE_FIXTURE(ReductionFixture, "metatables")

View file

@ -1,6 +1,5 @@
AnnotationTests.corecursive_types_error_on_tight_loop
AnnotationTests.duplicate_type_param_name
AnnotationTests.for_loop_counter_annotation_is_checked
AnnotationTests.generic_aliases_are_cloned_properly
AnnotationTests.occurs_check_on_cyclic_intersection_type
AnnotationTests.occurs_check_on_cyclic_union_type
@ -18,12 +17,8 @@ AutocompleteTest.keyword_methods
AutocompleteTest.no_incompatible_self_calls
AutocompleteTest.no_wrong_compatible_self_calls_with_generics
AutocompleteTest.string_singleton_as_table_key
AutocompleteTest.suggest_external_module_type
AutocompleteTest.suggest_table_keys
AutocompleteTest.type_correct_argument_type_suggestion
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
@ -118,37 +113,28 @@ ParserTests.parse_nesting_based_end_detection_failsafe_earlier
ParserTests.parse_nesting_based_end_detection_local_function
ProvisionalTests.assign_table_with_refined_property_with_a_similar_type_is_illegal
ProvisionalTests.bail_early_if_unification_is_too_complicated
ProvisionalTests.discriminate_from_x_not_equal_to_nil
ProvisionalTests.do_not_ice_when_trying_to_pick_first_of_generic_type_pack
ProvisionalTests.error_on_eq_metamethod_returning_a_type_other_than_boolean
ProvisionalTests.free_options_cannot_be_unified_together
ProvisionalTests.generic_type_leak_to_module_interface_variadic
ProvisionalTests.greedy_inference_with_shared_self_triggers_function_with_no_returns
ProvisionalTests.lvalue_equals_another_lvalue_with_no_overlap
ProvisionalTests.pcall_returns_at_least_two_value_but_function_returns_nothing
ProvisionalTests.refine_unknown_to_table_then_clone_it
ProvisionalTests.setmetatable_constrains_free_type_into_free_table
ProvisionalTests.specialization_binds_with_prototypes_too_early
ProvisionalTests.table_insert_with_a_singleton_argument
ProvisionalTests.typeguard_inference_incomplete
ProvisionalTests.weirditer_should_not_loop_forever
RefinementTest.apply_refinements_on_astexprindexexpr_whose_subscript_expr_is_constant_string
RefinementTest.call_an_incompatible_function_after_using_typeguard
RefinementTest.correctly_lookup_property_whose_base_was_previously_refined2
RefinementTest.discriminate_on_properties_of_disjoint_tables_where_that_property_is_true_or_false
RefinementTest.discriminate_tag
RefinementTest.else_with_no_explicit_expression_should_also_refine_the_tagged_union
RefinementTest.falsiness_of_TruthyPredicate_narrows_into_nil
RefinementTest.narrow_property_of_a_bounded_variable
RefinementTest.nonoptional_type_can_narrow_to_nil_if_sense_is_true
RefinementTest.refine_a_property_not_to_be_nil_through_an_intersection_table
RefinementTest.refine_unknowns
RefinementTest.type_guard_can_filter_for_intersection_of_tables
RefinementTest.type_guard_narrowed_into_nothingness
RefinementTest.type_narrow_for_all_the_userdata
RefinementTest.type_narrow_to_vector
RefinementTest.typeguard_cast_free_table_to_vector
RefinementTest.typeguard_in_assert_position
RefinementTest.typeguard_narrows_for_table
RefinementTest.x_as_any_if_x_is_instance_elseif_x_is_table
RefinementTest.x_is_not_instance_or_else_not_part
RuntimeLimits.typescript_port_of_Result_type
@ -178,6 +164,8 @@ TableTests.found_like_key_in_table_function_call
TableTests.found_like_key_in_table_property_access
TableTests.found_multiple_like_keys
TableTests.function_calls_produces_sealed_table_given_unsealed_table
TableTests.fuzz_table_unify_instantiated_table
TableTests.fuzz_table_unify_instantiated_table_with_prop_realloc
TableTests.generic_table_instantiation_potential_regression
TableTests.give_up_after_one_metatable_index_look_up
TableTests.indexer_on_sealed_table_must_unify_with_free_table
@ -220,9 +208,9 @@ TableTests.table_param_row_polymorphism_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
TableTests.table_unification_4
TableTests.tc_member_function
TableTests.tc_member_function_2
TableTests.unification_of_unions_in_a_self_referential_type
TableTests.unifying_tables_shouldnt_uaf1
TableTests.unifying_tables_shouldnt_uaf2
TableTests.used_colon_correctly
@ -357,9 +345,7 @@ TypeInferOperators.disallow_string_and_types_without_metatables_from_arithmetic_
TypeInferOperators.in_nonstrict_mode_strip_nil_from_intersections_when_considering_relational_operators
TypeInferOperators.infer_any_in_all_modes_when_lhs_is_unknown
TypeInferOperators.operator_eq_completely_incompatible
TypeInferOperators.or_joins_types_with_no_superfluous_union
TypeInferOperators.produce_the_correct_error_message_when_comparing_a_table_with_a_metatable_with_one_that_does_not
TypeInferOperators.refine_and_or
TypeInferOperators.typecheck_overloaded_multiply_that_is_an_intersection
TypeInferOperators.typecheck_overloaded_multiply_that_is_an_intersection_on_rhs
TypeInferOperators.UnknownGlobalCompoundAssign
@ -368,16 +354,8 @@ TypeInferOperators.unrelated_primitives_cannot_be_compared
TypeInferPrimitives.CheckMethodsOfNumber
TypeInferPrimitives.string_index
TypeInferUnknownNever.assign_to_global_which_is_never
TypeInferUnknownNever.assign_to_local_which_is_never
TypeInferUnknownNever.assign_to_prop_which_is_never
TypeInferUnknownNever.assign_to_subscript_which_is_never
TypeInferUnknownNever.call_never
TypeInferUnknownNever.dont_unify_operands_if_one_of_the_operand_is_never_in_any_ordering_operators
TypeInferUnknownNever.index_on_union_of_tables_for_properties_that_is_sorta_never
TypeInferUnknownNever.math_operators_and_never
TypeInferUnknownNever.type_packs_containing_never_is_itself_uninhabitable
TypeInferUnknownNever.type_packs_containing_never_is_itself_uninhabitable2
TypeInferUnknownNever.unary_minus_of_never
TypePackTests.detect_cyclic_typepacks2
TypePackTests.pack_tail_unification_check
TypePackTests.self_and_varargs_should_work
@ -401,24 +379,19 @@ TypePackTests.type_pack_type_parameters
TypePackTests.unify_variadic_tails_in_arguments
TypePackTests.unify_variadic_tails_in_arguments_free
TypePackTests.variadic_packs
TypeReductionTests.negations
TypeSingletons.function_call_with_singletons
TypeSingletons.function_call_with_singletons_mismatch
TypeSingletons.indexing_on_string_singletons
TypeSingletons.indexing_on_union_of_string_singletons
TypeSingletons.overloaded_function_call_with_singletons
TypeSingletons.overloaded_function_call_with_singletons_mismatch
TypeSingletons.return_type_of_f_is_not_widened
TypeSingletons.table_properties_singleton_strings_mismatch
TypeSingletons.table_properties_type_error_escapes
TypeSingletons.taking_the_length_of_string_singleton
TypeSingletons.taking_the_length_of_union_of_string_singleton
TypeSingletons.widen_the_supertype_if_it_is_free_and_subtype_has_singleton
TypeSingletons.widening_happens_almost_everywhere
TypeSingletons.widening_happens_almost_everywhere_except_for_tables
UnionTypes.index_on_a_union_type_with_missing_property
UnionTypes.index_on_a_union_type_with_one_optional_property
UnionTypes.index_on_a_union_type_with_one_property_of_type_any
UnionTypes.optional_assignment_errors
UnionTypes.optional_call_error
UnionTypes.optional_field_access_error