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 // When we dispatch this constraint, we update the key at this map to record
// the overload that we selected. // the overload that we selected.
const void* astFragment; const AstNode* astFragment;
DenseHashMap<const void*, TypeId>* astOriginalCallTypes; DenseHashMap<const AstNode*, TypeId>* astOriginalCallTypes;
DenseHashMap<const void*, TypeId>* astOverloadResolvedTypes; DenseHashMap<const AstNode*, TypeId>* astOverloadResolvedTypes;
}; };
// iteratee is iterable // iteratee is iterable

View file

@ -2,7 +2,7 @@
#pragma once #pragma once
#include "Luau/Ast.h" #include "Luau/Ast.h"
#include "Luau/Connective.h" #include "Luau/Refinement.h"
#include "Luau/Constraint.h" #include "Luau/Constraint.h"
#include "Luau/DataFlowGraph.h" #include "Luau/DataFlowGraph.h"
#include "Luau/Module.h" #include "Luau/Module.h"
@ -27,13 +27,13 @@ struct DcrLogger;
struct Inference struct Inference
{ {
TypeId ty = nullptr; TypeId ty = nullptr;
ConnectiveId connective = nullptr; RefinementId refinement = nullptr;
Inference() = default; Inference() = default;
explicit Inference(TypeId ty, ConnectiveId connective = nullptr) explicit Inference(TypeId ty, RefinementId refinement = nullptr)
: ty(ty) : ty(ty)
, connective(connective) , refinement(refinement)
{ {
} }
}; };
@ -41,13 +41,13 @@ struct Inference
struct InferencePack struct InferencePack
{ {
TypePackId tp = nullptr; TypePackId tp = nullptr;
std::vector<ConnectiveId> connectives; std::vector<RefinementId> refinements;
InferencePack() = default; InferencePack() = default;
explicit InferencePack(TypePackId tp, const std::vector<ConnectiveId>& connectives = {}) explicit InferencePack(TypePackId tp, const std::vector<RefinementId>& refinements = {})
: tp(tp) : tp(tp)
, connectives(connectives) , refinements(refinements)
{ {
} }
}; };
@ -74,35 +74,11 @@ struct ConstraintGraphBuilder
// will enqueue them during solving. // will enqueue them during solving.
std::vector<ConstraintPtr> unqueuedConstraints; std::vector<ConstraintPtr> unqueuedConstraints;
// A mapping of AST node to TypeId. // The private scope of type aliases for which the type parameters belong to.
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.
DenseHashMap<const AstStatTypeAlias*, ScopePtr> astTypeAliasDefiningScopes{nullptr}; DenseHashMap<const AstStatTypeAlias*, ScopePtr> astTypeAliasDefiningScopes{nullptr};
NotNull<const DataFlowGraph> dfg; NotNull<const DataFlowGraph> dfg;
ConnectiveArena connectiveArena; RefinementArena refinementArena;
int recursionCount = 0; int recursionCount = 0;
@ -156,7 +132,7 @@ struct ConstraintGraphBuilder
*/ */
NotNull<Constraint> addConstraint(const ScopePtr& scope, std::unique_ptr<Constraint> c); 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 * 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, AstExprTypeAssertion* typeAssert);
Inference check(const ScopePtr& scope, AstExprInterpString* interpString); Inference check(const ScopePtr& scope, AstExprInterpString* interpString);
Inference check(const ScopePtr& scope, AstExprTable* expr, std::optional<TypeId> expectedType); 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); 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*, TypePackId> astTypePacks{nullptr};
DenseHashMap<const AstExpr*, TypeId> astExpectedTypes{nullptr}; DenseHashMap<const AstExpr*, TypeId> astExpectedTypes{nullptr};
// Pointers are either AstExpr or AstStat. DenseHashMap<const AstNode*, TypeId> astOriginalCallTypes{nullptr};
DenseHashMap<const void*, TypeId> astOriginalCallTypes{nullptr}; DenseHashMap<const AstNode*, TypeId> astOverloadResolvedTypes{nullptr};
// Pointers are either AstExpr or AstStat.
DenseHashMap<const void*, TypeId> astOverloadResolvedTypes{nullptr};
DenseHashMap<const AstType*, TypeId> astResolvedTypes{nullptr}; DenseHashMap<const AstType*, TypeId> astResolvedTypes{nullptr};
DenseHashMap<const AstType*, TypeId> astOriginalResolvedTypes{nullptr};
DenseHashMap<const AstTypePack*, TypePackId> astResolvedTypePacks{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. // 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}; 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/Ast.h"
#include "Luau/Common.h" #include "Luau/Common.h"
#include "Luau/Connective.h" #include "Luau/Refinement.h"
#include "Luau/DataFlowGraph.h" #include "Luau/DataFlowGraph.h"
#include "Luau/DenseHash.h" #include "Luau/DenseHash.h"
#include "Luau/Def.h" #include "Luau/Def.h"
@ -266,12 +266,12 @@ struct MagicRefinementContext
ScopePtr scope; ScopePtr scope;
NotNull<struct ConstraintGraphBuilder> cgb; NotNull<struct ConstraintGraphBuilder> cgb;
NotNull<const DataFlowGraph> dfg; NotNull<const DataFlowGraph> dfg;
NotNull<ConnectiveArena> connectiveArena; NotNull<RefinementArena> refinementArena;
std::vector<ConnectiveId> argumentConnectives; std::vector<RefinementId> argumentRefinements;
const class AstExprCall* callSite; const class AstExprCall* callSite;
}; };
using DcrMagicRefinement = std::vector<ConnectiveId> (*)(const MagicRefinementContext&); using DcrMagicRefinement = std::vector<RefinementId> (*)(const MagicRefinementContext&);
struct FunctionType struct FunctionType
{ {

View file

@ -32,11 +32,23 @@ struct TypeReduction
explicit TypeReduction( explicit TypeReduction(
NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, NotNull<InternalErrorReporter> handle, const TypeReductionOptions& opts = {}); 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<TypeId> reduce(TypeId ty);
std::optional<TypePackId> reduce(TypePackId tp); std::optional<TypePackId> reduce(TypePackId tp);
std::optional<TypeFun> reduce(const TypeFun& fun); 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: private:
const TypeReduction* parent = nullptr;
NotNull<TypeArena> arena; NotNull<TypeArena> arena;
NotNull<BuiltinTypes> builtinTypes; NotNull<BuiltinTypes> builtinTypes;
NotNull<struct InternalErrorReporter> handle; NotNull<struct InternalErrorReporter> handle;
@ -50,6 +62,9 @@ private:
bool hasExceededCartesianProductLimit(TypeId ty) const; bool hasExceededCartesianProductLimit(TypeId ty) const;
bool hasExceededCartesianProductLimit(TypePackId tp) const; bool hasExceededCartesianProductLimit(TypePackId tp) const;
std::optional<TypeId> memoizedof(TypeId ty) const;
std::optional<TypePackId> memoizedof(TypePackId tp) const;
}; };
} // namespace Luau } // namespace Luau

View file

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

View file

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

View file

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

View file

@ -579,6 +579,7 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
module->astOriginalCallTypes.clear(); module->astOriginalCallTypes.clear();
module->astOverloadResolvedTypes.clear(); module->astOverloadResolvedTypes.clear();
module->astResolvedTypes.clear(); module->astResolvedTypes.clear();
module->astOriginalResolvedTypes.clear();
module->astResolvedTypePacks.clear(); module->astResolvedTypePacks.clear();
module->astScopes.clear(); module->astScopes.clear();
@ -591,6 +592,7 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
module->astOriginalCallTypes.clear(); module->astOriginalCallTypes.clear();
module->astResolvedTypes.clear(); module->astResolvedTypes.clear();
module->astResolvedTypePacks.clear(); module->astResolvedTypePacks.clear();
module->astOriginalResolvedTypes.clear();
module->scopes.resize(1); module->scopes.resize(1);
} }
} }
@ -922,23 +924,22 @@ ModulePtr Frontend::check(
for (TypeError& e : cs.errors) for (TypeError& e : cs.errors)
result->errors.emplace_back(std::move(e)); result->errors.emplace_back(std::move(e));
result->scopes = std::move(cgb.scopes); 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->type = sourceModule.type;
result->clonePublicInterface(builtinTypes, iceHandler); 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->internalTypes);
freeze(result->interfaceTypes); freeze(result->interfaceTypes);
Luau::check(builtinTypes, logger.get(), sourceModule, result.get());
if (FFlag::DebugLuauLogSolverToJson) if (FFlag::DebugLuauLogSolverToJson)
{ {
std::string output = logger->compileOutput(); std::string output = logger->compileOutput();

View file

@ -2616,10 +2616,6 @@ private:
emitWarning(*context, LintWarning::Code_IntegerParsing, node->location, emitWarning(*context, LintWarning::Code_IntegerParsing, node->location,
"Hexadecimal number literal exceeded available precision and has been truncated to 2^64"); "Hexadecimal number literal exceeded available precision and has been truncated to 2^64");
break; 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; return true;

View file

@ -232,9 +232,6 @@ void Module::clonePublicInterface(NotNull<BuiltinTypes> builtinTypes, InternalEr
this->returnType = moduleScope->returnType; this->returnType = moduleScope->returnType;
this->exportedTypeBindings = std::move(moduleScope->exportedTypeBindings); this->exportedTypeBindings = std::move(moduleScope->exportedTypeBindings);
} }
freeze(internalTypes);
freeze(interfaceTypes);
} }
bool Module::hasModuleScope() const 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 // 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 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})}; 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})}; 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})}; 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})}; 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)) if (seen.contains(ty))
return true; 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; return true;
if (auto uty = get<UnionType>(ty)) if (auto uty = get<UnionType>(ty))

View file

@ -4,22 +4,24 @@
#include "Luau/Ast.h" #include "Luau/Ast.h"
#include "Luau/AstQuery.h" #include "Luau/AstQuery.h"
#include "Luau/Clone.h" #include "Luau/Clone.h"
#include "Luau/DcrLogger.h"
#include "Luau/Error.h" #include "Luau/Error.h"
#include "Luau/Instantiation.h" #include "Luau/Instantiation.h"
#include "Luau/Metamethods.h" #include "Luau/Metamethods.h"
#include "Luau/Normalize.h" #include "Luau/Normalize.h"
#include "Luau/ToString.h" #include "Luau/ToString.h"
#include "Luau/TxnLog.h" #include "Luau/TxnLog.h"
#include "Luau/TypeUtils.h"
#include "Luau/Type.h" #include "Luau/Type.h"
#include "Luau/TypeReduction.h"
#include "Luau/TypeUtils.h"
#include "Luau/Unifier.h" #include "Luau/Unifier.h"
#include "Luau/ToString.h"
#include "Luau/DcrLogger.h"
#include <algorithm> #include <algorithm>
LUAU_FASTFLAG(DebugLuauLogSolverToJson); LUAU_FASTFLAG(DebugLuauLogSolverToJson)
LUAU_FASTFLAG(DebugLuauMagicTypes); LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAG(DebugLuauDontReduceTypes)
LUAU_FASTFLAG(LuauNegatedClassTypes) LUAU_FASTFLAG(LuauNegatedClassTypes)
namespace Luau namespace Luau
@ -223,10 +225,7 @@ struct TypeChecker2
{ {
auto pusher = pushStack(stat); auto pusher = pushStack(stat);
if (0) if (auto s = stat->as<AstStatBlock>())
{
}
else if (auto s = stat->as<AstStatBlock>())
return visit(s); return visit(s);
else if (auto s = stat->as<AstStatIf>()) else if (auto s = stat->as<AstStatIf>())
return visit(s); return visit(s);
@ -340,8 +339,7 @@ struct TypeChecker2
if (value) if (value)
visit(value, RValue); visit(value, RValue);
TypeId* maybeValueType = value ? module->astTypes.find(value) : nullptr; if (i != local->values.size - 1 || value)
if (i != local->values.size - 1 || maybeValueType)
{ {
AstLocal* var = i < local->vars.size ? local->vars.data[i] : nullptr; AstLocal* var = i < local->vars.size ? local->vars.data[i] : nullptr;
@ -391,13 +389,26 @@ struct TypeChecker2
void visit(AstStatFor* forStatement) void visit(AstStatFor* forStatement)
{ {
if (forStatement->var->annotation) NotNull<Scope> scope = stack.back();
visit(forStatement->var->annotation);
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); visit(forStatement->body);
} }
@ -543,7 +554,7 @@ struct TypeChecker2
else else
reportError(GenericError{"Cannot iterate over a table without indexer"}, forInStatement->values.data[0]->location); 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 // nothing
} }
@ -624,6 +635,9 @@ struct TypeChecker2
visit(rhs, RValue); visit(rhs, RValue);
TypeId rhsType = lookupType(rhs); TypeId rhsType = lookupType(rhs);
if (get<NeverType>(lhsType))
continue;
if (!isSubtype(rhsType, lhsType, stack.back())) if (!isSubtype(rhsType, lhsType, stack.back()))
{ {
reportError(TypeMismatch{lhsType, rhsType}, rhs->location); reportError(TypeMismatch{lhsType, rhsType}, rhs->location);
@ -715,10 +729,7 @@ struct TypeChecker2
{ {
auto StackPusher = pushStack(expr); auto StackPusher = pushStack(expr);
if (0) if (auto e = expr->as<AstExprGroup>())
{
}
else if (auto e = expr->as<AstExprGroup>())
return visit(e, context); return visit(e, context);
else if (auto e = expr->as<AstExprConstantNil>()) else if (auto e = expr->as<AstExprConstantNil>())
return visit(e); return visit(e);
@ -770,34 +781,34 @@ struct TypeChecker2
void visit(AstExprConstantNil* expr) 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) 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); NotNull<Scope> scope = stack.back();
TypeId numberType = builtinTypes->numberType; TypeId actualType = lookupType(expr);
TypeId expectedType = builtinTypes->numberType;
if (!isSubtype(numberType, actualType, stack.back())) LUAU_ASSERT(isSubtype(actualType, expectedType, scope));
{
reportError(TypeMismatch{actualType, numberType}, number->location);
}
} }
void visit(AstExprConstantString* string) void visit(AstExprConstantString* expr)
{ {
TypeId actualType = lookupType(string); NotNull<Scope> scope = stack.back();
TypeId stringType = builtinTypes->stringType; TypeId actualType = lookupType(expr);
TypeId expectedType = builtinTypes->stringType;
if (!isSubtype(actualType, stringType, stack.back())) LUAU_ASSERT(isSubtype(actualType, expectedType, scope));
{
reportError(TypeMismatch{actualType, stringType}, string->location);
}
} }
void visit(AstExprLocal* expr) void visit(AstExprLocal* expr)
@ -832,7 +843,7 @@ struct TypeChecker2
std::vector<Location> argLocs; std::vector<Location> argLocs;
argLocs.reserve(call->args.size + 1); argLocs.reserve(call->args.size + 1);
if (get<AnyType>(functionType) || get<ErrorType>(functionType)) if (get<AnyType>(functionType) || get<ErrorType>(functionType) || get<NeverType>(functionType))
return; return;
else if (std::optional<TypeId> callMm = findMetatableEntry(builtinTypes, module->errors, functionType, "__call", call->func->location)) 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->left, LValue);
visit(expr->right, LValue); visit(expr->right, LValue);
@ -1164,7 +1175,7 @@ struct TypeChecker2
if (mm) if (mm)
{ {
void* key = expr; AstNode* key = expr;
if (overrideKey != nullptr) if (overrideKey != nullptr)
key = overrideKey; key = overrideKey;
@ -1381,19 +1392,8 @@ struct TypeChecker2
{ {
pack = follow(pack); pack = follow(pack);
while (true) if (auto fst = first(pack, /*ignoreHiddenVariadics*/ false))
{ return *fst;
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;
else if (auto ftp = get<FreeTypePack>(pack)) else if (auto ftp = get<FreeTypePack>(pack))
{ {
TypeId result = testArena.addType(FreeType{ftp->scope}); TypeId result = testArena.addType(FreeType{ftp->scope});
@ -1407,6 +1407,8 @@ struct TypeChecker2
} }
else if (get<Unifiable::Error>(pack)) else if (get<Unifiable::Error>(pack))
return builtinTypes->errorRecoveryType(); return builtinTypes->errorRecoveryType();
else if (finite(pack) && size(pack) == 0)
return builtinTypes->nilType; // `(f())` where `f()` returns no values is coerced into `nil`
else else
ice.ice("flattenPack got a weird pack!"); 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> template<typename TID>
bool isSubtype(TID subTy, TID superTy, NotNull<Scope> scope) 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) void check(NotNull<BuiltinTypes> builtinTypes, DcrLogger* logger, const SourceModule& sourceModule, Module* module)
{ {
TypeChecker2 typeChecker{builtinTypes, logger, &sourceModule, module}; TypeChecker2 typeChecker{builtinTypes, logger, &sourceModule, module};
typeChecker.reduceTypes();
typeChecker.visit(sourceModule.root); typeChecker.visit(sourceModule.root);
unfreeze(module->interfaceTypes); unfreeze(module->interfaceTypes);

View file

@ -323,6 +323,8 @@ ModulePtr TypeChecker::checkWithoutRecursionCheck(const SourceModule& module, Mo
normalizer.arena = nullptr; normalizer.arena = nullptr;
currentModule->clonePublicInterface(builtinTypes, *iceHandler); currentModule->clonePublicInterface(builtinTypes, *iceHandler);
freeze(currentModule->internalTypes);
freeze(currentModule->interfaceTypes);
// Clear unifier cache since it's keyed off internal types that get deallocated // 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. // 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> #include <deque>
LUAU_FASTINTVARIABLE(LuauTypeReductionCartesianProductLimit, 100'000) LUAU_FASTINTVARIABLE(LuauTypeReductionCartesianProductLimit, 100'000)
LUAU_FASTINTVARIABLE(LuauTypeReductionRecursionLimit, 700) LUAU_FASTINTVARIABLE(LuauTypeReductionRecursionLimit, 400)
LUAU_FASTFLAGVARIABLE(DebugLuauDontReduceTypes, false) LUAU_FASTFLAGVARIABLE(DebugLuauDontReduceTypes, false)
namespace Luau namespace Luau
@ -37,7 +37,7 @@ struct TypeReducer
DenseHashMap<TypeId, ReductionContext<TypeId>>* memoizedTypes; DenseHashMap<TypeId, ReductionContext<TypeId>>* memoizedTypes;
DenseHashMap<TypePackId, ReductionContext<TypePackId>>* memoizedTypePacks; DenseHashMap<TypePackId, ReductionContext<TypePackId>>* memoizedTypePacks;
DenseHashSet<TypeId>* cyclicTypes; DenseHashSet<const void*>* cyclics;
int depth = 0; int depth = 0;
@ -68,8 +68,8 @@ struct TypeReducer
return {ctx->type, getMutable<T>(ctx->type)}; return {ctx->type, getMutable<T>(ctx->type)};
TypeId copiedTy = arena->addType(*t); TypeId copiedTy = arena->addType(*t);
(*memoizedTypes)[ty] = {copiedTy, false}; (*memoizedTypes)[ty] = {copiedTy, true};
(*memoizedTypes)[copiedTy] = {copiedTy, false}; (*memoizedTypes)[copiedTy] = {copiedTy, true};
return {copiedTy, getMutable<T>(copiedTy)}; return {copiedTy, getMutable<T>(copiedTy)};
} }
@ -142,31 +142,20 @@ struct TypeReducer
std::vector<TypeId> result; std::vector<TypeId> result;
bool didReduce = false; bool didReduce = false;
foldl_impl<T>(it, endIt, f, &result, &didReduce); foldl_impl<T>(it, endIt, f, &result, &didReduce);
if (!didReduce && ty)
return *ty; // 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 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`.
return reduce(flatten<T>(std::move(result))); return reduce(flatten<T>(std::move(result)));
}
} }
template<typename T> template<typename T>
TypeId apply(BinaryFold f, TypeId left, TypeId right) TypeId apply(BinaryFold f, TypeId left, TypeId right)
{ {
left = follow(left); std::vector<TypeId> types{left, right};
right = follow(right); return foldl<T>(begin(types), end(types), std::nullopt, f);
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> template<typename Into, typename Over>
@ -188,8 +177,8 @@ TypeId TypeReducer::reduce(TypeId ty)
if (auto ctx = memoizedTypes->find(ty); ctx && ctx->irreducible) if (auto ctx = memoizedTypes->find(ty); ctx && ctx->irreducible)
return ctx->type; return ctx->type;
else if (auto cyclicTy = cyclicTypes->find(ty)) else if (cyclics->contains(ty))
return *cyclicTy; return ty;
RecursionLimiter rl{&depth, FInt::LuauTypeReductionRecursionLimit}; RecursionLimiter rl{&depth, FInt::LuauTypeReductionRecursionLimit};
@ -216,6 +205,8 @@ TypePackId TypeReducer::reduce(TypePackId tp)
if (auto ctx = memoizedTypePacks->find(tp); ctx && ctx->irreducible) if (auto ctx = memoizedTypePacks->find(tp); ctx && ctx->irreducible)
return ctx->type; return ctx->type;
else if (cyclics->contains(tp))
return tp;
RecursionLimiter rl{&depth, FInt::LuauTypeReductionRecursionLimit}; 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) else if (t1->state == TableState::Generic || t2->state == TableState::Generic)
return std::nullopt; // '{ x: T } & { x: U } ~ '{ x: T } & { x: U } 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 & {} 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 return std::nullopt; // {} & (t1 where t1 = { p: t1 }) ~ {} & t1
TypeId resultTy = arena->addType(TableType{}); 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]: _ } return std::nullopt; // { [string]: _ } & { [number]: _ } ~ { [string]: _ } & { [number]: _ }
TypeId valueTy = apply<IntersectionType>(&TypeReducer::intersectionType, t1->indexer->indexResultType, t2->indexer->indexResultType); TypeId valueTy = apply<IntersectionType>(&TypeReducer::intersectionType, t1->indexer->indexResultType, t2->indexer->indexResultType);
if (get<NeverType>(valueTy)) table->indexer = TableIndexer{keyTy, valueTy}; // { [string]: number } & { [string]: string } ~ { [string]: never }
return builtinTypes->neverType; // { [_]: string } & { [_]: number } ~ { [_]: string & number } ~ { [_]: never } ~ never
table->indexer = TableIndexer{keyTy, valueTy};
} }
else if (t1->indexer) 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 return intersectionType(right, left); // T & M ~ M & T
else if (auto [m1, m2] = get2<MetatableType, MetatableType>(left, right); m1 && m2) else if (auto [m1, m2] = get2<MetatableType, MetatableType>(left, right); m1 && m2)
return std::nullopt; // TODO 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)) else if (auto nl = get<NegationType>(left))
{ {
// These should've been reduced already. // 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) else if (auto [nc, c] = get2<ClassType, ClassType>(nlTy, right); nc && c)
{ {
if (isSubclass(nc, c)) if (isSubclass(c, nc))
return std::nullopt; // ~Derived & Base ~ ~Derived & Base
else if (isSubclass(c, nc))
return builtinTypes->neverType; // ~Base & Derived ~ never return builtinTypes->neverType; // ~Base & Derived ~ never
else if (isSubclass(nc, c))
return std::nullopt; // ~Derived & Base ~ ~Derived & Base
else else
return right; // ~Base & Unrelated ~ Unrelated return right; // ~Base & Unrelated ~ Unrelated
} }
@ -499,7 +526,7 @@ std::optional<TypeId> TypeReducer::intersectionType(TypeId left, TypeId right)
return right; // ~string & {} ~ {} return right; // ~string & {} ~ {}
} }
else else
return std::nullopt; // TODO return right; // ~T & U ~ U
} }
else if (get<NegationType>(right)) else if (get<NegationType>(right))
return intersectionType(right, left); // T & ~U ~ ~U & T 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) else if (auto [nc, c] = get2<ClassType, ClassType>(nlTy, right); nc && c)
{ {
if (isSubclass(nc, c)) if (isSubclass(c, nc))
return builtinTypes->unknownType; // ~Derived | Base ~ unknown
else if (isSubclass(c, nc))
return std::nullopt; // ~Base | Derived ~ ~Base | Derived return std::nullopt; // ~Base | Derived ~ ~Base | Derived
else if (isSubclass(nc, c))
return builtinTypes->unknownType; // ~Derived | Base ~ unknown
else else
return left; // ~Base | Unrelated ~ ~Base return left; // ~Base | Unrelated ~ ~Base
} }
@ -777,22 +804,24 @@ TypeId TypeReducer::negationType(TypeId ty)
if (!n) if (!n)
return arena->addType(NegationType{ty}); 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 return nn->ty; // ~~T ~ T
else if (get<NeverType>(n->ty)) else if (get<NeverType>(negatedTy))
return builtinTypes->unknownType; // ~never ~ unknown return builtinTypes->unknownType; // ~never ~ unknown
else if (get<UnknownType>(n->ty)) else if (get<UnknownType>(negatedTy))
return builtinTypes->neverType; // ~unknown ~ never return builtinTypes->neverType; // ~unknown ~ never
else if (get<AnyType>(n->ty)) else if (get<AnyType>(negatedTy))
return builtinTypes->anyType; // ~any ~ any return builtinTypes->anyType; // ~any ~ any
else if (auto ni = get<IntersectionType>(n->ty)) else if (auto ni = get<IntersectionType>(negatedTy))
{ {
std::vector<TypeId> options; std::vector<TypeId> options;
for (TypeId part : ni) for (TypeId part : ni)
options.push_back(negationType(arena->addType(NegationType{part}))); options.push_back(negationType(arena->addType(NegationType{part})));
return reduce(flatten<UnionType>(std::move(options))); // ~(T & U) ~ (~T | ~U) 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; std::vector<TypeId> parts;
for (TypeId option : nu) for (TypeId option : nu)
@ -910,16 +939,26 @@ TypePackId TypeReducer::memoize(TypePackId tp, TypePackId reducedTp)
struct MarkCycles : TypeVisitor struct MarkCycles : TypeVisitor
{ {
DenseHashSet<TypeId> cyclicTypes{nullptr}; DenseHashSet<const void*> cyclics{nullptr};
void cycle(TypeId ty) override 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 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; return ty;
else if (!options.allowTypeReductionsFromOtherArenas && ty->owningArena != arena) else if (!options.allowTypeReductionsFromOtherArenas && ty->owningArena != arena)
return ty; return ty;
else if (auto ctx = memoizedTypes.find(ty); ctx && ctx->irreducible) else if (auto memoized = memoizedof(ty))
return ctx->type; return *memoized;
else if (hasExceededCartesianProductLimit(ty)) else if (hasExceededCartesianProductLimit(ty))
return std::nullopt; return std::nullopt;
@ -952,7 +991,7 @@ std::optional<TypeId> TypeReduction::reduce(TypeId ty)
MarkCycles finder; MarkCycles finder;
finder.traverse(ty); 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); return reducer.reduce(ty);
} }
catch (const RecursionLimitException&) catch (const RecursionLimitException&)
@ -969,8 +1008,8 @@ std::optional<TypePackId> TypeReduction::reduce(TypePackId tp)
return tp; return tp;
else if (!options.allowTypeReductionsFromOtherArenas && tp->owningArena != arena) else if (!options.allowTypeReductionsFromOtherArenas && tp->owningArena != arena)
return tp; return tp;
else if (auto ctx = memoizedTypePacks.find(tp); ctx && ctx->irreducible) else if (auto memoized = memoizedof(tp))
return ctx->type; return *memoized;
else if (hasExceededCartesianProductLimit(tp)) else if (hasExceededCartesianProductLimit(tp))
return std::nullopt; return std::nullopt;
@ -979,7 +1018,7 @@ std::optional<TypePackId> TypeReduction::reduce(TypePackId tp)
MarkCycles finder; MarkCycles finder;
finder.traverse(tp); 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); return reducer.reduce(tp);
} }
catch (const RecursionLimitException&) catch (const RecursionLimitException&)
@ -1000,6 +1039,13 @@ std::optional<TypeFun> TypeReduction::reduce(const TypeFun& fun)
return std::nullopt; 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 size_t TypeReduction::cartesianProductSize(TypeId ty) const
{ {
ty = follow(ty); ty = follow(ty);
@ -1047,4 +1093,24 @@ bool TypeReduction::hasExceededCartesianProductLimit(TypePackId tp) const
return false; 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 } // namespace Luau

View file

@ -22,6 +22,7 @@ LUAU_FASTFLAGVARIABLE(LuauInstantiateInSubtyping, false)
LUAU_FASTFLAGVARIABLE(LuauScalarShapeUnifyToMtOwner2, false) LUAU_FASTFLAGVARIABLE(LuauScalarShapeUnifyToMtOwner2, false)
LUAU_FASTFLAGVARIABLE(LuauUninhabitedSubAnything2, false) LUAU_FASTFLAGVARIABLE(LuauUninhabitedSubAnything2, false)
LUAU_FASTFLAGVARIABLE(LuauMaintainScopesInUnifier, false) LUAU_FASTFLAGVARIABLE(LuauMaintainScopesInUnifier, false)
LUAU_FASTFLAGVARIABLE(LuauTableUnifyInstantiationFix, false)
LUAU_FASTFLAG(LuauClassTypeVarsInSubstitution) LUAU_FASTFLAG(LuauClassTypeVarsInSubstitution)
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution) LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
LUAU_FASTFLAG(LuauNegatedFunctionTypes) LUAU_FASTFLAG(LuauNegatedFunctionTypes)
@ -600,11 +601,8 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
else if (log.getMutable<ClassType>(subTy)) else if (log.getMutable<ClassType>(subTy))
tryUnifyWithClass(subTy, superTy, /*reversed*/ true); tryUnifyWithClass(subTy, superTy, /*reversed*/ true);
else if (log.get<NegationType>(superTy)) else if (log.get<NegationType>(superTy) || log.get<NegationType>(subTy))
tryUnifyTypeWithNegation(subTy, superTy); tryUnifyNegations(subTy, superTy);
else if (log.get<NegationType>(subTy))
tryUnifyNegationWithType(subTy, superTy);
else if (FFlag::LuauUninhabitedSubAnything2 && !normalizer->isInhabited(subTy)) 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()}); 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) void Unifier::tryUnifyIntersectionWithType(TypeId subTy, const IntersectionType* uv, TypeId superTy, bool cacheEnabled, bool isFunctionCall)
{ {
// A & B <: T if A <: T or B <: T // 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; std::vector<TxnLog> logs;
for (size_t i = 0; i < uv->parts.size(); ++i) 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) void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
{ {
TypeId activeSubTy = subTy;
TableType* superTable = log.getMutable<TableType>(superTy); TableType* superTable = log.getMutable<TableType>(superTy);
TableType* subTable = log.getMutable<TableType>(subTy); TableType* subTable = log.getMutable<TableType>(subTy);
TableType* instantiatedSubTable = subTable; TableType* instantiatedSubTable = subTable; // TODO: remove with FFlagLuauTableUnifyInstantiationFix
if (!superTable || !subTable) if (!superTable || !subTable)
ice("passed non-table types to unifyTables"); ice("passed non-table types to unifyTables");
@ -1747,8 +1784,16 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
std::optional<TypeId> instantiated = instantiation.substitute(subTy); std::optional<TypeId> instantiated = instantiation.substitute(subTy);
if (instantiated.has_value()) if (instantiated.has_value())
{ {
subTable = log.getMutable<TableType>(*instantiated); if (FFlag::LuauTableUnifyInstantiationFix)
instantiatedSubTable = subTable; {
activeSubTy = *instantiated;
subTable = log.getMutable<TableType>(activeSubTy);
}
else
{
subTable = log.getMutable<TableType>(*instantiated);
instantiatedSubTable = subTable;
}
if (!subTable) if (!subTable)
ice("instantiation made a table type into a non-table type in tryUnifyTables"); 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) else if (subTable->state == TableState::Free)
{ {
PendingType* pendingSub = log.queue(subTy); PendingType* pendingSub = log.queue(activeSubTy);
TableType* ttv = getMutable<TableType>(pendingSub); TableType* ttv = getMutable<TableType>(pendingSub);
LUAU_ASSERT(ttv); LUAU_ASSERT(ttv);
ttv->props[name] = prop; 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 // table. If we detect that this has happened, we start over, with the updated
// txn log. // txn log.
TypeId superTyNew = FFlag::LuauScalarShapeUnifyToMtOwner2 ? log.follow(superTy) : superTy; 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 (FFlag::LuauScalarShapeUnifyToMtOwner2)
{ {
// If one of the types stopped being a table altogether, we need to restart from the top // 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); 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* newSuperTable = log.getMutable<TableType>(superTyNew);
TableType* newSubTable = log.getMutable<TableType>(subTyNew); 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()) if (errors.empty())
return tryUnifyTables(subTy, superTy, isIntersection); return tryUnifyTables(subTy, superTy, isIntersection);
@ -1922,12 +1967,12 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
extraProperties.push_back(name); extraProperties.push_back(name);
TypeId superTyNew = FFlag::LuauScalarShapeUnifyToMtOwner2 ? log.follow(superTy) : superTy; 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 (FFlag::LuauScalarShapeUnifyToMtOwner2)
{ {
// If one of the types stopped being a table altogether, we need to restart from the top // 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); return tryUnify(subTy, superTy, false, isIntersection);
} }
@ -1936,7 +1981,8 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
// txn log. // txn log.
TableType* newSuperTable = log.getMutable<TableType>(superTyNew); TableType* newSuperTable = log.getMutable<TableType>(superTyNew);
TableType* newSubTable = log.getMutable<TableType>(subTyNew); 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()) if (errors.empty())
return tryUnifyTables(subTy, superTy, isIntersection); return tryUnifyTables(subTy, superTy, isIntersection);
@ -1992,7 +2038,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
if (FFlag::LuauScalarShapeUnifyToMtOwner2) if (FFlag::LuauScalarShapeUnifyToMtOwner2)
{ {
superTable = log.getMutable<TableType>(log.follow(superTy)); superTable = log.getMutable<TableType>(log.follow(superTy));
subTable = log.getMutable<TableType>(log.follow(subTy)); subTable = log.getMutable<TableType>(log.follow(activeSubTy));
if (!superTable || !subTable) if (!superTable || !subTable)
return; return;
@ -2000,7 +2046,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
else else
{ {
superTable = log.getMutable<TableType>(superTy); superTable = log.getMutable<TableType>(superTy);
subTable = log.getMutable<TableType>(subTy); subTable = log.getMutable<TableType>(activeSubTy);
} }
if (!missingProperties.empty()) if (!missingProperties.empty())
@ -2313,11 +2359,10 @@ void Unifier::tryUnifyWithClass(TypeId subTy, TypeId superTy, bool reversed)
return fail(); return fail();
} }
void Unifier::tryUnifyTypeWithNegation(TypeId subTy, TypeId superTy) void Unifier::tryUnifyNegations(TypeId subTy, TypeId superTy)
{ {
const NegationType* ntv = get<NegationType>(superTy); if (!log.get<NegationType>(subTy) && !log.get<NegationType>(superTy))
if (!ntv) ice("tryUnifyNegations superTy or subTy must be a negation type");
ice("tryUnifyTypeWithNegation superTy must be a negation type");
const NormalizedType* subNorm = normalizer->normalize(subTy); const NormalizedType* subNorm = normalizer->normalize(subTy);
const NormalizedType* superNorm = normalizer->normalize(superTy); const NormalizedType* superNorm = normalizer->normalize(superTy);
@ -2331,16 +2376,6 @@ void Unifier::tryUnifyTypeWithNegation(TypeId subTy, TypeId superTy)
reportError(location, TypeMismatch{superTy, subTy, mismatchContext()}); 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) static void queueTypePack(std::vector<TypeId>& queue, DenseHashSet<TypePackId>& seenTypePacks, Unifier& state, TypePackId a, TypePackId anyTypePack)
{ {
while (true) while (true)

View file

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

View file

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

View file

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

View file

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

View file

@ -3,8 +3,7 @@
#include "Luau/Bytecode.h" #include "Luau/Bytecode.h"
#include "Luau/Common.h" #include "Luau/Common.h"
#include "Luau/IrData.h"
#include "IrData.h"
namespace Luau 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); 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) void AssemblyBuilderX64::vxorpd(OperandX64 dst, OperandX64 src1, OperandX64 src2)
{ {
placeAvx("vxorpd", dst, src1, src2, 0x57, false, AVX_0F, AVX_66); 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) void AssemblyBuilderX64::vucomisd(OperandX64 src1, OperandX64 src2)
{ {
placeAvx("vucomisd", src1, src2, 0x2e, false, AVX_0F, AVX_66); 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); 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() void AssemblyBuilderX64::finalize()
{ {
code.resize(codePos - code.data()); 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())); 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) OperandX64 AssemblyBuilderX64::bytes(const void* ptr, size_t size, size_t align)
{ {
size_t pos = allocateData(size, align); size_t pos = allocateData(size, align);

View file

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

View file

@ -424,6 +424,197 @@ BuiltinImplResult emitBuiltinMathLog(AssemblyBuilderX64& build, int nparams, int
return {BuiltinImplType::UsesFallback, 1}; 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) BuiltinImplResult emitBuiltin(AssemblyBuilderX64& build, int bfid, int nparams, int ra, int arg, OperandX64 args, int nresults, Label& fallback)
{ {
switch (bfid) switch (bfid)
@ -476,6 +667,18 @@ BuiltinImplResult emitBuiltin(AssemblyBuilderX64& build, int bfid, int nparams,
return emitBuiltinMathLog10(build, nparams, ra, arg, args, nresults, fallback); return emitBuiltinMathLog10(build, nparams, ra, arg, args, nresults, fallback);
case LBF_MATH_LOG: case LBF_MATH_LOG:
return emitBuiltinMathLog(build, nparams, ra, arg, args, nresults, fallback); 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: default:
return {BuiltinImplType::None, -1}; 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 sClosure = qword[rsp + kStackSize + 0]; // Closure* cl
constexpr OperandX64 sCode = qword[rsp + kStackSize + 8]; // Instruction* code 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 // 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) #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 // 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 "Luau/IrData.h"
#include "IrUtils.h" #include "Luau/IrUtils.h"
#include <stddef.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 // 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/Common.h"
#include "Luau/IrUtils.h"
#include "CustomExecUtils.h" #include "CustomExecUtils.h"
#include "IrTranslation.h" #include "IrTranslation.h"
#include "IrUtils.h"
#include "lapi.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 // 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" #include "lua.h"

View file

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

View file

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

View file

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

View file

@ -87,6 +87,10 @@ void initHelperFunctions(NativeState& data)
data.context.libm_log = log; data.context.libm_log = log;
data.context.libm_log2 = log2; data.context.libm_log2 = log2;
data.context.libm_log10 = log10; 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_asin = asin;
data.context.libm_sin = sin; data.context.libm_sin = sin;

View file

@ -96,6 +96,10 @@ struct NativeContext
double (*libm_log)(double) = nullptr; double (*libm_log)(double) = nullptr;
double (*libm_log2)(double) = nullptr; double (*libm_log2)(double) = nullptr;
double (*libm_log10)(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 // Helper functions
bool (*forgLoopNodeIter)(lua_State* L, Table* h, int index, TValue* ra) = nullptr; 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(LuauCompileInlineThresholdMaxBoost, 300)
LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5) LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5)
LUAU_FASTFLAGVARIABLE(LuauMultiAssignmentConflictFix, false)
LUAU_FASTFLAGVARIABLE(LuauSelfAssignmentSkip, false)
LUAU_FASTFLAGVARIABLE(LuauCompileInterpStringLimit, false)
namespace Luau namespace Luau
{ {
@ -1580,8 +1576,7 @@ struct Compiler
RegScope rs(this); RegScope rs(this);
uint8_t baseReg = FFlag::LuauCompileInterpStringLimit ? allocReg(expr, unsigned(2 + expr->expressions.size)) uint8_t baseReg = allocReg(expr, unsigned(2 + expr->expressions.size));
: allocReg(expr, uint8_t(2 + expr->expressions.size));
emitLoadK(baseReg, formatStringIndex); emitLoadK(baseReg, formatStringIndex);
@ -2030,7 +2025,7 @@ struct Compiler
if (int reg = getExprLocalReg(expr); reg >= 0) if (int reg = getExprLocalReg(expr); reg >= 0)
{ {
// Optimization: we don't need to move if target happens to be in the same register // 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); bytecode.emitABC(LOP_MOVE, target, uint8_t(reg), 0);
} }
else else
@ -2982,48 +2977,31 @@ struct Compiler
Visitor visitor(this); 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
for (size_t i = 0; i < vars.size(); ++i)
{ {
// mark any registers that are used *after* assignment as conflicting const LValue& li = vars[i].lvalue;
// first we go through assignments to locals, since they are performed before assignments to other l-values if (li.kind == LValue::Kind_Local)
for (size_t i = 0; i < vars.size(); ++i)
{ {
const LValue& li = vars[i].lvalue;
if (li.kind == LValue::Kind_Local)
{
if (i < values.size)
values.data[i]->visit(&visitor);
visitor.assigned[li.reg] = true;
}
}
// and now we handle all other l-values
for (size_t i = 0; i < vars.size(); ++i)
{
const LValue& li = vars[i].lvalue;
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) if (i < values.size)
values.data[i]->visit(&visitor); values.data[i]->visit(&visitor);
if (li.kind == LValue::Kind_Local) visitor.assigned[li.reg] = true;
visitor.assigned[li.reg] = true;
} }
} }
// and now we handle all other l-values
for (size_t i = 0; i < vars.size(); ++i)
{
const LValue& li = vars[i].lvalue;
if (li.kind != LValue::Kind_Local && i < values.size)
values.data[i]->visit(&visitor);
}
// mark any registers used in trailing expressions as conflicting as well // mark any registers used in trailing expressions as conflicting as well
for (size_t i = vars.size(); i < values.size; ++i) for (size_t i = vars.size(); i < values.size; ++i)
values.data[i]->visit(&visitor); values.data[i]->visit(&visitor);

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 $(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 $(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 $(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 $(TESTS_TARGET): LDFLAGS+=-lpthread
$(REPL_CLI_TARGET): LDFLAGS+=-lpthread $(REPL_CLI_TARGET): LDFLAGS+=-lpthread
@ -192,7 +192,7 @@ $(TESTS_TARGET) $(REPL_CLI_TARGET) $(ANALYZE_CLI_TARGET):
$(CXX) $^ $(LDFLAGS) -o $@ $(CXX) $^ $(LDFLAGS) -o $@
# executable targets for fuzzing # 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 $@ $(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 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/CodeGen.h
CodeGen/include/Luau/ConditionA64.h CodeGen/include/Luau/ConditionA64.h
CodeGen/include/Luau/ConditionX64.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/Label.h
CodeGen/include/Luau/OperandX64.h CodeGen/include/Luau/OperandX64.h
CodeGen/include/Luau/RegisterA64.h CodeGen/include/Luau/RegisterA64.h
@ -100,13 +105,8 @@ target_sources(Luau.CodeGen PRIVATE
CodeGen/src/EmitInstructionX64.h CodeGen/src/EmitInstructionX64.h
CodeGen/src/Fallbacks.h CodeGen/src/Fallbacks.h
CodeGen/src/FallbacksProlog.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/IrLoweringX64.h
CodeGen/src/IrTranslation.h CodeGen/src/IrTranslation.h
CodeGen/src/IrUtils.h
CodeGen/src/NativeState.h CodeGen/src/NativeState.h
) )
@ -120,7 +120,7 @@ target_sources(Luau.Analysis PRIVATE
Analysis/include/Luau/BuiltinDefinitions.h Analysis/include/Luau/BuiltinDefinitions.h
Analysis/include/Luau/Clone.h Analysis/include/Luau/Clone.h
Analysis/include/Luau/Config.h Analysis/include/Luau/Config.h
Analysis/include/Luau/Connective.h Analysis/include/Luau/Refinement.h
Analysis/include/Luau/Constraint.h Analysis/include/Luau/Constraint.h
Analysis/include/Luau/ConstraintGraphBuilder.h Analysis/include/Luau/ConstraintGraphBuilder.h
Analysis/include/Luau/ConstraintSolver.h Analysis/include/Luau/ConstraintSolver.h
@ -175,7 +175,7 @@ target_sources(Luau.Analysis PRIVATE
Analysis/src/BuiltinDefinitions.cpp Analysis/src/BuiltinDefinitions.cpp
Analysis/src/Clone.cpp Analysis/src/Clone.cpp
Analysis/src/Config.cpp Analysis/src/Config.cpp
Analysis/src/Connective.cpp Analysis/src/Refinement.cpp
Analysis/src/Constraint.cpp Analysis/src/Constraint.cpp
Analysis/src/ConstraintGraphBuilder.cpp Analysis/src/ConstraintGraphBuilder.cpp
Analysis/src/ConstraintSolver.cpp Analysis/src/ConstraintSolver.cpp

View file

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

View file

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

View file

@ -4,6 +4,7 @@
#include "Luau/BuiltinDefinitions.h" #include "Luau/BuiltinDefinitions.h"
#include "Luau/BytecodeBuilder.h" #include "Luau/BytecodeBuilder.h"
#include "Luau/CodeGen.h"
#include "Luau/Common.h" #include "Luau/Common.h"
#include "Luau/Compiler.h" #include "Luau/Compiler.h"
#include "Luau/Frontend.h" #include "Luau/Frontend.h"
@ -25,11 +26,13 @@ const bool kFuzzLinter = true;
const bool kFuzzTypeck = true; const bool kFuzzTypeck = true;
const bool kFuzzVM = true; const bool kFuzzVM = true;
const bool kFuzzTranspile = true; const bool kFuzzTranspile = true;
const bool kFuzzCodegen = true;
// Should we generate type annotations? // Should we generate type annotations?
const bool kFuzzTypes = true; const bool kFuzzTypes = true;
static_assert(!(kFuzzVM && !kFuzzCompiler), "VM requires the compiler!"); 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); 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); lua_State* L = lua_newstate(allocate, NULL);
if (kFuzzCodegen && Luau::CodeGen::isSupported())
Luau::CodeGen::create(L);
lua_callbacks(L)->interrupt = interrupt; lua_callbacks(L)->interrupt = interrupt;
luaL_openlibs(L); luaL_openlibs(L);
@ -349,20 +355,30 @@ DEFINE_PROTO_FUZZER(const luau::ModuleSet& message)
{ {
static lua_State* globalState = createGlobalState(); static lua_State* globalState = createGlobalState();
lua_State* L = lua_newthread(globalState); auto runCode = [](const std::string& bytecode, bool useCodegen) {
luaL_sandboxthread(L); lua_State* L = lua_newthread(globalState);
luaL_sandboxthread(L);
if (luau_load(L, "=fuzz", bytecode.data(), bytecode.size(), 0) == 0) if (luau_load(L, "=fuzz", bytecode.data(), bytecode.size(), 0) == 0)
{ {
interruptDeadline = std::chrono::system_clock::now() + kInterruptTimeout; if (useCodegen)
Luau::CodeGen::compile(L, -1);
lua_resume(L, NULL, 0); interruptDeadline = std::chrono::system_clock::now() + kInterruptTimeout;
}
lua_pop(globalState, 1); lua_resume(L, NULL, 0);
}
// we'd expect full GC to reclaim all memory allocated by the script lua_pop(globalState, 1);
lua_gc(globalState, LUA_GCCOLLECT, 0);
LUAU_ASSERT(heapSize < 256 * 1024); // 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(vmulsd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0x2b, 0x59, 0xc6);
SINGLE_COMPARE(vdivsd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0x2b, 0x5e, 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(vxorpd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0x29, 0x57, 0xc6);
SINGLE_COMPARE(vandpd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0x29, 0x54, 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(vmaxsd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0x2b, 0x5f, 0xc6);
SINGLE_COMPARE(vminsd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0x2b, 0x5d, 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") TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "AVXUnaryMergeInstructionForms")
@ -510,6 +514,7 @@ TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "AVXTernaryInstructionForms")
SINGLE_COMPARE( SINGLE_COMPARE(
vroundsd(xmm8, xmm13, xmmword[r13 + rdx], RoundingModeX64::RoundToPositiveInfinity), 0xc4, 0x43, 0x11, 0x0b, 0x44, 0x15, 0x00, 0x0a); 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(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") 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)); build.vmovaps(xmm4, build.f32x4(1.0f, 2.0f, 4.0f, 8.0f));
char arr[16] = "hello world!123"; char arr[16] = "hello world!123";
build.vmovupd(xmm5, build.bytes(arr, 16, 8)); build.vmovupd(xmm5, build.bytes(arr, 16, 8));
build.vmovapd(xmm5, build.f64x2(5.0, 6.0));
build.ret(); build.ret();
}, },
{ {
@ -630,9 +636,12 @@ TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "Constants")
0xc4, 0xe1, 0x7b, 0x10, 0x1d, 0xcc, 0xff, 0xff, 0xff, 0xc4, 0xe1, 0x7b, 0x10, 0x1d, 0xcc, 0xff, 0xff, 0xff,
0xc4, 0xe1, 0x78, 0x28, 0x25, 0xab, 0xff, 0xff, 0xff, 0xc4, 0xe1, 0x78, 0x28, 0x25, 0xab, 0xff, 0xff, 0xff,
0xc4, 0xe1, 0x79, 0x10, 0x2d, 0x92, 0xff, 0xff, 0xff, 0xc4, 0xe1, 0x79, 0x10, 0x2d, 0x92, 0xff, 0xff, 0xff,
0xc4, 0xe1, 0x79, 0x28, 0x2d, 0x79, 0xff, 0xff, 0xff,
0xc3 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, 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '!', '1', '2', '3', 0x0,
0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x80, 0x3f,
0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40,

View file

@ -15,7 +15,6 @@
LUAU_FASTFLAG(LuauTraceTypesInNonstrictMode2) LUAU_FASTFLAG(LuauTraceTypesInNonstrictMode2)
LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel) LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel)
LUAU_FASTFLAG(LuauFixAutocompleteInIf)
LUAU_FASTFLAG(LuauFixAutocompleteInWhile) LUAU_FASTFLAG(LuauFixAutocompleteInWhile)
LUAU_FASTFLAG(LuauFixAutocompleteInFor) LUAU_FASTFLAG(LuauFixAutocompleteInFor)
@ -859,30 +858,16 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_if_middle_keywords")
CHECK_EQ(ac2.entryMap.count("end"), 0); CHECK_EQ(ac2.entryMap.count("end"), 0);
CHECK_EQ(ac2.context, AutocompleteContext::Keyword); CHECK_EQ(ac2.context, AutocompleteContext::Keyword);
if (FFlag::LuauFixAutocompleteInIf) check(R"(
{ if x t@1
check(R"( )");
if x t@1
)");
auto ac3 = autocomplete('1'); auto ac3 = autocomplete('1');
CHECK_EQ(3, ac3.entryMap.size()); CHECK_EQ(3, ac3.entryMap.size());
CHECK_EQ(ac3.entryMap.count("then"), 1); CHECK_EQ(ac3.entryMap.count("then"), 1);
CHECK_EQ(ac3.entryMap.count("and"), 1); CHECK_EQ(ac3.entryMap.count("and"), 1);
CHECK_EQ(ac3.entryMap.count("or"), 1); CHECK_EQ(ac3.entryMap.count("or"), 1);
CHECK_EQ(ac3.context, AutocompleteContext::Keyword); 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"( check(R"(
if x then if x then
@ -926,22 +911,19 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_if_middle_keywords")
CHECK_EQ(ac5.entryMap.count("end"), 0); CHECK_EQ(ac5.entryMap.count("end"), 0);
CHECK_EQ(ac5.context, AutocompleteContext::Statement); CHECK_EQ(ac5.context, AutocompleteContext::Statement);
if (FFlag::LuauFixAutocompleteInIf) check(R"(
{ if t@1
check(R"( )");
if t@1
)");
auto ac6 = autocomplete('1'); auto ac6 = autocomplete('1');
CHECK_EQ(ac6.entryMap.count("true"), 1); CHECK_EQ(ac6.entryMap.count("true"), 1);
CHECK_EQ(ac6.entryMap.count("false"), 1); CHECK_EQ(ac6.entryMap.count("false"), 1);
CHECK_EQ(ac6.entryMap.count("then"), 0); CHECK_EQ(ac6.entryMap.count("then"), 0);
CHECK_EQ(ac6.entryMap.count("function"), 1); CHECK_EQ(ac6.entryMap.count("function"), 1);
CHECK_EQ(ac6.entryMap.count("else"), 0); CHECK_EQ(ac6.entryMap.count("else"), 0);
CHECK_EQ(ac6.entryMap.count("elseif"), 0); CHECK_EQ(ac6.entryMap.count("elseif"), 0);
CHECK_EQ(ac6.entryMap.count("end"), 0); CHECK_EQ(ac6.entryMap.count("end"), 0);
CHECK_EQ(ac6.context, AutocompleteContext::Expression); CHECK_EQ(ac6.context, AutocompleteContext::Expression);
}
} }
TEST_CASE_FIXTURE(ACFixture, "autocomplete_until_in_repeat") 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") TEST_CASE_FIXTURE(ACFixture, "type_reduction_is_hooked_up_to_autocomplete")
{ {
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
check(R"( check(R"(
type T = { x: (number & string)? } 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")); REQUIRE(ac1.entryMap.count("x"));
std::optional<TypeId> ty1 = ac1.entryMap.at("x").type; std::optional<TypeId> ty1 = ac1.entryMap.at("x").type;
REQUIRE(ty1); REQUIRE(ty1);
CHECK("(number & string)?" == toString(*ty1, opts)); CHECK("nil" == toString(*ty1, opts));
// CHECK("nil" == toString(*ty1, opts));
auto ac2 = autocomplete('2'); auto ac2 = autocomplete('2');
REQUIRE(ac2.entryMap.count("thingamabob")); REQUIRE(ac2.entryMap.count("thingamabob"));
std::optional<TypeId> ty2 = ac2.entryMap.at("thingamabob").type; std::optional<TypeId> ty2 = ac2.entryMap.at("thingamabob").type;
REQUIRE(ty2); 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") TEST_CASE_FIXTURE(ACFixture, "string_contents_is_available_to_callback")

View file

@ -1025,8 +1025,6 @@ L0: RETURN R0 0
TEST_CASE("AndOr") TEST_CASE("AndOr")
{ {
ScopedFastFlag luauSelfAssignmentSkip{"LuauSelfAssignmentSkip", true};
// codegen for constant, local, global for and // codegen for constant, local, global for and
CHECK_EQ("\n" + compileFunction0("local a = 1 a = a and 2 return a"), R"( CHECK_EQ("\n" + compileFunction0("local a = 1 a = a and 2 return a"), R"(
LOADN R0 1 LOADN R0 1
@ -1319,8 +1317,6 @@ RETURN R0 0
TEST_CASE("InterpStringRegisterLimit") 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}", 254) + "`").c_str()), std::exception);
CHECK_THROWS_AS(compileFunction0(("local a = `" + rep("{1}", 253) + "`").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") TEST_CASE("UpvaluesLoopsBytecode")
{ {
ScopedFastFlag luauSelfAssignmentSkip{"LuauSelfAssignmentSkip", true};
CHECK_EQ("\n" + compileFunction(R"( CHECK_EQ("\n" + compileFunction(R"(
function test() function test()
for i=1,10 do for i=1,10 do
@ -5161,8 +5155,6 @@ RETURN R1 1
TEST_CASE("InlineMutate") TEST_CASE("InlineMutate")
{ {
ScopedFastFlag luauSelfAssignmentSkip{"LuauSelfAssignmentSkip", true};
// if the argument is mutated, it gets a register even if the value is constant // if the argument is mutated, it gets a register even if the value is constant
CHECK_EQ("\n" + compileFunction(R"( CHECK_EQ("\n" + compileFunction(R"(
local function foo(a) local function foo(a)
@ -6756,8 +6748,6 @@ MOVE R1 R3
RETURN R0 0 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 // 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"( CHECK_EQ("\n" + compileFunction0(R"(
local a, b = ... local a, b = ...
@ -6795,8 +6785,6 @@ L0: RETURN R1 -1
TEST_CASE("SkipSelfAssignment") TEST_CASE("SkipSelfAssignment")
{ {
ScopedFastFlag luauSelfAssignmentSkip{"LuauSelfAssignmentSkip", true};
CHECK_EQ("\n" + compileFunction0("local a a = a"), R"( CHECK_EQ("\n" + compileFunction0("local a a = a"), R"(
LOADNIL R0 LOADNIL R0
RETURN R0 0 RETURN R0 0

View file

@ -297,8 +297,6 @@ TEST_CASE("Clear")
TEST_CASE("Strings") TEST_CASE("Strings")
{ {
ScopedFastFlag luauStringFormatAnyFix{"LuauStringFormatAnyFix", true};
runConformance("strings.lua"); 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"); 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") TEST_CASE_FIXTURE(Fixture, "ComparisonPrecedence")
{ {
LintResult result = lint(R"( LintResult result = lint(R"(

View file

@ -2,6 +2,7 @@
#include "Fixture.h" #include "Fixture.h"
#include "Luau/AstQuery.h"
#include "Luau/Common.h" #include "Luau/Common.h"
#include "Luau/Type.h" #include "Luau/Type.h"
#include "doctest.h" #include "doctest.h"
@ -9,6 +10,8 @@
#include "Luau/Normalize.h" #include "Luau/Normalize.h"
#include "Luau/BuiltinDefinitions.h" #include "Luau/BuiltinDefinitions.h"
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
using namespace Luau; using namespace Luau;
namespace namespace
@ -377,9 +380,25 @@ struct NormalizeFixture : Fixture
normalizer.clearCaches(); normalizer.clearCaches();
CheckResult result = check("type _Res = " + annotation); CheckResult result = check("type _Res = " + annotation);
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
std::optional<TypeId> ty = lookupType("_Res");
REQUIRE(ty); if (FFlag::DebugLuauDeferredConstraintResolution)
return normalizer.normalize(*ty); {
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) 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") TEST_CASE_FIXTURE(Fixture, "parse_numbers_error")
{ {
ScopedFastFlag luauErrorDoubleHexPrefix{"LuauErrorDoubleHexPrefix", true};
CHECK_EQ(getParseError("return 0b123"), "Malformed number"); CHECK_EQ(getParseError("return 0b123"), "Malformed number");
CHECK_EQ(getParseError("return 123x"), "Malformed number"); CHECK_EQ(getParseError("return 123x"), "Malformed number");
CHECK_EQ(getParseError("return 0xg"), "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"); 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") TEST_CASE_FIXTURE(Fixture, "break_return_not_last_error")
{ {
CHECK_EQ(getParseError("return 0 print(5)"), "Expected <eof>, got 'print'"); 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") TEST_CASE_FIXTURE(Fixture, "bound")
{ {
CheckResult result = check(R"( TypeArena arena;
function a(): number return 444 end
local b = a()
)");
LUAU_REQUIRE_NO_ERRORS(result);
std::optional<TypeId> ty = getType("b"); TypeId ty = arena.addType(BoundType{builtinTypes->numberType});
REQUIRE(bool(ty));
ToDotOptions opts; ToDotOptions opts;
opts.showPointers = false; opts.showPointers = false;
@ -96,7 +91,7 @@ n1 [label="BoundType 1"];
n1 -> n2; n1 -> n2;
n2 [label="number"]; n2 [label="number"];
})", })",
toDot(*ty, opts)); toDot(ty, opts));
} }
TEST_CASE_FIXTURE(Fixture, "function") TEST_CASE_FIXTURE(Fixture, "function")
@ -172,10 +167,9 @@ n3 [label="number"];
TEST_CASE_FIXTURE(Fixture, "intersection") TEST_CASE_FIXTURE(Fixture, "intersection")
{ {
CheckResult result = check(R"( TypeArena arena;
local a: string & number -- uninhabited
)"); TypeId ty = arena.addType(IntersectionType{{builtinTypes->stringType, builtinTypes->numberType}});
LUAU_REQUIRE_NO_ERRORS(result);
ToDotOptions opts; ToDotOptions opts;
opts.showPointers = false; opts.showPointers = false;
@ -186,7 +180,7 @@ n2 [label="string"];
n1 -> n3; n1 -> n3;
n3 [label="number"]; n3 [label="number"];
})", })",
toDot(requireType("a"), opts)); toDot(ty, opts));
} }
TEST_CASE_FIXTURE(Fixture, "table") TEST_CASE_FIXTURE(Fixture, "table")
@ -396,44 +390,25 @@ n3 [label="number"];
TEST_CASE_FIXTURE(Fixture, "bound_table") TEST_CASE_FIXTURE(Fixture, "bound_table")
{ {
CheckResult result = check(R"( TypeArena arena;
local a = {x=2}
local b
b.x = 2
b = a
)");
LUAU_REQUIRE_NO_ERRORS(result);
std::optional<TypeId> ty = getType("b"); TypeId ty = arena.addType(TableType{});
REQUIRE(bool(ty)); getMutable<TableType>(ty)->props["x"] = {builtinTypes->numberType};
TypeId boundTy = arena.addType(TableType{});
getMutable<TableType>(boundTy)->boundTo = ty;
ToDotOptions opts; ToDotOptions opts;
opts.showPointers = false; opts.showPointers = false;
if (FFlag::DebugLuauDeferredConstraintResolution) CHECK_EQ(R"(digraph graphname {
{
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 [label="TableType 1"];
n1 -> n2 [label="boundTo"]; n1 -> n2 [label="boundTo"];
n2 [label="TableType a"]; n2 [label="TableType 2"];
n2 -> n3 [label="x"]; n2 -> n3 [label="x"];
n3 [label="number"]; n3 [label="number"];
})", })",
toDot(*ty, opts)); toDot(boundTy, opts));
}
} }
TEST_CASE_FIXTURE(Fixture, "builtintypes") 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; o.maxTypeLength = 30;
CHECK_EQ(toString(requireType("f0"), o), "() -> ()"); CHECK_EQ(toString(requireType("f0"), o), "() -> ()");
CHECK_EQ(toString(requireType("f1"), o), "<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?))... *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?))... *TRUNCATED*"); CHECK_EQ(toString(requireType("f3"), o), "<c>(c) -> (<b>(b) -> (<a>(a) -> (() -> ()) | (a & ~false & ~nil)... *TRUNCATED*");
} }
else else
{ {
@ -321,9 +321,9 @@ TEST_CASE_FIXTURE(Fixture, "stringifying_type_is_still_capped_when_exhaustive")
{ {
o.maxTypeLength = 30; o.maxTypeLength = 30;
CHECK_EQ(toString(requireType("f0"), o), "() -> ()"); CHECK_EQ(toString(requireType("f0"), o), "() -> ()");
CHECK_EQ(toString(requireType("f1"), o), "<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?))... *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?))... *TRUNCATED*"); CHECK_EQ(toString(requireType("f3"), o), "<c>(c) -> (<b>(b) -> (<a>(a) -> (() -> ()) | (a & ~false & ~nil)... *TRUNCATED*");
} }
else 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(_)) 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") TEST_CASE_FIXTURE(Fixture, "type_alias_fwd_declaration_is_precise")

View file

@ -132,22 +132,40 @@ TEST_CASE_FIXTURE(Fixture, "should_still_pick_an_overload_whose_arguments_are_un
TEST_CASE_FIXTURE(Fixture, "propagates_name") TEST_CASE_FIXTURE(Fixture, "propagates_name")
{ {
const std::string code = R"( if (FFlag::DebugLuauDeferredConstraintResolution)
type A={a:number} {
type B={b:string} CheckResult result = check(R"(
type A={a:number}
type B={b:string}
local c:A&B local c:A&B
local b = c local b = c
)"; )");
const std::string expected = R"(
type A={a:number}
type B={b:string}
local c:A&B LUAU_REQUIRE_NO_ERRORS(result);
local b:A&B=c
)";
CHECK_EQ(expected, decorateWithTypes(code)); CHECK("{| a: number, b: string |}" == toString(requireType("b")));
}
else
{
const std::string code = R"(
type A={a:number}
type B={b:string}
local c:A&B
local b = c
)";
const std::string expected = R"(
type A={a:number}
type B={b:string}
local c:A&B
local b:A&B=c
)";
CHECK_EQ(expected, decorateWithTypes(code));
}
} }
TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_with_property_guaranteed_to_exist") 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); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
const IntersectionType* r = get<IntersectionType>(requireType("r")); CHECK("{| y: number |}" == toString(requireType("r")));
REQUIRE(r); else
CHECK("{| y: number |} & {| y: number |}" == toString(requireType("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);
} }
TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_works_at_arbitrary_depth") TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_works_at_arbitrary_depth")
@ -207,7 +218,10 @@ TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_with_mixed_types")
)"); )");
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ("number & string", toString(requireType("r"))); if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ("never", toString(requireType("r")));
else
CHECK_EQ("number & string", toString(requireType("r")));
} }
TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_with_one_part_missing_the_property") TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_with_one_part_missing_the_property")
@ -387,7 +401,10 @@ local a: XYZ = 3
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ(toString(result.errors[0]), R"(Type 'number' could not be converted into 'X & Y & Z' 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: caused by:
Not all intersection parts are compatible. Type 'number' could not be converted into 'X')"); 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); 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") 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); 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") TEST_CASE_FIXTURE(Fixture, "intersect_false_and_bool_and_false")
@ -456,9 +481,14 @@ TEST_CASE_FIXTURE(Fixture, "intersect_false_and_bool_and_false")
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
// TODO: odd stringification of `false & (boolean & false)`.) if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ(toString(result.errors[0]), CHECK_EQ(toString(result.errors[0]), "Type 'false' could not be converted into 'true'");
"Type 'boolean & false & false' could not be converted into 'true'; none of the intersection parts are compatible"); 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") TEST_CASE_FIXTURE(Fixture, "intersect_saturate_overloaded_functions")
@ -496,8 +526,21 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables")
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); 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)
"'{| p: nil |}'; none of the intersection parts are compatible"); {
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") TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_top_properties")
@ -508,9 +551,35 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_top_properties")
local z : { p : string?, q : number? } = x -- Not OK local z : { p : string?, q : number? } = x -- Not OK
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); if (FFlag::DebugLuauDeferredConstraintResolution)
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"); 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?, "
"q: number? |}'; none of the intersection parts are compatible");
}
} }
TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_never_properties") TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_never_properties")
@ -537,9 +606,18 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_returning_intersections")
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ(toString(result.errors[0]), if (FFlag::DebugLuauDeferredConstraintResolution)
"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"); 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") 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); 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") 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); 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(); TEST_SUITE_END();

View file

@ -661,4 +661,32 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "loop_iter_metamethod_ok_with_inference")
CHECK(toString(requireType("b")) == "string"); 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(); TEST_SUITE_END();

View file

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

View file

@ -594,28 +594,6 @@ return wrapStrictTable(Constants, "Constants")
CHECK(get<AnyType>(*result)); 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 namespace
{ {
struct IsSubtypeFixture : Fixture 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(); 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}}}; 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) if (ctx.callSite->args.size != 1)
return {}; return {};
@ -54,7 +54,7 @@ std::vector<ConnectiveId> dcrMagicRefinementInstanceIsA(const MagicRefinementCon
if (!tfun) if (!tfun)
return {}; return {};
return {ctx.connectiveArena->proposition(*def, tfun->type)}; return {ctx.refinementArena->proposition(*def, tfun->type)};
} }
struct RefinementClassFixture : BuiltinsFixture struct RefinementClassFixture : BuiltinsFixture
@ -122,16 +122,8 @@ TEST_CASE_FIXTURE(Fixture, "is_truthy_constraint")
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution) CHECK_EQ("string", toString(requireTypeAtPosition({3, 26})));
{ CHECK_EQ("nil", toString(requireTypeAtPosition({5, 26})));
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") TEST_CASE_FIXTURE(Fixture, "invert_is_truthy_constraint")
@ -148,16 +140,8 @@ TEST_CASE_FIXTURE(Fixture, "invert_is_truthy_constraint")
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution) CHECK_EQ("nil", toString(requireTypeAtPosition({3, 26})));
{ CHECK_EQ("string", toString(requireTypeAtPosition({5, 26})));
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") TEST_CASE_FIXTURE(Fixture, "parenthesized_expressions_are_followed_through")
@ -174,16 +158,8 @@ TEST_CASE_FIXTURE(Fixture, "parenthesized_expressions_are_followed_through")
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution) CHECK_EQ("nil", toString(requireTypeAtPosition({3, 26})));
{ CHECK_EQ("string", toString(requireTypeAtPosition({5, 26})));
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") TEST_CASE_FIXTURE(Fixture, "and_constraint")
@ -202,16 +178,8 @@ TEST_CASE_FIXTURE(Fixture, "and_constraint")
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution) CHECK_EQ("string", toString(requireTypeAtPosition({3, 26})));
{ CHECK_EQ("number", toString(requireTypeAtPosition({4, 26})));
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("string?", toString(requireTypeAtPosition({6, 26})));
CHECK_EQ("number?", toString(requireTypeAtPosition({7, 26}))); CHECK_EQ("number?", toString(requireTypeAtPosition({7, 26})));
@ -236,16 +204,8 @@ TEST_CASE_FIXTURE(Fixture, "not_and_constraint")
CHECK_EQ("string?", toString(requireTypeAtPosition({3, 26}))); CHECK_EQ("string?", toString(requireTypeAtPosition({3, 26})));
CHECK_EQ("number?", toString(requireTypeAtPosition({4, 26}))); CHECK_EQ("number?", toString(requireTypeAtPosition({4, 26})));
if (FFlag::DebugLuauDeferredConstraintResolution) CHECK_EQ("string", toString(requireTypeAtPosition({6, 26})));
{ CHECK_EQ("number", toString(requireTypeAtPosition({7, 26})));
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") TEST_CASE_FIXTURE(Fixture, "or_predicate_with_truthy_predicates")
@ -267,16 +227,8 @@ TEST_CASE_FIXTURE(Fixture, "or_predicate_with_truthy_predicates")
CHECK_EQ("string?", toString(requireTypeAtPosition({3, 26}))); CHECK_EQ("string?", toString(requireTypeAtPosition({3, 26})));
CHECK_EQ("number?", toString(requireTypeAtPosition({4, 26}))); CHECK_EQ("number?", toString(requireTypeAtPosition({4, 26})));
if (FFlag::DebugLuauDeferredConstraintResolution) CHECK_EQ("nil", toString(requireTypeAtPosition({6, 26})));
{ CHECK_EQ("nil", toString(requireTypeAtPosition({7, 26})));
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") TEST_CASE_FIXTURE(Fixture, "a_and_b_or_a_and_c")
@ -297,26 +249,17 @@ TEST_CASE_FIXTURE(Fixture, "a_and_b_or_a_and_c")
LUAU_REQUIRE_NO_ERRORS(result); 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("string", toString(requireTypeAtPosition({3, 28})));
CHECK_EQ("number?", toString(requireTypeAtPosition({8, 28}))); CHECK_EQ("number?", toString(requireTypeAtPosition({4, 28})));
CHECK_EQ("boolean", toString(requireTypeAtPosition({9, 28}))); if (FFlag::DebugLuauDeferredConstraintResolution)
} CHECK_EQ("boolean", toString(requireTypeAtPosition({5, 28})));
else else
{
CHECK_EQ("string", toString(requireTypeAtPosition({3, 28})));
CHECK_EQ("number?", toString(requireTypeAtPosition({4, 28})));
CHECK_EQ("true", toString(requireTypeAtPosition({5, 28}))); // oh no! :( CHECK_EQ("true", toString(requireTypeAtPosition({5, 28}))); // oh no! :(
CHECK_EQ("string?", toString(requireTypeAtPosition({7, 28}))); CHECK_EQ("string?", toString(requireTypeAtPosition({7, 28})));
CHECK_EQ("number?", toString(requireTypeAtPosition({8, 28}))); CHECK_EQ("number?", toString(requireTypeAtPosition({8, 28})));
CHECK_EQ("boolean", toString(requireTypeAtPosition({9, 28}))); CHECK_EQ("boolean", toString(requireTypeAtPosition({9, 28})));
}
} }
TEST_CASE_FIXTURE(Fixture, "type_assertion_expr_carry_its_constraints") TEST_CASE_FIXTURE(Fixture, "type_assertion_expr_carry_its_constraints")
@ -357,14 +300,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_in_if_condition_position")
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution) CHECK_EQ("number", toString(requireTypeAtPosition({3, 26})));
{
CHECK_EQ("any & number", toString(requireTypeAtPosition({3, 26})));
}
else
{
CHECK_EQ("number", toString(requireTypeAtPosition({3, 26})));
}
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_in_assert_position") TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_in_assert_position")
@ -433,8 +369,8 @@ TEST_CASE_FIXTURE(Fixture, "truthy_constraint_on_properties")
if (FFlag::DebugLuauDeferredConstraintResolution) if (FFlag::DebugLuauDeferredConstraintResolution)
{ {
CHECK("{| x: number? |} & {| x: ~(false?) |}" == toString(requireTypeAtPosition({4, 23}))); CHECK("{| x: number |}" == toString(requireTypeAtPosition({4, 23})));
CHECK("(number?) & ~(false?)" == toString(requireTypeAtPosition({5, 26}))); CHECK("number" == toString(requireTypeAtPosition({5, 26})));
} }
CHECK_EQ("number?", toString(requireType("bar"))); CHECK_EQ("number?", toString(requireType("bar")));
@ -478,22 +414,11 @@ TEST_CASE_FIXTURE(Fixture, "lvalue_is_equal_to_another_lvalue")
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution) CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "(number | string)?"); // a == b
{ CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "boolean?"); // a == b
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, 33})), "(number | string)?"); // a ~= b
CHECK_EQ(toString(requireTypeAtPosition({5, 36})), "(boolean?) & unknown"); // a ~= b CHECK_EQ(toString(requireTypeAtPosition({5, 36})), "boolean?"); // 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") TEST_CASE_FIXTURE(Fixture, "lvalue_is_equal_to_a_term")
@ -510,16 +435,8 @@ TEST_CASE_FIXTURE(Fixture, "lvalue_is_equal_to_a_term")
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution) CHECK_EQ(toString(requireTypeAtPosition({3, 28})), "(number | string)?"); // a == 1;
{ CHECK_EQ(toString(requireTypeAtPosition({5, 28})), "(number | string)?"); // a ~= 1
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") 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) if (FFlag::DebugLuauDeferredConstraintResolution)
{ {
CHECK_EQ(toString(requireTypeAtPosition({3, 28})), R"("hello" & ((number | string)?))"); // a == "hello" CHECK_EQ(toString(requireTypeAtPosition({3, 28})), R"("hello")"); // a == "hello"
CHECK_EQ(toString(requireTypeAtPosition({5, 28})), R"(((number | string)?) & ~"hello")"); // a ~= "hello" CHECK_EQ(toString(requireTypeAtPosition({5, 28})), R"(((string & ~"hello") | number)?)"); // a ~= "hello"
} }
else else
{ {
@ -562,16 +479,8 @@ TEST_CASE_FIXTURE(Fixture, "lvalue_is_not_nil")
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution) CHECK_EQ(toString(requireTypeAtPosition({3, 28})), "number | string"); // a ~= nil
{ CHECK_EQ(toString(requireTypeAtPosition({5, 28})), "(number | string)?"); // a == nil
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") TEST_CASE_FIXTURE(Fixture, "free_type_is_equal_to_an_lvalue")
@ -586,17 +495,8 @@ TEST_CASE_FIXTURE(Fixture, "free_type_is_equal_to_an_lvalue")
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution) CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "a"); // a == b
{ CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "string?"); // a == b
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") TEST_CASE_FIXTURE(Fixture, "unknown_lvalue_is_not_synonymous_with_other_on_not_equal")
@ -611,16 +511,8 @@ TEST_CASE_FIXTURE(Fixture, "unknown_lvalue_is_not_synonymous_with_other_on_not_e
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution) CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "any"); // a ~= b
{ CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "{| x: number |}?"); // a ~= b
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") TEST_CASE_FIXTURE(Fixture, "string_not_equal_to_string_or_nil")
@ -639,22 +531,11 @@ TEST_CASE_FIXTURE(Fixture, "string_not_equal_to_string_or_nil")
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution) CHECK_EQ(toString(requireTypeAtPosition({6, 29})), "string"); // a ~= b
{ CHECK_EQ(toString(requireTypeAtPosition({6, 32})), "string?"); // a ~= b
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, 29})), "string"); // a == b
CHECK_EQ(toString(requireTypeAtPosition({8, 32})), "(string?) & unknown"); // a == b CHECK_EQ(toString(requireTypeAtPosition({8, 32})), "string?"); // 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") TEST_CASE_FIXTURE(Fixture, "narrow_property_of_a_bounded_variable")
@ -729,16 +610,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_not_to_be_string")
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution) CHECK_EQ("boolean | number", toString(requireTypeAtPosition({3, 28}))); // type(x) ~= "string"
{ CHECK_EQ("string", toString(requireTypeAtPosition({5, 28}))); // type(x) == "string"
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") TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_narrows_for_table")
@ -773,16 +646,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_narrows_for_functions")
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution) CHECK_EQ("(number) -> string", toString(requireTypeAtPosition({3, 28}))); // type(x) == "function"
{ CHECK_EQ("string", toString(requireTypeAtPosition({5, 28}))); // type(x) ~= "function"
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") TEST_CASE_FIXTURE(BuiltinsFixture, "type_guard_can_filter_for_intersection_of_tables")
@ -821,16 +686,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_guard_can_filter_for_overloaded_functio
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution) CHECK_EQ("((number) -> string) & ((string) -> number)", toString(requireTypeAtPosition({4, 28})));
{ CHECK_EQ("nil", toString(requireTypeAtPosition({6, 28})));
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") TEST_CASE_FIXTURE(BuiltinsFixture, "type_guard_narrowed_into_nothingness")
@ -898,16 +755,8 @@ TEST_CASE_FIXTURE(Fixture, "not_a_and_not_b")
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution) CHECK_EQ("nil", toString(requireTypeAtPosition({3, 28})));
{ CHECK_EQ("nil", toString(requireTypeAtPosition({4, 28})));
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") TEST_CASE_FIXTURE(Fixture, "not_a_and_not_b2")
@ -923,16 +772,8 @@ TEST_CASE_FIXTURE(Fixture, "not_a_and_not_b2")
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution) CHECK_EQ("nil", toString(requireTypeAtPosition({3, 28})));
{ CHECK_EQ("nil", toString(requireTypeAtPosition({4, 28})));
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") TEST_CASE_FIXTURE(BuiltinsFixture, "either_number_or_string")
@ -947,14 +788,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "either_number_or_string")
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution) CHECK_EQ("number | string", toString(requireTypeAtPosition({3, 28})));
{
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") TEST_CASE_FIXTURE(Fixture, "not_t_or_some_prop_of_t")
@ -984,16 +818,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "assert_a_to_be_truthy_then_assert_a_to_be_nu
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution) CHECK_EQ("number | string", toString(requireTypeAtPosition({3, 18})));
{ CHECK_EQ("number", toString(requireTypeAtPosition({5, 18})));
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") TEST_CASE_FIXTURE(BuiltinsFixture, "merge_should_be_fully_agnostic_of_hashmap_ordering")
@ -1012,14 +838,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "merge_should_be_fully_agnostic_of_hashmap_or
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution) CHECK_EQ("string", toString(requireTypeAtPosition({6, 28})));
{
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") TEST_CASE_FIXTURE(BuiltinsFixture, "refine_the_correct_types_opposite_of_when_a_is_not_number_or_string")
@ -1036,16 +855,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "refine_the_correct_types_opposite_of_when_a_
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution) CHECK_EQ("boolean", toString(requireTypeAtPosition({3, 28})));
{ CHECK_EQ("number | string", toString(requireTypeAtPosition({5, 28})));
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") TEST_CASE_FIXTURE(BuiltinsFixture, "is_truthy_constraint_ifelse_expression")
@ -1058,16 +869,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "is_truthy_constraint_ifelse_expression")
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution) CHECK_EQ("string", toString(requireTypeAtPosition({2, 29})));
{ CHECK_EQ("nil", toString(requireTypeAtPosition({2, 45})));
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") TEST_CASE_FIXTURE(BuiltinsFixture, "invert_is_truthy_constraint_ifelse_expression")
@ -1080,16 +883,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "invert_is_truthy_constraint_ifelse_expressio
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution) CHECK_EQ("nil", toString(requireTypeAtPosition({2, 42})));
{ CHECK_EQ("string", toString(requireTypeAtPosition({2, 50})));
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") TEST_CASE_FIXTURE(BuiltinsFixture, "type_comparison_ifelse_expression")
@ -1106,16 +901,11 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_comparison_ifelse_expression")
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ("number", toString(requireTypeAtPosition({6, 49})));
if (FFlag::DebugLuauDeferredConstraintResolution) if (FFlag::DebugLuauDeferredConstraintResolution)
{ CHECK_EQ("~number", toString(requireTypeAtPosition({6, 66})));
CHECK_EQ("any & number", toString(requireTypeAtPosition({6, 49})));
CHECK_EQ("any & ~number", toString(requireTypeAtPosition({6, 66})));
}
else else
{
CHECK_EQ("number", toString(requireTypeAtPosition({6, 49})));
CHECK_EQ("any", toString(requireTypeAtPosition({6, 66}))); CHECK_EQ("any", toString(requireTypeAtPosition({6, 66})));
}
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "correctly_lookup_a_shadowed_local_that_which_was_previously_refined") TEST_CASE_FIXTURE(BuiltinsFixture, "correctly_lookup_a_shadowed_local_that_which_was_previously_refined")
@ -1196,17 +986,11 @@ TEST_CASE_FIXTURE(Fixture, "discriminate_from_truthiness_of_x")
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ(R"({| tag: "exists", x: string |})", toString(requireTypeAtPosition({5, 28})));
if (FFlag::DebugLuauDeferredConstraintResolution) if (FFlag::DebugLuauDeferredConstraintResolution)
{ CHECK_EQ(R"({| tag: "missing", x: nil |})", toString(requireTypeAtPosition({7, 28})));
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 else
{
CHECK_EQ(R"({| tag: "exists", x: string |})", toString(requireTypeAtPosition({5, 28})));
CHECK_EQ(R"({| tag: "exists", x: string |} | {| tag: "missing", x: nil |})", toString(requireTypeAtPosition({7, 28}))); CHECK_EQ(R"({| tag: "exists", x: string |} | {| tag: "missing", x: nil |})", toString(requireTypeAtPosition({7, 28})));
}
} }
TEST_CASE_FIXTURE(Fixture, "discriminate_tag") TEST_CASE_FIXTURE(Fixture, "discriminate_tag")
@ -1229,8 +1013,8 @@ TEST_CASE_FIXTURE(Fixture, "discriminate_tag")
if (FFlag::DebugLuauDeferredConstraintResolution) if (FFlag::DebugLuauDeferredConstraintResolution)
{ {
CHECK_EQ(R"((Cat | Dog) & {| tag: "Cat" |})", toString(requireTypeAtPosition({7, 33}))); CHECK_EQ(R"({| catfood: string, name: string, tag: "Cat" |})", toString(requireTypeAtPosition({7, 33})));
CHECK_EQ(R"((Cat | Dog) & {| tag: ~"Cat" |} & {| tag: "Dog" |})", toString(requireTypeAtPosition({9, 33}))); CHECK_EQ(R"({| dogfood: string, name: string, tag: "Dog" |})", toString(requireTypeAtPosition({9, 33})));
} }
else else
{ {
@ -1259,8 +1043,8 @@ TEST_CASE_FIXTURE(Fixture, "discriminate_tag_with_implicit_else")
if (FFlag::DebugLuauDeferredConstraintResolution) if (FFlag::DebugLuauDeferredConstraintResolution)
{ {
CHECK_EQ(R"((Cat | Dog) & {| tag: "Cat" |})", toString(requireTypeAtPosition({7, 33}))); CHECK_EQ(R"({| catfood: string, name: string, tag: "Cat" |})", toString(requireTypeAtPosition({7, 33})));
CHECK_EQ(R"((Cat | Dog) & {| tag: ~"Cat" |})", toString(requireTypeAtPosition({9, 33}))); CHECK_EQ(R"({| dogfood: string, name: string, tag: "Dog" |})", toString(requireTypeAtPosition({9, 33})));
} }
else else
{ {
@ -1294,16 +1078,8 @@ TEST_CASE_FIXTURE(Fixture, "narrow_boolean_to_true_or_false")
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution) CHECK_EQ("true", toString(requireTypeAtPosition({3, 28})));
{ CHECK_EQ("false", toString(requireTypeAtPosition({5, 28})));
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") TEST_CASE_FIXTURE(Fixture, "discriminate_on_properties_of_disjoint_tables_where_that_property_is_true_or_false")
@ -1355,16 +1131,8 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "discriminate_from_isa_of_x")
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution) CHECK_EQ(R"({| tag: "Part", x: Part |})", toString(requireTypeAtPosition({5, 28})));
{ CHECK_EQ(R"({| tag: "Folder", x: Folder |})", toString(requireTypeAtPosition({7, 28})));
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") TEST_CASE_FIXTURE(RefinementClassFixture, "typeguard_cast_free_table_to_vector")
@ -1406,16 +1174,8 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "typeguard_cast_instance_or_vector3_to
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution) CHECK_EQ("Vector3", toString(requireTypeAtPosition({3, 28})));
{ CHECK_EQ("Instance", toString(requireTypeAtPosition({5, 28})));
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") 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) if (FFlag::DebugLuauDeferredConstraintResolution)
{ {
CHECK_EQ("(Instance | Vector3 | number | string) & never", toString(requireTypeAtPosition({3, 28}))); CHECK_EQ("never", toString(requireTypeAtPosition({3, 28})));
CHECK_EQ("(Instance | Vector3 | number | string) & ~never", toString(requireTypeAtPosition({5, 28}))); CHECK_EQ("Instance | Vector3 | number | string", toString(requireTypeAtPosition({5, 28})));
} }
else else
{ {
@ -1476,16 +1236,8 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "eliminate_subclasses_of_instance")
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution) CHECK_EQ("Folder | Part", toString(requireTypeAtPosition({3, 28})));
{ CHECK_EQ("string", toString(requireTypeAtPosition({5, 28})));
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") TEST_CASE_FIXTURE(RefinementClassFixture, "narrow_from_subclasses_of_instance_or_string_or_vector3")
@ -1502,16 +1254,8 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "narrow_from_subclasses_of_instance_or
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution) CHECK_EQ("Folder | Part", toString(requireTypeAtPosition({3, 28})));
{ CHECK_EQ("Vector3 | string", toString(requireTypeAtPosition({5, 28})));
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") TEST_CASE_FIXTURE(RefinementClassFixture, "x_as_any_if_x_is_instance_elseif_x_is_table")
@ -1556,16 +1300,8 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "refine_param_of_type_instance_without
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution) CHECK_EQ("Folder", toString(requireTypeAtPosition({3, 28})));
{ CHECK_EQ("never", toString(requireTypeAtPosition({5, 28})));
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") TEST_CASE_FIXTURE(RefinementClassFixture, "refine_param_of_type_folder_or_part_without_using_typeof")
@ -1582,16 +1318,8 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "refine_param_of_type_folder_or_part_w
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution) CHECK_EQ("Folder", toString(requireTypeAtPosition({3, 28})));
{ CHECK_EQ("Part", toString(requireTypeAtPosition({5, 28})));
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") TEST_CASE_FIXTURE(RefinementClassFixture, "isa_type_refinement_must_be_known_ahead_of_time")
@ -1610,16 +1338,8 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "isa_type_refinement_must_be_known_ahe
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution) CHECK_EQ("Instance", toString(requireTypeAtPosition({3, 28})));
{ CHECK_EQ("Instance", toString(requireTypeAtPosition({5, 28})));
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") 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) if (FFlag::DebugLuauDeferredConstraintResolution)
{ {
CHECK_EQ("unknown & string", toString(requireTypeAtPosition({3, 28}))); CHECK_EQ("string", toString(requireTypeAtPosition({3, 28})));
CHECK_EQ("unknown & ~string", toString(requireTypeAtPosition({5, 28}))); CHECK_EQ("~string", toString(requireTypeAtPosition({5, 28})));
} }
else else
{ {
@ -1714,14 +1434,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "what_nonsensical_condition")
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution) CHECK_EQ("never", toString(requireTypeAtPosition({3, 28})));
{
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") TEST_CASE_FIXTURE(Fixture, "else_with_no_explicit_expression_should_also_refine_the_tagged_union")
@ -1752,7 +1465,30 @@ local _ = _ ~= _ or _ or _
end 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); LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ("table", toString(requireTypeAtPosition({3, 29})));
} }
TEST_SUITE_END(); TEST_SUITE_END();

View file

@ -3435,4 +3435,62 @@ _ = _._
LUAU_REQUIRE_ERRORS(result); 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(); TEST_SUITE_END();

View file

@ -1014,7 +1014,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_packs_with_tails_in_vararg_adjustment")
end) end)
return result return result
end end
end end
)"); )");
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);

View file

@ -116,11 +116,23 @@ TEST_CASE_FIXTURE(Fixture, "type_packs_containing_never_is_itself_uninhabitable"
local x, y, z = f() local x, y, z = f()
)"); )");
LUAU_REQUIRE_NO_ERRORS(result); 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("never", toString(requireType("x"))); CHECK_EQ("string", toString(requireType("x")));
CHECK_EQ("never", toString(requireType("y"))); CHECK_EQ("never", toString(requireType("y")));
CHECK_EQ("never", toString(requireType("z"))); 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") TEST_CASE_FIXTURE(Fixture, "type_packs_containing_never_is_itself_uninhabitable2")
@ -135,10 +147,20 @@ TEST_CASE_FIXTURE(Fixture, "type_packs_containing_never_is_itself_uninhabitable2
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ("never", toString(requireType("x1"))); if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ("never", toString(requireType("x2"))); {
CHECK_EQ("never", toString(requireType("y1"))); CHECK_EQ("string", toString(requireType("x1")));
CHECK_EQ("never", toString(requireType("y2"))); 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") TEST_CASE_FIXTURE(Fixture, "index_on_never")
@ -290,8 +312,14 @@ TEST_CASE_FIXTURE(Fixture, "dont_unify_operands_if_one_of_the_operand_is_never_i
)"); )");
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
// Widening doesn't normalize yet, so the result is a bit strange
CHECK_EQ("<a>(nil, a) -> boolean | boolean", toString(requireType("ord"))); 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") 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)); 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") SUBCASE("fresh_type_and_string")
{ {
TypeId freshTy = arena.freshType(nullptr); TypeId freshTy = arena.freshType(nullptr);
@ -690,7 +708,7 @@ TEST_CASE_FIXTURE(ReductionFixture, "intersections_with_negations")
SUBCASE("string_and_not_error") SUBCASE("string_and_not_error")
{ {
TypeId ty = reductionof("string & Not<err>"); 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") 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)); 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") SUBCASE("not_top_table_and_table")
{ {
TypeId ty = reductionof("Not<tbl> & {}"); TypeId ty = reductionof("Not<tbl> & {}");
@ -1251,6 +1275,12 @@ TEST_CASE_FIXTURE(ReductionFixture, "tables")
TypeId ty = reductionof("{ x: { y: string & number } }"); TypeId ty = reductionof("{ x: { y: string & number } }");
CHECK("never" == toStringFull(ty)); CHECK("never" == toStringFull(ty));
} }
SUBCASE("array_of_never")
{
TypeId ty = reductionof("{never}");
CHECK("{never}" == toStringFull(ty));
}
} }
TEST_CASE_FIXTURE(ReductionFixture, "metatables") TEST_CASE_FIXTURE(ReductionFixture, "metatables")

View file

@ -1,6 +1,5 @@
AnnotationTests.corecursive_types_error_on_tight_loop AnnotationTests.corecursive_types_error_on_tight_loop
AnnotationTests.duplicate_type_param_name AnnotationTests.duplicate_type_param_name
AnnotationTests.for_loop_counter_annotation_is_checked
AnnotationTests.generic_aliases_are_cloned_properly AnnotationTests.generic_aliases_are_cloned_properly
AnnotationTests.occurs_check_on_cyclic_intersection_type AnnotationTests.occurs_check_on_cyclic_intersection_type
AnnotationTests.occurs_check_on_cyclic_union_type AnnotationTests.occurs_check_on_cyclic_union_type
@ -18,12 +17,8 @@ AutocompleteTest.keyword_methods
AutocompleteTest.no_incompatible_self_calls AutocompleteTest.no_incompatible_self_calls
AutocompleteTest.no_wrong_compatible_self_calls_with_generics AutocompleteTest.no_wrong_compatible_self_calls_with_generics
AutocompleteTest.string_singleton_as_table_key AutocompleteTest.string_singleton_as_table_key
AutocompleteTest.suggest_external_module_type
AutocompleteTest.suggest_table_keys AutocompleteTest.suggest_table_keys
AutocompleteTest.type_correct_argument_type_suggestion
AutocompleteTest.type_correct_expected_argument_type_pack_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_argument_type_suggestion_self
AutocompleteTest.type_correct_expected_return_type_pack_suggestion AutocompleteTest.type_correct_expected_return_type_pack_suggestion
AutocompleteTest.type_correct_expected_return_type_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 ParserTests.parse_nesting_based_end_detection_local_function
ProvisionalTests.assign_table_with_refined_property_with_a_similar_type_is_illegal ProvisionalTests.assign_table_with_refined_property_with_a_similar_type_is_illegal
ProvisionalTests.bail_early_if_unification_is_too_complicated 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.do_not_ice_when_trying_to_pick_first_of_generic_type_pack
ProvisionalTests.error_on_eq_metamethod_returning_a_type_other_than_boolean ProvisionalTests.error_on_eq_metamethod_returning_a_type_other_than_boolean
ProvisionalTests.free_options_cannot_be_unified_together ProvisionalTests.free_options_cannot_be_unified_together
ProvisionalTests.generic_type_leak_to_module_interface_variadic ProvisionalTests.generic_type_leak_to_module_interface_variadic
ProvisionalTests.greedy_inference_with_shared_self_triggers_function_with_no_returns 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.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.setmetatable_constrains_free_type_into_free_table
ProvisionalTests.specialization_binds_with_prototypes_too_early ProvisionalTests.specialization_binds_with_prototypes_too_early
ProvisionalTests.table_insert_with_a_singleton_argument ProvisionalTests.table_insert_with_a_singleton_argument
ProvisionalTests.typeguard_inference_incomplete ProvisionalTests.typeguard_inference_incomplete
ProvisionalTests.weirditer_should_not_loop_forever ProvisionalTests.weirditer_should_not_loop_forever
RefinementTest.apply_refinements_on_astexprindexexpr_whose_subscript_expr_is_constant_string 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.else_with_no_explicit_expression_should_also_refine_the_tagged_union
RefinementTest.falsiness_of_TruthyPredicate_narrows_into_nil RefinementTest.falsiness_of_TruthyPredicate_narrows_into_nil
RefinementTest.narrow_property_of_a_bounded_variable RefinementTest.narrow_property_of_a_bounded_variable
RefinementTest.nonoptional_type_can_narrow_to_nil_if_sense_is_true 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_can_filter_for_intersection_of_tables
RefinementTest.type_guard_narrowed_into_nothingness
RefinementTest.type_narrow_for_all_the_userdata RefinementTest.type_narrow_for_all_the_userdata
RefinementTest.type_narrow_to_vector RefinementTest.type_narrow_to_vector
RefinementTest.typeguard_cast_free_table_to_vector RefinementTest.typeguard_cast_free_table_to_vector
RefinementTest.typeguard_in_assert_position RefinementTest.typeguard_in_assert_position
RefinementTest.typeguard_narrows_for_table
RefinementTest.x_as_any_if_x_is_instance_elseif_x_is_table RefinementTest.x_as_any_if_x_is_instance_elseif_x_is_table
RefinementTest.x_is_not_instance_or_else_not_part RefinementTest.x_is_not_instance_or_else_not_part
RuntimeLimits.typescript_port_of_Result_type 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_like_key_in_table_property_access
TableTests.found_multiple_like_keys TableTests.found_multiple_like_keys
TableTests.function_calls_produces_sealed_table_given_unsealed_table 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.generic_table_instantiation_potential_regression
TableTests.give_up_after_one_metatable_index_look_up TableTests.give_up_after_one_metatable_index_look_up
TableTests.indexer_on_sealed_table_must_unify_with_free_table 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_simple_call
TableTests.table_subtyping_with_extra_props_dont_report_multiple_errors TableTests.table_subtyping_with_extra_props_dont_report_multiple_errors
TableTests.table_subtyping_with_missing_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
TableTests.tc_member_function_2 TableTests.tc_member_function_2
TableTests.unification_of_unions_in_a_self_referential_type
TableTests.unifying_tables_shouldnt_uaf1 TableTests.unifying_tables_shouldnt_uaf1
TableTests.unifying_tables_shouldnt_uaf2 TableTests.unifying_tables_shouldnt_uaf2
TableTests.used_colon_correctly 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.in_nonstrict_mode_strip_nil_from_intersections_when_considering_relational_operators
TypeInferOperators.infer_any_in_all_modes_when_lhs_is_unknown TypeInferOperators.infer_any_in_all_modes_when_lhs_is_unknown
TypeInferOperators.operator_eq_completely_incompatible 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.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
TypeInferOperators.typecheck_overloaded_multiply_that_is_an_intersection_on_rhs TypeInferOperators.typecheck_overloaded_multiply_that_is_an_intersection_on_rhs
TypeInferOperators.UnknownGlobalCompoundAssign TypeInferOperators.UnknownGlobalCompoundAssign
@ -368,16 +354,8 @@ TypeInferOperators.unrelated_primitives_cannot_be_compared
TypeInferPrimitives.CheckMethodsOfNumber TypeInferPrimitives.CheckMethodsOfNumber
TypeInferPrimitives.string_index TypeInferPrimitives.string_index
TypeInferUnknownNever.assign_to_global_which_is_never 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.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.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.detect_cyclic_typepacks2
TypePackTests.pack_tail_unification_check TypePackTests.pack_tail_unification_check
TypePackTests.self_and_varargs_should_work 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
TypePackTests.unify_variadic_tails_in_arguments_free TypePackTests.unify_variadic_tails_in_arguments_free
TypePackTests.variadic_packs TypePackTests.variadic_packs
TypeReductionTests.negations
TypeSingletons.function_call_with_singletons TypeSingletons.function_call_with_singletons
TypeSingletons.function_call_with_singletons_mismatch TypeSingletons.function_call_with_singletons_mismatch
TypeSingletons.indexing_on_string_singletons
TypeSingletons.indexing_on_union_of_string_singletons TypeSingletons.indexing_on_union_of_string_singletons
TypeSingletons.overloaded_function_call_with_singletons TypeSingletons.overloaded_function_call_with_singletons
TypeSingletons.overloaded_function_call_with_singletons_mismatch TypeSingletons.overloaded_function_call_with_singletons_mismatch
TypeSingletons.return_type_of_f_is_not_widened TypeSingletons.return_type_of_f_is_not_widened
TypeSingletons.table_properties_singleton_strings_mismatch TypeSingletons.table_properties_singleton_strings_mismatch
TypeSingletons.table_properties_type_error_escapes TypeSingletons.table_properties_type_error_escapes
TypeSingletons.taking_the_length_of_string_singleton
TypeSingletons.taking_the_length_of_union_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.widen_the_supertype_if_it_is_free_and_subtype_has_singleton
TypeSingletons.widening_happens_almost_everywhere TypeSingletons.widening_happens_almost_everywhere
TypeSingletons.widening_happens_almost_everywhere_except_for_tables TypeSingletons.widening_happens_almost_everywhere_except_for_tables
UnionTypes.index_on_a_union_type_with_missing_property 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_assignment_errors
UnionTypes.optional_call_error UnionTypes.optional_call_error
UnionTypes.optional_field_access_error UnionTypes.optional_field_access_error