mirror of
https://github.com/luau-lang/luau.git
synced 2024-11-15 06:15:44 +08:00
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:
parent
f763f4c948
commit
62483d40f0
@ -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
|
@ -71,9 +71,9 @@ struct BinaryConstraint
|
||||
|
||||
// When we dispatch this constraint, we update the key at this map to record
|
||||
// the overload that we selected.
|
||||
const void* astFragment;
|
||||
DenseHashMap<const void*, TypeId>* astOriginalCallTypes;
|
||||
DenseHashMap<const void*, TypeId>* astOverloadResolvedTypes;
|
||||
const AstNode* astFragment;
|
||||
DenseHashMap<const AstNode*, TypeId>* astOriginalCallTypes;
|
||||
DenseHashMap<const AstNode*, TypeId>* astOverloadResolvedTypes;
|
||||
};
|
||||
|
||||
// iteratee is iterable
|
||||
|
@ -2,7 +2,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Ast.h"
|
||||
#include "Luau/Connective.h"
|
||||
#include "Luau/Refinement.h"
|
||||
#include "Luau/Constraint.h"
|
||||
#include "Luau/DataFlowGraph.h"
|
||||
#include "Luau/Module.h"
|
||||
@ -27,13 +27,13 @@ struct DcrLogger;
|
||||
struct Inference
|
||||
{
|
||||
TypeId ty = nullptr;
|
||||
ConnectiveId connective = nullptr;
|
||||
RefinementId refinement = nullptr;
|
||||
|
||||
Inference() = default;
|
||||
|
||||
explicit Inference(TypeId ty, ConnectiveId connective = nullptr)
|
||||
explicit Inference(TypeId ty, RefinementId refinement = nullptr)
|
||||
: ty(ty)
|
||||
, connective(connective)
|
||||
, refinement(refinement)
|
||||
{
|
||||
}
|
||||
};
|
||||
@ -41,13 +41,13 @@ struct Inference
|
||||
struct InferencePack
|
||||
{
|
||||
TypePackId tp = nullptr;
|
||||
std::vector<ConnectiveId> connectives;
|
||||
std::vector<RefinementId> refinements;
|
||||
|
||||
InferencePack() = default;
|
||||
|
||||
explicit InferencePack(TypePackId tp, const std::vector<ConnectiveId>& connectives = {})
|
||||
explicit InferencePack(TypePackId tp, const std::vector<RefinementId>& refinements = {})
|
||||
: tp(tp)
|
||||
, connectives(connectives)
|
||||
, refinements(refinements)
|
||||
{
|
||||
}
|
||||
};
|
||||
@ -74,35 +74,11 @@ struct ConstraintGraphBuilder
|
||||
// will enqueue them during solving.
|
||||
std::vector<ConstraintPtr> unqueuedConstraints;
|
||||
|
||||
// A mapping of AST node to TypeId.
|
||||
DenseHashMap<const AstExpr*, TypeId> astTypes{nullptr};
|
||||
|
||||
// A mapping of AST node to TypePackId.
|
||||
DenseHashMap<const AstExpr*, TypePackId> astTypePacks{nullptr};
|
||||
|
||||
DenseHashMap<const AstExpr*, TypeId> astExpectedTypes{nullptr};
|
||||
|
||||
// If the node was applied as a function, this is the unspecialized type of
|
||||
// that expression.
|
||||
DenseHashMap<const void*, TypeId> astOriginalCallTypes{nullptr};
|
||||
|
||||
// If overload resolution was performed on this element, this is the
|
||||
// overload that was selected.
|
||||
DenseHashMap<const void*, TypeId> astOverloadResolvedTypes{nullptr};
|
||||
|
||||
|
||||
|
||||
// Types resolved from type annotations. Analogous to astTypes.
|
||||
DenseHashMap<const AstType*, TypeId> astResolvedTypes{nullptr};
|
||||
|
||||
// Type packs resolved from type annotations. Analogous to astTypePacks.
|
||||
DenseHashMap<const AstTypePack*, TypePackId> astResolvedTypePacks{nullptr};
|
||||
|
||||
// Defining scopes for AST nodes.
|
||||
// The private scope of type aliases for which the type parameters belong to.
|
||||
DenseHashMap<const AstStatTypeAlias*, ScopePtr> astTypeAliasDefiningScopes{nullptr};
|
||||
|
||||
NotNull<const DataFlowGraph> dfg;
|
||||
ConnectiveArena connectiveArena;
|
||||
RefinementArena refinementArena;
|
||||
|
||||
int recursionCount = 0;
|
||||
|
||||
@ -156,7 +132,7 @@ struct ConstraintGraphBuilder
|
||||
*/
|
||||
NotNull<Constraint> addConstraint(const ScopePtr& scope, std::unique_ptr<Constraint> c);
|
||||
|
||||
void applyRefinements(const ScopePtr& scope, Location location, ConnectiveId connective);
|
||||
void applyRefinements(const ScopePtr& scope, Location location, RefinementId refinement);
|
||||
|
||||
/**
|
||||
* The entry point to the ConstraintGraphBuilder. This will construct a set
|
||||
@ -213,7 +189,7 @@ struct ConstraintGraphBuilder
|
||||
Inference check(const ScopePtr& scope, AstExprTypeAssertion* typeAssert);
|
||||
Inference check(const ScopePtr& scope, AstExprInterpString* interpString);
|
||||
Inference check(const ScopePtr& scope, AstExprTable* expr, std::optional<TypeId> expectedType);
|
||||
std::tuple<TypeId, TypeId, ConnectiveId> checkBinary(const ScopePtr& scope, AstExprBinary* binary, std::optional<TypeId> expectedType);
|
||||
std::tuple<TypeId, TypeId, RefinementId> checkBinary(const ScopePtr& scope, AstExprBinary* binary, std::optional<TypeId> expectedType);
|
||||
|
||||
TypePackId checkLValues(const ScopePtr& scope, AstArray<AstExpr*> exprs);
|
||||
|
||||
|
@ -74,14 +74,13 @@ struct Module
|
||||
DenseHashMap<const AstExpr*, TypePackId> astTypePacks{nullptr};
|
||||
DenseHashMap<const AstExpr*, TypeId> astExpectedTypes{nullptr};
|
||||
|
||||
// Pointers are either AstExpr or AstStat.
|
||||
DenseHashMap<const void*, TypeId> astOriginalCallTypes{nullptr};
|
||||
|
||||
// Pointers are either AstExpr or AstStat.
|
||||
DenseHashMap<const void*, TypeId> astOverloadResolvedTypes{nullptr};
|
||||
DenseHashMap<const AstNode*, TypeId> astOriginalCallTypes{nullptr};
|
||||
DenseHashMap<const AstNode*, TypeId> astOverloadResolvedTypes{nullptr};
|
||||
|
||||
DenseHashMap<const AstType*, TypeId> astResolvedTypes{nullptr};
|
||||
DenseHashMap<const AstType*, TypeId> astOriginalResolvedTypes{nullptr};
|
||||
DenseHashMap<const AstTypePack*, TypePackId> astResolvedTypePacks{nullptr};
|
||||
|
||||
// Map AST nodes to the scope they create. Cannot be NotNull<Scope> because we need a sentinel value for the map.
|
||||
DenseHashMap<const AstNode*, Scope*> astScopes{nullptr};
|
||||
|
||||
|
68
Analysis/include/Luau/Refinement.h
Normal file
68
Analysis/include/Luau/Refinement.h
Normal 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
|
@ -3,7 +3,7 @@
|
||||
|
||||
#include "Luau/Ast.h"
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/Connective.h"
|
||||
#include "Luau/Refinement.h"
|
||||
#include "Luau/DataFlowGraph.h"
|
||||
#include "Luau/DenseHash.h"
|
||||
#include "Luau/Def.h"
|
||||
@ -266,12 +266,12 @@ struct MagicRefinementContext
|
||||
ScopePtr scope;
|
||||
NotNull<struct ConstraintGraphBuilder> cgb;
|
||||
NotNull<const DataFlowGraph> dfg;
|
||||
NotNull<ConnectiveArena> connectiveArena;
|
||||
std::vector<ConnectiveId> argumentConnectives;
|
||||
NotNull<RefinementArena> refinementArena;
|
||||
std::vector<RefinementId> argumentRefinements;
|
||||
const class AstExprCall* callSite;
|
||||
};
|
||||
|
||||
using DcrMagicRefinement = std::vector<ConnectiveId> (*)(const MagicRefinementContext&);
|
||||
using DcrMagicRefinement = std::vector<RefinementId> (*)(const MagicRefinementContext&);
|
||||
|
||||
struct FunctionType
|
||||
{
|
||||
|
@ -32,11 +32,23 @@ struct TypeReduction
|
||||
explicit TypeReduction(
|
||||
NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, NotNull<InternalErrorReporter> handle, const TypeReductionOptions& opts = {});
|
||||
|
||||
TypeReduction(const TypeReduction&) = delete;
|
||||
TypeReduction& operator=(const TypeReduction&) = delete;
|
||||
|
||||
TypeReduction(TypeReduction&&) = default;
|
||||
TypeReduction& operator=(TypeReduction&&) = default;
|
||||
|
||||
std::optional<TypeId> reduce(TypeId ty);
|
||||
std::optional<TypePackId> reduce(TypePackId tp);
|
||||
std::optional<TypeFun> reduce(const TypeFun& fun);
|
||||
|
||||
/// Creating a child TypeReduction will allow the parent TypeReduction to share its memoization with the child TypeReductions.
|
||||
/// This is safe as long as the parent's TypeArena continues to outlive both TypeReduction memoization.
|
||||
TypeReduction fork(NotNull<TypeArena> arena, const TypeReductionOptions& opts = {}) const;
|
||||
|
||||
private:
|
||||
const TypeReduction* parent = nullptr;
|
||||
|
||||
NotNull<TypeArena> arena;
|
||||
NotNull<BuiltinTypes> builtinTypes;
|
||||
NotNull<struct InternalErrorReporter> handle;
|
||||
@ -50,6 +62,9 @@ private:
|
||||
|
||||
bool hasExceededCartesianProductLimit(TypeId ty) const;
|
||||
bool hasExceededCartesianProductLimit(TypePackId tp) const;
|
||||
|
||||
std::optional<TypeId> memoizedof(TypeId ty) const;
|
||||
std::optional<TypePackId> memoizedof(TypePackId tp) const;
|
||||
};
|
||||
|
||||
} // namespace Luau
|
||||
|
@ -95,8 +95,7 @@ private:
|
||||
void tryUnifyScalarShape(TypeId subTy, TypeId superTy, bool reversed);
|
||||
void tryUnifyWithMetatable(TypeId subTy, TypeId superTy, bool reversed);
|
||||
void tryUnifyWithClass(TypeId subTy, TypeId superTy, bool reversed);
|
||||
void tryUnifyTypeWithNegation(TypeId subTy, TypeId superTy);
|
||||
void tryUnifyNegationWithType(TypeId subTy, TypeId superTy);
|
||||
void tryUnifyNegations(TypeId subTy, TypeId superTy);
|
||||
|
||||
TypePackId tryApplyOverloadedFunction(TypeId function, const NormalizedFunctionType& overloads, TypePackId args);
|
||||
|
||||
|
@ -7,13 +7,13 @@
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/TypeInfer.h"
|
||||
#include "Luau/TypePack.h"
|
||||
#include "Luau/TypeReduction.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauCompleteTableKeysBetter, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauFixAutocompleteInIf, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauFixAutocompleteInWhile, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauFixAutocompleteInFor, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauAutocompleteStringContent, false);
|
||||
@ -1534,20 +1534,13 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
|
||||
}
|
||||
else if (AstStatIf* statIf = extractStat<AstStatIf>(ancestry);
|
||||
statIf && (!statIf->thenLocation || statIf->thenLocation->containsClosed(position)) &&
|
||||
(!FFlag::LuauFixAutocompleteInIf || (statIf->condition && !statIf->condition->location.containsClosed(position))))
|
||||
(statIf->condition && !statIf->condition->location.containsClosed(position)))
|
||||
{
|
||||
if (FFlag::LuauFixAutocompleteInIf)
|
||||
{
|
||||
AutocompleteEntryMap ret;
|
||||
ret["then"] = {AutocompleteEntryKind::Keyword};
|
||||
ret["and"] = {AutocompleteEntryKind::Keyword};
|
||||
ret["or"] = {AutocompleteEntryKind::Keyword};
|
||||
return {std::move(ret), ancestry, AutocompleteContext::Keyword};
|
||||
}
|
||||
else
|
||||
{
|
||||
return {{{"then", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword};
|
||||
}
|
||||
AutocompleteEntryMap ret;
|
||||
ret["then"] = {AutocompleteEntryKind::Keyword};
|
||||
ret["and"] = {AutocompleteEntryKind::Keyword};
|
||||
ret["or"] = {AutocompleteEntryKind::Keyword};
|
||||
return {std::move(ret), ancestry, AutocompleteContext::Keyword};
|
||||
}
|
||||
else if (AstStatRepeat* statRepeat = node->as<AstStatRepeat>(); statRepeat && statRepeat->condition->is<AstExprError>())
|
||||
return autocompleteExpression(sourceModule, *module, builtinTypes, typeArena, ancestry, position);
|
||||
@ -1671,7 +1664,6 @@ AutocompleteResult autocomplete(Frontend& frontend, const ModuleName& moduleName
|
||||
return {};
|
||||
|
||||
ModulePtr module = frontend.moduleResolverForAutocomplete.getModule(moduleName);
|
||||
|
||||
if (!module)
|
||||
return {};
|
||||
|
||||
|
@ -42,7 +42,7 @@ static bool dcrMagicFunctionSelect(MagicFunctionCallContext context);
|
||||
static bool dcrMagicFunctionRequire(MagicFunctionCallContext context);
|
||||
static bool dcrMagicFunctionPack(MagicFunctionCallContext context);
|
||||
|
||||
static std::vector<ConnectiveId> dcrMagicRefinementAssert(const MagicRefinementContext& context);
|
||||
static std::vector<RefinementId> dcrMagicRefinementAssert(const MagicRefinementContext& context);
|
||||
|
||||
TypeId makeUnion(TypeArena& arena, std::vector<TypeId>&& types)
|
||||
{
|
||||
@ -624,12 +624,12 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionAssert(
|
||||
return WithPredicate<TypePackId>{arena.addTypePack(TypePack{std::move(head), tail})};
|
||||
}
|
||||
|
||||
static std::vector<ConnectiveId> dcrMagicRefinementAssert(const MagicRefinementContext& ctx)
|
||||
static std::vector<RefinementId> dcrMagicRefinementAssert(const MagicRefinementContext& ctx)
|
||||
{
|
||||
if (ctx.argumentConnectives.empty())
|
||||
if (ctx.argumentRefinements.empty())
|
||||
return {};
|
||||
|
||||
ctx.cgb->applyRefinements(ctx.scope, ctx.callSite->location, ctx.argumentConnectives[0]);
|
||||
ctx.cgb->applyRefinements(ctx.scope, ctx.callSite->location, ctx.argumentRefinements[0]);
|
||||
return {};
|
||||
}
|
||||
|
||||
|
@ -191,16 +191,16 @@ static void unionRefinements(const std::unordered_map<DefId, TypeId>& lhs, const
|
||||
}
|
||||
}
|
||||
|
||||
static void computeRefinement(const ScopePtr& scope, ConnectiveId connective, std::unordered_map<DefId, TypeId>* refis, bool sense,
|
||||
static void computeRefinement(const ScopePtr& scope, RefinementId refinement, std::unordered_map<DefId, TypeId>* refis, bool sense,
|
||||
NotNull<TypeArena> arena, bool eq, std::vector<ConstraintV>* constraints)
|
||||
{
|
||||
using RefinementMap = std::unordered_map<DefId, TypeId>;
|
||||
|
||||
if (!connective)
|
||||
if (!refinement)
|
||||
return;
|
||||
else if (auto negation = get<Negation>(connective))
|
||||
return computeRefinement(scope, negation->connective, refis, !sense, arena, eq, constraints);
|
||||
else if (auto conjunction = get<Conjunction>(connective))
|
||||
else if (auto negation = get<Negation>(refinement))
|
||||
return computeRefinement(scope, negation->refinement, refis, !sense, arena, eq, constraints);
|
||||
else if (auto conjunction = get<Conjunction>(refinement))
|
||||
{
|
||||
RefinementMap lhsRefis;
|
||||
RefinementMap rhsRefis;
|
||||
@ -211,7 +211,7 @@ static void computeRefinement(const ScopePtr& scope, ConnectiveId connective, st
|
||||
if (!sense)
|
||||
unionRefinements(lhsRefis, rhsRefis, *refis, arena);
|
||||
}
|
||||
else if (auto disjunction = get<Disjunction>(connective))
|
||||
else if (auto disjunction = get<Disjunction>(refinement))
|
||||
{
|
||||
RefinementMap lhsRefis;
|
||||
RefinementMap rhsRefis;
|
||||
@ -222,12 +222,12 @@ static void computeRefinement(const ScopePtr& scope, ConnectiveId connective, st
|
||||
if (sense)
|
||||
unionRefinements(lhsRefis, rhsRefis, *refis, arena);
|
||||
}
|
||||
else if (auto equivalence = get<Equivalence>(connective))
|
||||
else if (auto equivalence = get<Equivalence>(refinement))
|
||||
{
|
||||
computeRefinement(scope, equivalence->lhs, refis, sense, arena, true, constraints);
|
||||
computeRefinement(scope, equivalence->rhs, refis, sense, arena, true, constraints);
|
||||
}
|
||||
else if (auto proposition = get<Proposition>(connective))
|
||||
else if (auto proposition = get<Proposition>(refinement))
|
||||
{
|
||||
TypeId discriminantTy = proposition->discriminantTy;
|
||||
if (!sense && !eq)
|
||||
@ -264,14 +264,14 @@ static std::pair<DefId, TypeId> computeDiscriminantType(NotNull<TypeArena> arena
|
||||
return {def, discriminantTy};
|
||||
}
|
||||
|
||||
void ConstraintGraphBuilder::applyRefinements(const ScopePtr& scope, Location location, ConnectiveId connective)
|
||||
void ConstraintGraphBuilder::applyRefinements(const ScopePtr& scope, Location location, RefinementId refinement)
|
||||
{
|
||||
if (!connective)
|
||||
if (!refinement)
|
||||
return;
|
||||
|
||||
std::unordered_map<DefId, TypeId> refinements;
|
||||
std::vector<ConstraintV> constraints;
|
||||
computeRefinement(scope, connective, &refinements, /*sense*/ true, arena, /*eq*/ false, &constraints);
|
||||
computeRefinement(scope, refinement, &refinements, /*sense*/ true, arena, /*eq*/ false, &constraints);
|
||||
|
||||
for (auto [def, discriminantTy] : refinements)
|
||||
{
|
||||
@ -559,7 +559,10 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocal* local)
|
||||
|
||||
void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFor* for_)
|
||||
{
|
||||
auto checkNumber = [&](AstExpr* expr) {
|
||||
if (for_->var->annotation)
|
||||
resolveType(scope, for_->var->annotation, /* inTypeArguments */ false);
|
||||
|
||||
auto inferNumber = [&](AstExpr* expr) {
|
||||
if (!expr)
|
||||
return;
|
||||
|
||||
@ -567,9 +570,9 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFor* for_)
|
||||
addConstraint(scope, expr->location, SubtypeConstraint{t, builtinTypes->numberType});
|
||||
};
|
||||
|
||||
checkNumber(for_->from);
|
||||
checkNumber(for_->to);
|
||||
checkNumber(for_->step);
|
||||
inferNumber(for_->from);
|
||||
inferNumber(for_->to);
|
||||
inferNumber(for_->step);
|
||||
|
||||
ScopePtr forScope = childScope(for_, scope);
|
||||
forScope->bindings[for_->var] = Binding{builtinTypes->numberType, for_->var->location};
|
||||
@ -770,23 +773,23 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatCompoundAssign*
|
||||
|
||||
TypeId resultType = arena->addType(BlockedType{});
|
||||
addConstraint(scope, assign->location,
|
||||
BinaryConstraint{assign->op, varId, valueInf.ty, resultType, assign, &astOriginalCallTypes, &astOverloadResolvedTypes});
|
||||
BinaryConstraint{assign->op, varId, valueInf.ty, resultType, assign, &module->astOriginalCallTypes, &module->astOverloadResolvedTypes});
|
||||
addConstraint(scope, assign->location, SubtypeConstraint{resultType, varId});
|
||||
}
|
||||
|
||||
void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatIf* ifStatement)
|
||||
{
|
||||
ScopePtr condScope = childScope(ifStatement->condition, scope);
|
||||
auto [_, connective] = check(condScope, ifStatement->condition, std::nullopt);
|
||||
auto [_, refinement] = check(condScope, ifStatement->condition, std::nullopt);
|
||||
|
||||
ScopePtr thenScope = childScope(ifStatement->thenbody, scope);
|
||||
applyRefinements(thenScope, Location{}, connective);
|
||||
applyRefinements(thenScope, Location{}, refinement);
|
||||
visit(thenScope, ifStatement->thenbody);
|
||||
|
||||
if (ifStatement->elsebody)
|
||||
{
|
||||
ScopePtr elseScope = childScope(ifStatement->elsebody, scope);
|
||||
applyRefinements(elseScope, Location{}, connectiveArena.negation(connective));
|
||||
applyRefinements(elseScope, Location{}, refinementArena.negation(refinement));
|
||||
visit(elseScope, ifStatement->elsebody);
|
||||
}
|
||||
}
|
||||
@ -1049,7 +1052,7 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExpr*
|
||||
}
|
||||
|
||||
LUAU_ASSERT(result.tp);
|
||||
astTypePacks[expr] = result.tp;
|
||||
module->astTypePacks[expr] = result.tp;
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -1096,7 +1099,7 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCa
|
||||
|
||||
std::vector<TypeId> args;
|
||||
std::optional<TypePackId> argTail;
|
||||
std::vector<ConnectiveId> argumentConnectives;
|
||||
std::vector<RefinementId> argumentRefinements;
|
||||
|
||||
Checkpoint argCheckpoint = checkpoint(this);
|
||||
|
||||
@ -1113,7 +1116,7 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCa
|
||||
// computing fnType. If computing that did not cause us to exceed a
|
||||
// recursion limit, we can fetch it from astTypes rather than
|
||||
// recomputing it.
|
||||
TypeId* selfTy = astTypes.find(exprArgs[0]);
|
||||
TypeId* selfTy = module->astTypes.find(exprArgs[0]);
|
||||
if (selfTy)
|
||||
args.push_back(*selfTy);
|
||||
else
|
||||
@ -1121,9 +1124,9 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCa
|
||||
}
|
||||
else if (i < exprArgs.size() - 1 || !(arg->is<AstExprCall>() || arg->is<AstExprVarargs>()))
|
||||
{
|
||||
auto [ty, connective] = check(scope, arg, expectedType);
|
||||
auto [ty, refinement] = check(scope, arg, expectedType);
|
||||
args.push_back(ty);
|
||||
argumentConnectives.push_back(connective);
|
||||
argumentRefinements.push_back(refinement);
|
||||
}
|
||||
else
|
||||
argTail = checkPack(scope, arg, {}).tp; // FIXME? not sure about expectedTypes here
|
||||
@ -1137,11 +1140,11 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCa
|
||||
constraint->dependencies.push_back(extractArgsConstraint);
|
||||
});
|
||||
|
||||
std::vector<ConnectiveId> returnConnectives;
|
||||
std::vector<RefinementId> returnRefinements;
|
||||
if (auto ftv = get<FunctionType>(follow(fnType)); ftv && ftv->dcrMagicRefinement)
|
||||
{
|
||||
MagicRefinementContext ctx{scope, NotNull{this}, dfg, NotNull{&connectiveArena}, std::move(argumentConnectives), call};
|
||||
returnConnectives = ftv->dcrMagicRefinement(ctx);
|
||||
MagicRefinementContext ctx{scope, NotNull{this}, dfg, NotNull{&refinementArena}, std::move(argumentRefinements), call};
|
||||
returnRefinements = ftv->dcrMagicRefinement(ctx);
|
||||
}
|
||||
|
||||
if (matchSetmetatable(*call))
|
||||
@ -1169,11 +1172,11 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCa
|
||||
}
|
||||
|
||||
|
||||
return InferencePack{arena->addTypePack({resultTy}), std::move(returnConnectives)};
|
||||
return InferencePack{arena->addTypePack({resultTy}), std::move(returnRefinements)};
|
||||
}
|
||||
else
|
||||
{
|
||||
astOriginalCallTypes[call->func] = fnType;
|
||||
module->astOriginalCallTypes[call->func] = fnType;
|
||||
|
||||
TypeId instantiatedType = arena->addType(BlockedType{});
|
||||
// TODO: How do expectedTypes play into this? Do they?
|
||||
@ -1208,7 +1211,7 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCa
|
||||
fcc->dependencies.emplace_back(constraint.get());
|
||||
});
|
||||
|
||||
return InferencePack{rets, std::move(returnConnectives)};
|
||||
return InferencePack{rets, std::move(returnRefinements)};
|
||||
}
|
||||
}
|
||||
|
||||
@ -1261,7 +1264,7 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExpr* expr, st
|
||||
gc->dependencies.emplace_back(constraint.get());
|
||||
});
|
||||
|
||||
return Inference{generalizedTy};
|
||||
result = Inference{generalizedTy};
|
||||
}
|
||||
else if (auto indexName = expr->as<AstExprIndexName>())
|
||||
result = check(scope, indexName);
|
||||
@ -1294,9 +1297,9 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExpr* expr, st
|
||||
}
|
||||
|
||||
LUAU_ASSERT(result.ty);
|
||||
astTypes[expr] = result.ty;
|
||||
module->astTypes[expr] = result.ty;
|
||||
if (expectedType)
|
||||
astExpectedTypes[expr] = *expectedType;
|
||||
module->astExpectedTypes[expr] = *expectedType;
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -1366,7 +1369,7 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprLocal* loc
|
||||
return Inference{builtinTypes->errorRecoveryType()}; // TODO: replace with ice, locals should never exist before its definition.
|
||||
|
||||
if (def)
|
||||
return Inference{*resultTy, connectiveArena.proposition(*def, builtinTypes->truthyType)};
|
||||
return Inference{*resultTy, refinementArena.proposition(*def, builtinTypes->truthyType)};
|
||||
else
|
||||
return Inference{*resultTy};
|
||||
}
|
||||
@ -1456,7 +1459,7 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIndexName*
|
||||
if (def)
|
||||
{
|
||||
if (auto ty = scope->lookup(*def))
|
||||
return Inference{*ty, connectiveArena.proposition(*def, builtinTypes->truthyType)};
|
||||
return Inference{*ty, refinementArena.proposition(*def, builtinTypes->truthyType)};
|
||||
else
|
||||
scope->dcrRefinements[*def] = result;
|
||||
}
|
||||
@ -1470,7 +1473,7 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIndexName*
|
||||
addConstraint(scope, indexName->expr->location, SubtypeConstraint{obj, expectedTableType});
|
||||
|
||||
if (def)
|
||||
return Inference{result, connectiveArena.proposition(*def, builtinTypes->truthyType)};
|
||||
return Inference{result, refinementArena.proposition(*def, builtinTypes->truthyType)};
|
||||
else
|
||||
return Inference{result};
|
||||
}
|
||||
@ -1492,48 +1495,40 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIndexExpr*
|
||||
|
||||
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprUnary* unary)
|
||||
{
|
||||
auto [operandType, connective] = check(scope, unary->expr);
|
||||
auto [operandType, refinement] = check(scope, unary->expr);
|
||||
TypeId resultType = arena->addType(BlockedType{});
|
||||
addConstraint(scope, unary->location, UnaryConstraint{unary->op, operandType, resultType});
|
||||
|
||||
if (unary->op == AstExprUnary::Not)
|
||||
return Inference{resultType, connectiveArena.negation(connective)};
|
||||
return Inference{resultType, refinementArena.negation(refinement)};
|
||||
else
|
||||
return Inference{resultType};
|
||||
}
|
||||
|
||||
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprBinary* binary, std::optional<TypeId> expectedType)
|
||||
{
|
||||
auto [leftType, rightType, connective] = checkBinary(scope, binary, expectedType);
|
||||
auto [leftType, rightType, refinement] = checkBinary(scope, binary, expectedType);
|
||||
|
||||
TypeId resultType = arena->addType(BlockedType{});
|
||||
addConstraint(scope, binary->location,
|
||||
BinaryConstraint{binary->op, leftType, rightType, resultType, binary, &astOriginalCallTypes, &astOverloadResolvedTypes});
|
||||
return Inference{resultType, std::move(connective)};
|
||||
BinaryConstraint{binary->op, leftType, rightType, resultType, binary, &module->astOriginalCallTypes, &module->astOverloadResolvedTypes});
|
||||
return Inference{resultType, std::move(refinement)};
|
||||
}
|
||||
|
||||
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIfElse* ifElse, std::optional<TypeId> expectedType)
|
||||
{
|
||||
ScopePtr condScope = childScope(ifElse->condition, scope);
|
||||
auto [_, connective] = check(scope, ifElse->condition);
|
||||
auto [_, refinement] = check(scope, ifElse->condition);
|
||||
|
||||
ScopePtr thenScope = childScope(ifElse->trueExpr, scope);
|
||||
applyRefinements(thenScope, ifElse->trueExpr->location, connective);
|
||||
applyRefinements(thenScope, ifElse->trueExpr->location, refinement);
|
||||
TypeId thenType = check(thenScope, ifElse->trueExpr, expectedType).ty;
|
||||
|
||||
ScopePtr elseScope = childScope(ifElse->falseExpr, scope);
|
||||
applyRefinements(elseScope, ifElse->falseExpr->location, connectiveArena.negation(connective));
|
||||
applyRefinements(elseScope, ifElse->falseExpr->location, refinementArena.negation(refinement));
|
||||
TypeId elseType = check(elseScope, ifElse->falseExpr, expectedType).ty;
|
||||
|
||||
if (ifElse->hasElse)
|
||||
{
|
||||
TypeId resultType = expectedType ? *expectedType : freshType(scope);
|
||||
addConstraint(scope, ifElse->trueExpr->location, SubtypeConstraint{thenType, resultType});
|
||||
addConstraint(scope, ifElse->falseExpr->location, SubtypeConstraint{elseType, resultType});
|
||||
return Inference{resultType};
|
||||
}
|
||||
|
||||
return Inference{thenType};
|
||||
return Inference{expectedType ? *expectedType : arena->addType(UnionType{{thenType, elseType}})};
|
||||
}
|
||||
|
||||
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprTypeAssertion* typeAssert)
|
||||
@ -1550,28 +1545,28 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprInterpStri
|
||||
return Inference{builtinTypes->stringType};
|
||||
}
|
||||
|
||||
std::tuple<TypeId, TypeId, ConnectiveId> ConstraintGraphBuilder::checkBinary(
|
||||
std::tuple<TypeId, TypeId, RefinementId> ConstraintGraphBuilder::checkBinary(
|
||||
const ScopePtr& scope, AstExprBinary* binary, std::optional<TypeId> expectedType)
|
||||
{
|
||||
if (binary->op == AstExprBinary::And)
|
||||
{
|
||||
auto [leftType, leftConnective] = check(scope, binary->left, expectedType);
|
||||
auto [leftType, leftRefinement] = check(scope, binary->left, expectedType);
|
||||
|
||||
ScopePtr rightScope = childScope(binary->right, scope);
|
||||
applyRefinements(rightScope, binary->right->location, leftConnective);
|
||||
auto [rightType, rightConnective] = check(rightScope, binary->right, expectedType);
|
||||
applyRefinements(rightScope, binary->right->location, leftRefinement);
|
||||
auto [rightType, rightRefinement] = check(rightScope, binary->right, expectedType);
|
||||
|
||||
return {leftType, rightType, connectiveArena.conjunction(leftConnective, rightConnective)};
|
||||
return {leftType, rightType, refinementArena.conjunction(leftRefinement, rightRefinement)};
|
||||
}
|
||||
else if (binary->op == AstExprBinary::Or)
|
||||
{
|
||||
auto [leftType, leftConnective] = check(scope, binary->left, expectedType);
|
||||
auto [leftType, leftRefinement] = check(scope, binary->left, expectedType);
|
||||
|
||||
ScopePtr rightScope = childScope(binary->right, scope);
|
||||
applyRefinements(rightScope, binary->right->location, connectiveArena.negation(leftConnective));
|
||||
auto [rightType, rightConnective] = check(rightScope, binary->right, expectedType);
|
||||
applyRefinements(rightScope, binary->right->location, refinementArena.negation(leftRefinement));
|
||||
auto [rightType, rightRefinement] = check(rightScope, binary->right, expectedType);
|
||||
|
||||
return {leftType, rightType, connectiveArena.disjunction(leftConnective, rightConnective)};
|
||||
return {leftType, rightType, refinementArena.disjunction(leftRefinement, rightRefinement)};
|
||||
}
|
||||
else if (auto typeguard = matchTypeGuard(binary))
|
||||
{
|
||||
@ -1613,11 +1608,11 @@ std::tuple<TypeId, TypeId, ConnectiveId> ConstraintGraphBuilder::checkBinary(
|
||||
discriminantTy = ty;
|
||||
}
|
||||
|
||||
ConnectiveId proposition = connectiveArena.proposition(*def, discriminantTy);
|
||||
RefinementId proposition = refinementArena.proposition(*def, discriminantTy);
|
||||
if (binary->op == AstExprBinary::CompareEq)
|
||||
return {leftType, rightType, proposition};
|
||||
else if (binary->op == AstExprBinary::CompareNe)
|
||||
return {leftType, rightType, connectiveArena.negation(proposition)};
|
||||
return {leftType, rightType, refinementArena.negation(proposition)};
|
||||
else
|
||||
ice->ice("matchTypeGuard should only return a Some under `==` or `~=`!");
|
||||
}
|
||||
@ -1626,21 +1621,21 @@ std::tuple<TypeId, TypeId, ConnectiveId> ConstraintGraphBuilder::checkBinary(
|
||||
TypeId leftType = check(scope, binary->left, expectedType, true).ty;
|
||||
TypeId rightType = check(scope, binary->right, expectedType, true).ty;
|
||||
|
||||
ConnectiveId leftConnective = nullptr;
|
||||
RefinementId leftRefinement = nullptr;
|
||||
if (auto def = dfg->getDef(binary->left))
|
||||
leftConnective = connectiveArena.proposition(*def, rightType);
|
||||
leftRefinement = refinementArena.proposition(*def, rightType);
|
||||
|
||||
ConnectiveId rightConnective = nullptr;
|
||||
RefinementId rightRefinement = nullptr;
|
||||
if (auto def = dfg->getDef(binary->right))
|
||||
rightConnective = connectiveArena.proposition(*def, leftType);
|
||||
rightRefinement = refinementArena.proposition(*def, leftType);
|
||||
|
||||
if (binary->op == AstExprBinary::CompareNe)
|
||||
{
|
||||
leftConnective = connectiveArena.negation(leftConnective);
|
||||
rightConnective = connectiveArena.negation(rightConnective);
|
||||
leftRefinement = refinementArena.negation(leftRefinement);
|
||||
rightRefinement = refinementArena.negation(rightRefinement);
|
||||
}
|
||||
|
||||
return {leftType, rightType, connectiveArena.equivalence(leftConnective, rightConnective)};
|
||||
return {leftType, rightType, refinementArena.equivalence(leftRefinement, rightRefinement)};
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -1737,13 +1732,13 @@ TypeId ConstraintGraphBuilder::checkLValue(const ScopePtr& scope, AstExpr* expr)
|
||||
for (size_t i = 0; i < segments.size(); ++i)
|
||||
{
|
||||
TypeId segmentTy = arena->addType(BlockedType{});
|
||||
astTypes[exprs[i]] = segmentTy;
|
||||
module->astTypes[exprs[i]] = segmentTy;
|
||||
addConstraint(scope, expr->location, HasPropConstraint{segmentTy, prevSegmentTy, segments[i]});
|
||||
prevSegmentTy = segmentTy;
|
||||
}
|
||||
|
||||
astTypes[expr] = prevSegmentTy;
|
||||
astTypes[e] = updatedType;
|
||||
module->astTypes[expr] = prevSegmentTy;
|
||||
module->astTypes[e] = updatedType;
|
||||
// astTypes[expr] = propTy;
|
||||
|
||||
return propTy;
|
||||
@ -1895,6 +1890,10 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS
|
||||
if (local->annotation)
|
||||
{
|
||||
annotationTy = resolveType(signatureScope, local->annotation, /* inTypeArguments */ false);
|
||||
// If we provide an annotation that is wrong, type inference should ignore the annotation
|
||||
// and try to infer a fresh type, like in the old solver
|
||||
if (get<ErrorType>(follow(annotationTy)))
|
||||
annotationTy = freshType(signatureScope);
|
||||
addConstraint(signatureScope, local->annotation->location, SubtypeConstraint{t, annotationTy});
|
||||
}
|
||||
else if (i < expectedArgPack.head.size())
|
||||
@ -1964,7 +1963,7 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS
|
||||
|
||||
TypeId actualFunctionType = arena->addType(std::move(actualFunction));
|
||||
LUAU_ASSERT(actualFunctionType);
|
||||
astTypes[fn] = actualFunctionType;
|
||||
module->astTypes[fn] = actualFunctionType;
|
||||
|
||||
if (expectedType && get<FreeType>(*expectedType))
|
||||
{
|
||||
@ -2214,7 +2213,7 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, b
|
||||
result = builtinTypes->errorRecoveryType();
|
||||
}
|
||||
|
||||
astResolvedTypes[ty] = result;
|
||||
module->astResolvedTypes[ty] = result;
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -2248,7 +2247,7 @@ TypePackId ConstraintGraphBuilder::resolveTypePack(const ScopePtr& scope, AstTyp
|
||||
result = builtinTypes->errorRecoveryTypePack();
|
||||
}
|
||||
|
||||
astResolvedTypePacks[tp] = result;
|
||||
module->astResolvedTypePacks[tp] = result;
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -2307,13 +2306,13 @@ std::vector<std::pair<Name, GenericTypePackDefinition>> ConstraintGraphBuilder::
|
||||
|
||||
Inference ConstraintGraphBuilder::flattenPack(const ScopePtr& scope, Location location, InferencePack pack)
|
||||
{
|
||||
const auto& [tp, connectives] = pack;
|
||||
ConnectiveId connective = nullptr;
|
||||
if (!connectives.empty())
|
||||
connective = connectives[0];
|
||||
const auto& [tp, refinements] = pack;
|
||||
RefinementId refinement = nullptr;
|
||||
if (!refinements.empty())
|
||||
refinement = refinements[0];
|
||||
|
||||
if (auto f = first(tp))
|
||||
return Inference{*f, connective};
|
||||
return Inference{*f, refinement};
|
||||
|
||||
TypeId typeResult = freshType(scope);
|
||||
TypePack onePack{{typeResult}, freshTypePack(scope)};
|
||||
@ -2321,7 +2320,7 @@ Inference ConstraintGraphBuilder::flattenPack(const ScopePtr& scope, Location lo
|
||||
|
||||
addConstraint(scope, location, PackSubtypeConstraint{tp, oneTypePack});
|
||||
|
||||
return Inference{typeResult, connective};
|
||||
return Inference{typeResult, refinement};
|
||||
}
|
||||
|
||||
void ConstraintGraphBuilder::reportError(Location location, TypeErrorData err)
|
||||
|
@ -528,7 +528,7 @@ bool ConstraintSolver::tryDispatch(const UnaryConstraint& c, NotNull<const Const
|
||||
}
|
||||
case AstExprUnary::Minus:
|
||||
{
|
||||
if (isNumber(operandType) || get<AnyType>(operandType) || get<ErrorType>(operandType))
|
||||
if (isNumber(operandType) || get<AnyType>(operandType) || get<ErrorType>(operandType) || get<NeverType>(operandType))
|
||||
{
|
||||
asMutable(c.resultType)->ty.emplace<BoundType>(c.operandType);
|
||||
}
|
||||
@ -1415,7 +1415,7 @@ bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull<const Con
|
||||
bind(c.resultType, subjectType);
|
||||
return true;
|
||||
}
|
||||
else if (get<AnyType>(subjectType) || get<ErrorType>(subjectType))
|
||||
else if (get<AnyType>(subjectType) || get<ErrorType>(subjectType) || get<NeverType>(subjectType))
|
||||
{
|
||||
bind(c.resultType, subjectType);
|
||||
return true;
|
||||
|
@ -579,6 +579,7 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
|
||||
module->astOriginalCallTypes.clear();
|
||||
module->astOverloadResolvedTypes.clear();
|
||||
module->astResolvedTypes.clear();
|
||||
module->astOriginalResolvedTypes.clear();
|
||||
module->astResolvedTypePacks.clear();
|
||||
module->astScopes.clear();
|
||||
|
||||
@ -591,6 +592,7 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
|
||||
module->astOriginalCallTypes.clear();
|
||||
module->astResolvedTypes.clear();
|
||||
module->astResolvedTypePacks.clear();
|
||||
module->astOriginalResolvedTypes.clear();
|
||||
module->scopes.resize(1);
|
||||
}
|
||||
}
|
||||
@ -922,23 +924,22 @@ ModulePtr Frontend::check(
|
||||
|
||||
for (TypeError& e : cs.errors)
|
||||
result->errors.emplace_back(std::move(e));
|
||||
|
||||
result->scopes = std::move(cgb.scopes);
|
||||
result->astTypes = std::move(cgb.astTypes);
|
||||
result->astTypePacks = std::move(cgb.astTypePacks);
|
||||
result->astExpectedTypes = std::move(cgb.astExpectedTypes);
|
||||
result->astOriginalCallTypes = std::move(cgb.astOriginalCallTypes);
|
||||
result->astOverloadResolvedTypes = std::move(cgb.astOverloadResolvedTypes);
|
||||
result->astResolvedTypes = std::move(cgb.astResolvedTypes);
|
||||
result->astResolvedTypePacks = std::move(cgb.astResolvedTypePacks);
|
||||
result->type = sourceModule.type;
|
||||
|
||||
result->clonePublicInterface(builtinTypes, iceHandler);
|
||||
|
||||
Luau::check(builtinTypes, logger.get(), sourceModule, result.get());
|
||||
|
||||
// Ideally we freeze the arenas before the call into Luau::check, but TypeReduction
|
||||
// needs to allocate new types while Luau::check is in progress, so here we are.
|
||||
//
|
||||
// It does mean that mutations to the type graph can happen after the constraints
|
||||
// have been solved, which will cause hard-to-debug problems. We should revisit this.
|
||||
freeze(result->internalTypes);
|
||||
freeze(result->interfaceTypes);
|
||||
|
||||
Luau::check(builtinTypes, logger.get(), sourceModule, result.get());
|
||||
|
||||
if (FFlag::DebugLuauLogSolverToJson)
|
||||
{
|
||||
std::string output = logger->compileOutput();
|
||||
|
@ -2616,10 +2616,6 @@ private:
|
||||
emitWarning(*context, LintWarning::Code_IntegerParsing, node->location,
|
||||
"Hexadecimal number literal exceeded available precision and has been truncated to 2^64");
|
||||
break;
|
||||
case ConstantNumberParseResult::DoublePrefix:
|
||||
emitWarning(*context, LintWarning::Code_IntegerParsing, node->location,
|
||||
"Hexadecimal number literal has a double prefix, which will fail to parse in the future; remove the extra 0x to fix");
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -232,9 +232,6 @@ void Module::clonePublicInterface(NotNull<BuiltinTypes> builtinTypes, InternalEr
|
||||
this->returnType = moduleScope->returnType;
|
||||
this->exportedTypeBindings = std::move(moduleScope->exportedTypeBindings);
|
||||
}
|
||||
|
||||
freeze(internalTypes);
|
||||
freeze(interfaceTypes);
|
||||
}
|
||||
|
||||
bool Module::hasModuleScope() const
|
||||
|
@ -1,30 +1,30 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/Connective.h"
|
||||
#include "Luau/Refinement.h"
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
ConnectiveId ConnectiveArena::negation(ConnectiveId connective)
|
||||
RefinementId RefinementArena::negation(RefinementId refinement)
|
||||
{
|
||||
return NotNull{allocator.allocate(Negation{connective})};
|
||||
return NotNull{allocator.allocate(Negation{refinement})};
|
||||
}
|
||||
|
||||
ConnectiveId ConnectiveArena::conjunction(ConnectiveId lhs, ConnectiveId rhs)
|
||||
RefinementId RefinementArena::conjunction(RefinementId lhs, RefinementId rhs)
|
||||
{
|
||||
return NotNull{allocator.allocate(Conjunction{lhs, rhs})};
|
||||
}
|
||||
|
||||
ConnectiveId ConnectiveArena::disjunction(ConnectiveId lhs, ConnectiveId rhs)
|
||||
RefinementId RefinementArena::disjunction(RefinementId lhs, RefinementId rhs)
|
||||
{
|
||||
return NotNull{allocator.allocate(Disjunction{lhs, rhs})};
|
||||
}
|
||||
|
||||
ConnectiveId ConnectiveArena::equivalence(ConnectiveId lhs, ConnectiveId rhs)
|
||||
RefinementId RefinementArena::equivalence(RefinementId lhs, RefinementId rhs)
|
||||
{
|
||||
return NotNull{allocator.allocate(Equivalence{lhs, rhs})};
|
||||
}
|
||||
|
||||
ConnectiveId ConnectiveArena::proposition(DefId def, TypeId discriminantTy)
|
||||
RefinementId RefinementArena::proposition(DefId def, TypeId discriminantTy)
|
||||
{
|
||||
return NotNull{allocator.allocate(Proposition{def, discriminantTy})};
|
||||
}
|
@ -414,7 +414,7 @@ bool hasLength(TypeId ty, DenseHashSet<TypeId>& seen, int* recursionCount)
|
||||
if (seen.contains(ty))
|
||||
return true;
|
||||
|
||||
if (isString(ty) || get<AnyType>(ty) || get<TableType>(ty) || get<MetatableType>(ty))
|
||||
if (isString(ty) || isPrim(ty, PrimitiveType::Table) || get<AnyType>(ty) || get<TableType>(ty) || get<MetatableType>(ty))
|
||||
return true;
|
||||
|
||||
if (auto uty = get<UnionType>(ty))
|
||||
|
@ -4,22 +4,24 @@
|
||||
#include "Luau/Ast.h"
|
||||
#include "Luau/AstQuery.h"
|
||||
#include "Luau/Clone.h"
|
||||
#include "Luau/DcrLogger.h"
|
||||
#include "Luau/Error.h"
|
||||
#include "Luau/Instantiation.h"
|
||||
#include "Luau/Metamethods.h"
|
||||
#include "Luau/Normalize.h"
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/TxnLog.h"
|
||||
#include "Luau/TypeUtils.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/TypeReduction.h"
|
||||
#include "Luau/TypeUtils.h"
|
||||
#include "Luau/Unifier.h"
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/DcrLogger.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauLogSolverToJson);
|
||||
LUAU_FASTFLAG(DebugLuauMagicTypes);
|
||||
LUAU_FASTFLAG(DebugLuauLogSolverToJson)
|
||||
LUAU_FASTFLAG(DebugLuauMagicTypes)
|
||||
LUAU_FASTFLAG(DebugLuauDontReduceTypes)
|
||||
|
||||
LUAU_FASTFLAG(LuauNegatedClassTypes)
|
||||
|
||||
namespace Luau
|
||||
@ -223,10 +225,7 @@ struct TypeChecker2
|
||||
{
|
||||
auto pusher = pushStack(stat);
|
||||
|
||||
if (0)
|
||||
{
|
||||
}
|
||||
else if (auto s = stat->as<AstStatBlock>())
|
||||
if (auto s = stat->as<AstStatBlock>())
|
||||
return visit(s);
|
||||
else if (auto s = stat->as<AstStatIf>())
|
||||
return visit(s);
|
||||
@ -340,8 +339,7 @@ struct TypeChecker2
|
||||
if (value)
|
||||
visit(value, RValue);
|
||||
|
||||
TypeId* maybeValueType = value ? module->astTypes.find(value) : nullptr;
|
||||
if (i != local->values.size - 1 || maybeValueType)
|
||||
if (i != local->values.size - 1 || value)
|
||||
{
|
||||
AstLocal* var = i < local->vars.size ? local->vars.data[i] : nullptr;
|
||||
|
||||
@ -391,13 +389,26 @@ struct TypeChecker2
|
||||
|
||||
void visit(AstStatFor* forStatement)
|
||||
{
|
||||
if (forStatement->var->annotation)
|
||||
visit(forStatement->var->annotation);
|
||||
NotNull<Scope> scope = stack.back();
|
||||
|
||||
if (forStatement->var->annotation)
|
||||
{
|
||||
visit(forStatement->var->annotation);
|
||||
reportErrors(tryUnify(scope, forStatement->var->location, builtinTypes->numberType, lookupAnnotation(forStatement->var->annotation)));
|
||||
}
|
||||
|
||||
auto checkNumber = [this, scope](AstExpr* expr) {
|
||||
if (!expr)
|
||||
return;
|
||||
|
||||
visit(expr, RValue);
|
||||
reportErrors(tryUnify(scope, expr->location, lookupType(expr), builtinTypes->numberType));
|
||||
};
|
||||
|
||||
checkNumber(forStatement->from);
|
||||
checkNumber(forStatement->to);
|
||||
checkNumber(forStatement->step);
|
||||
|
||||
visit(forStatement->from, RValue);
|
||||
visit(forStatement->to, RValue);
|
||||
if (forStatement->step)
|
||||
visit(forStatement->step, RValue);
|
||||
visit(forStatement->body);
|
||||
}
|
||||
|
||||
@ -543,7 +554,7 @@ struct TypeChecker2
|
||||
else
|
||||
reportError(GenericError{"Cannot iterate over a table without indexer"}, forInStatement->values.data[0]->location);
|
||||
}
|
||||
else if (get<AnyType>(iteratorTy) || get<ErrorType>(iteratorTy))
|
||||
else if (get<AnyType>(iteratorTy) || get<ErrorType>(iteratorTy) || get<NeverType>(iteratorTy))
|
||||
{
|
||||
// nothing
|
||||
}
|
||||
@ -624,6 +635,9 @@ struct TypeChecker2
|
||||
visit(rhs, RValue);
|
||||
TypeId rhsType = lookupType(rhs);
|
||||
|
||||
if (get<NeverType>(lhsType))
|
||||
continue;
|
||||
|
||||
if (!isSubtype(rhsType, lhsType, stack.back()))
|
||||
{
|
||||
reportError(TypeMismatch{lhsType, rhsType}, rhs->location);
|
||||
@ -715,10 +729,7 @@ struct TypeChecker2
|
||||
{
|
||||
auto StackPusher = pushStack(expr);
|
||||
|
||||
if (0)
|
||||
{
|
||||
}
|
||||
else if (auto e = expr->as<AstExprGroup>())
|
||||
if (auto e = expr->as<AstExprGroup>())
|
||||
return visit(e, context);
|
||||
else if (auto e = expr->as<AstExprConstantNil>())
|
||||
return visit(e);
|
||||
@ -770,34 +781,34 @@ struct TypeChecker2
|
||||
|
||||
void visit(AstExprConstantNil* expr)
|
||||
{
|
||||
// TODO!
|
||||
NotNull<Scope> scope = stack.back();
|
||||
TypeId actualType = lookupType(expr);
|
||||
TypeId expectedType = builtinTypes->nilType;
|
||||
LUAU_ASSERT(isSubtype(actualType, expectedType, scope));
|
||||
}
|
||||
|
||||
void visit(AstExprConstantBool* expr)
|
||||
{
|
||||
// TODO!
|
||||
NotNull<Scope> scope = stack.back();
|
||||
TypeId actualType = lookupType(expr);
|
||||
TypeId expectedType = builtinTypes->booleanType;
|
||||
LUAU_ASSERT(isSubtype(actualType, expectedType, scope));
|
||||
}
|
||||
|
||||
void visit(AstExprConstantNumber* number)
|
||||
void visit(AstExprConstantNumber* expr)
|
||||
{
|
||||
TypeId actualType = lookupType(number);
|
||||
TypeId numberType = builtinTypes->numberType;
|
||||
|
||||
if (!isSubtype(numberType, actualType, stack.back()))
|
||||
{
|
||||
reportError(TypeMismatch{actualType, numberType}, number->location);
|
||||
}
|
||||
NotNull<Scope> scope = stack.back();
|
||||
TypeId actualType = lookupType(expr);
|
||||
TypeId expectedType = builtinTypes->numberType;
|
||||
LUAU_ASSERT(isSubtype(actualType, expectedType, scope));
|
||||
}
|
||||
|
||||
void visit(AstExprConstantString* string)
|
||||
void visit(AstExprConstantString* expr)
|
||||
{
|
||||
TypeId actualType = lookupType(string);
|
||||
TypeId stringType = builtinTypes->stringType;
|
||||
|
||||
if (!isSubtype(actualType, stringType, stack.back()))
|
||||
{
|
||||
reportError(TypeMismatch{actualType, stringType}, string->location);
|
||||
}
|
||||
NotNull<Scope> scope = stack.back();
|
||||
TypeId actualType = lookupType(expr);
|
||||
TypeId expectedType = builtinTypes->stringType;
|
||||
LUAU_ASSERT(isSubtype(actualType, expectedType, scope));
|
||||
}
|
||||
|
||||
void visit(AstExprLocal* expr)
|
||||
@ -832,7 +843,7 @@ struct TypeChecker2
|
||||
std::vector<Location> argLocs;
|
||||
argLocs.reserve(call->args.size + 1);
|
||||
|
||||
if (get<AnyType>(functionType) || get<ErrorType>(functionType))
|
||||
if (get<AnyType>(functionType) || get<ErrorType>(functionType) || get<NeverType>(functionType))
|
||||
return;
|
||||
else if (std::optional<TypeId> callMm = findMetatableEntry(builtinTypes, module->errors, functionType, "__call", call->func->location))
|
||||
{
|
||||
@ -1080,7 +1091,7 @@ struct TypeChecker2
|
||||
}
|
||||
}
|
||||
|
||||
TypeId visit(AstExprBinary* expr, void* overrideKey = nullptr)
|
||||
TypeId visit(AstExprBinary* expr, AstNode* overrideKey = nullptr)
|
||||
{
|
||||
visit(expr->left, LValue);
|
||||
visit(expr->right, LValue);
|
||||
@ -1164,7 +1175,7 @@ struct TypeChecker2
|
||||
|
||||
if (mm)
|
||||
{
|
||||
void* key = expr;
|
||||
AstNode* key = expr;
|
||||
if (overrideKey != nullptr)
|
||||
key = overrideKey;
|
||||
|
||||
@ -1381,19 +1392,8 @@ struct TypeChecker2
|
||||
{
|
||||
pack = follow(pack);
|
||||
|
||||
while (true)
|
||||
{
|
||||
auto tp = get<TypePack>(pack);
|
||||
if (tp && tp->head.empty() && tp->tail)
|
||||
pack = *tp->tail;
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
if (auto ty = first(pack))
|
||||
return *ty;
|
||||
else if (auto vtp = get<VariadicTypePack>(pack))
|
||||
return vtp->ty;
|
||||
if (auto fst = first(pack, /*ignoreHiddenVariadics*/ false))
|
||||
return *fst;
|
||||
else if (auto ftp = get<FreeTypePack>(pack))
|
||||
{
|
||||
TypeId result = testArena.addType(FreeType{ftp->scope});
|
||||
@ -1407,6 +1407,8 @@ struct TypeChecker2
|
||||
}
|
||||
else if (get<Unifiable::Error>(pack))
|
||||
return builtinTypes->errorRecoveryType();
|
||||
else if (finite(pack) && size(pack) == 0)
|
||||
return builtinTypes->nilType; // `(f())` where `f()` returns no values is coerced into `nil`
|
||||
else
|
||||
ice.ice("flattenPack got a weird pack!");
|
||||
}
|
||||
@ -1652,6 +1654,69 @@ struct TypeChecker2
|
||||
}
|
||||
}
|
||||
|
||||
void reduceTypes()
|
||||
{
|
||||
if (FFlag::DebugLuauDontReduceTypes)
|
||||
return;
|
||||
|
||||
for (auto [_, scope] : module->scopes)
|
||||
{
|
||||
for (auto& [_, b] : scope->bindings)
|
||||
{
|
||||
if (auto reduced = module->reduction->reduce(b.typeId))
|
||||
b.typeId = *reduced;
|
||||
}
|
||||
|
||||
if (auto reduced = module->reduction->reduce(scope->returnType))
|
||||
scope->returnType = *reduced;
|
||||
|
||||
if (scope->varargPack)
|
||||
{
|
||||
if (auto reduced = module->reduction->reduce(*scope->varargPack))
|
||||
scope->varargPack = *reduced;
|
||||
}
|
||||
|
||||
auto reduceMap = [this](auto& map) {
|
||||
for (auto& [_, tf] : map)
|
||||
{
|
||||
if (auto reduced = module->reduction->reduce(tf))
|
||||
tf = *reduced;
|
||||
}
|
||||
};
|
||||
|
||||
reduceMap(scope->exportedTypeBindings);
|
||||
reduceMap(scope->privateTypeBindings);
|
||||
reduceMap(scope->privateTypePackBindings);
|
||||
for (auto& [_, space] : scope->importedTypeBindings)
|
||||
reduceMap(space);
|
||||
}
|
||||
|
||||
auto reduceOrError = [this](auto& map) {
|
||||
for (auto [ast, t] : map)
|
||||
{
|
||||
if (!t)
|
||||
continue; // Reminder: this implies that the recursion limit was exceeded.
|
||||
else if (auto reduced = module->reduction->reduce(t))
|
||||
map[ast] = *reduced;
|
||||
else
|
||||
reportError(NormalizationTooComplex{}, ast->location);
|
||||
}
|
||||
};
|
||||
|
||||
module->astOriginalResolvedTypes = module->astResolvedTypes;
|
||||
|
||||
// Both [`Module::returnType`] and [`Module::exportedTypeBindings`] are empty here, and
|
||||
// is populated by [`Module::clonePublicInterface`] in the future, so by that point these
|
||||
// two aforementioned fields will only contain types that are irreducible.
|
||||
reduceOrError(module->astTypes);
|
||||
reduceOrError(module->astTypePacks);
|
||||
reduceOrError(module->astExpectedTypes);
|
||||
reduceOrError(module->astOriginalCallTypes);
|
||||
reduceOrError(module->astOverloadResolvedTypes);
|
||||
reduceOrError(module->astResolvedTypes);
|
||||
reduceOrError(module->astResolvedTypePacks);
|
||||
}
|
||||
|
||||
template<typename TID>
|
||||
bool isSubtype(TID subTy, TID superTy, NotNull<Scope> scope)
|
||||
{
|
||||
@ -1797,7 +1862,7 @@ struct TypeChecker2
|
||||
void check(NotNull<BuiltinTypes> builtinTypes, DcrLogger* logger, const SourceModule& sourceModule, Module* module)
|
||||
{
|
||||
TypeChecker2 typeChecker{builtinTypes, logger, &sourceModule, module};
|
||||
|
||||
typeChecker.reduceTypes();
|
||||
typeChecker.visit(sourceModule.root);
|
||||
|
||||
unfreeze(module->interfaceTypes);
|
||||
|
@ -323,6 +323,8 @@ ModulePtr TypeChecker::checkWithoutRecursionCheck(const SourceModule& module, Mo
|
||||
normalizer.arena = nullptr;
|
||||
|
||||
currentModule->clonePublicInterface(builtinTypes, *iceHandler);
|
||||
freeze(currentModule->internalTypes);
|
||||
freeze(currentModule->interfaceTypes);
|
||||
|
||||
// Clear unifier cache since it's keyed off internal types that get deallocated
|
||||
// This avoids fake cross-module cache hits and keeps cache size at bay when typechecking large module graphs.
|
||||
|
@ -10,7 +10,7 @@
|
||||
#include <deque>
|
||||
|
||||
LUAU_FASTINTVARIABLE(LuauTypeReductionCartesianProductLimit, 100'000)
|
||||
LUAU_FASTINTVARIABLE(LuauTypeReductionRecursionLimit, 700)
|
||||
LUAU_FASTINTVARIABLE(LuauTypeReductionRecursionLimit, 400)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauDontReduceTypes, false)
|
||||
|
||||
namespace Luau
|
||||
@ -37,7 +37,7 @@ struct TypeReducer
|
||||
|
||||
DenseHashMap<TypeId, ReductionContext<TypeId>>* memoizedTypes;
|
||||
DenseHashMap<TypePackId, ReductionContext<TypePackId>>* memoizedTypePacks;
|
||||
DenseHashSet<TypeId>* cyclicTypes;
|
||||
DenseHashSet<const void*>* cyclics;
|
||||
|
||||
int depth = 0;
|
||||
|
||||
@ -68,8 +68,8 @@ struct TypeReducer
|
||||
return {ctx->type, getMutable<T>(ctx->type)};
|
||||
|
||||
TypeId copiedTy = arena->addType(*t);
|
||||
(*memoizedTypes)[ty] = {copiedTy, false};
|
||||
(*memoizedTypes)[copiedTy] = {copiedTy, false};
|
||||
(*memoizedTypes)[ty] = {copiedTy, true};
|
||||
(*memoizedTypes)[copiedTy] = {copiedTy, true};
|
||||
return {copiedTy, getMutable<T>(copiedTy)};
|
||||
}
|
||||
|
||||
@ -142,31 +142,20 @@ struct TypeReducer
|
||||
std::vector<TypeId> result;
|
||||
bool didReduce = false;
|
||||
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
|
||||
{
|
||||
// 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)));
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
TypeId apply(BinaryFold f, TypeId left, TypeId right)
|
||||
{
|
||||
left = follow(left);
|
||||
right = follow(right);
|
||||
|
||||
if (get<T>(left) || get<T>(right))
|
||||
{
|
||||
std::vector<TypeId> types{left, right};
|
||||
return foldl<T>(begin(types), end(types), std::nullopt, f);
|
||||
}
|
||||
else if (auto reduced = (this->*f)(left, right))
|
||||
return *reduced;
|
||||
else
|
||||
return arena->addType(T{{left, right}});
|
||||
std::vector<TypeId> types{left, right};
|
||||
return foldl<T>(begin(types), end(types), std::nullopt, f);
|
||||
}
|
||||
|
||||
template<typename Into, typename Over>
|
||||
@ -188,8 +177,8 @@ TypeId TypeReducer::reduce(TypeId ty)
|
||||
|
||||
if (auto ctx = memoizedTypes->find(ty); ctx && ctx->irreducible)
|
||||
return ctx->type;
|
||||
else if (auto cyclicTy = cyclicTypes->find(ty))
|
||||
return *cyclicTy;
|
||||
else if (cyclics->contains(ty))
|
||||
return ty;
|
||||
|
||||
RecursionLimiter rl{&depth, FInt::LuauTypeReductionRecursionLimit};
|
||||
|
||||
@ -216,6 +205,8 @@ TypePackId TypeReducer::reduce(TypePackId tp)
|
||||
|
||||
if (auto ctx = memoizedTypePacks->find(tp); ctx && ctx->irreducible)
|
||||
return ctx->type;
|
||||
else if (cyclics->contains(tp))
|
||||
return tp;
|
||||
|
||||
RecursionLimiter rl{&depth, FInt::LuauTypeReductionRecursionLimit};
|
||||
|
||||
@ -356,9 +347,9 @@ std::optional<TypeId> TypeReducer::intersectionType(TypeId left, TypeId right)
|
||||
else if (t1->state == TableState::Generic || t2->state == TableState::Generic)
|
||||
return std::nullopt; // '{ x: T } & { x: U } ~ '{ x: T } & { x: U }
|
||||
|
||||
if (cyclicTypes->find(left))
|
||||
if (cyclics->contains(left))
|
||||
return std::nullopt; // (t1 where t1 = { p: t1 }) & {} ~ t1 & {}
|
||||
else if (cyclicTypes->find(right))
|
||||
else if (cyclics->contains(right))
|
||||
return std::nullopt; // {} & (t1 where t1 = { p: t1 }) ~ {} & t1
|
||||
|
||||
TypeId resultTy = arena->addType(TableType{});
|
||||
@ -396,10 +387,7 @@ std::optional<TypeId> TypeReducer::intersectionType(TypeId left, TypeId right)
|
||||
return std::nullopt; // { [string]: _ } & { [number]: _ } ~ { [string]: _ } & { [number]: _ }
|
||||
|
||||
TypeId valueTy = apply<IntersectionType>(&TypeReducer::intersectionType, t1->indexer->indexResultType, t2->indexer->indexResultType);
|
||||
if (get<NeverType>(valueTy))
|
||||
return builtinTypes->neverType; // { [_]: string } & { [_]: number } ~ { [_]: string & number } ~ { [_]: never } ~ never
|
||||
|
||||
table->indexer = TableIndexer{keyTy, valueTy};
|
||||
table->indexer = TableIndexer{keyTy, valueTy}; // { [string]: number } & { [string]: string } ~ { [string]: never }
|
||||
}
|
||||
else if (t1->indexer)
|
||||
{
|
||||
@ -422,6 +410,45 @@ std::optional<TypeId> TypeReducer::intersectionType(TypeId left, TypeId right)
|
||||
return intersectionType(right, left); // T & M ~ M & T
|
||||
else if (auto [m1, m2] = get2<MetatableType, MetatableType>(left, right); m1 && m2)
|
||||
return std::nullopt; // TODO
|
||||
else if (auto [nl, nr] = get2<NegationType, NegationType>(left, right); nl && nr)
|
||||
{
|
||||
// These should've been reduced already.
|
||||
TypeId nlTy = follow(nl->ty);
|
||||
TypeId nrTy = follow(nr->ty);
|
||||
LUAU_ASSERT(!get<UnknownType>(nlTy) && !get<UnknownType>(nrTy));
|
||||
LUAU_ASSERT(!get<NeverType>(nlTy) && !get<NeverType>(nrTy));
|
||||
LUAU_ASSERT(!get<AnyType>(nlTy) && !get<AnyType>(nrTy));
|
||||
LUAU_ASSERT(!get<IntersectionType>(nlTy) && !get<IntersectionType>(nrTy));
|
||||
LUAU_ASSERT(!get<UnionType>(nlTy) && !get<UnionType>(nrTy));
|
||||
|
||||
if (auto [npl, npr] = get2<PrimitiveType, PrimitiveType>(nlTy, nrTy); npl && npr)
|
||||
{
|
||||
if (npl->type == npr->type)
|
||||
return left; // ~P1 & ~P2 ~ ~P1 iff P1 == P2
|
||||
else
|
||||
return std::nullopt; // ~P1 & ~P2 ~ ~P1 & ~P2 iff P1 != P2
|
||||
}
|
||||
else if (auto [nsl, nsr] = get2<SingletonType, SingletonType>(nlTy, nrTy); nsl && nsr)
|
||||
{
|
||||
if (*nsl == *nsr)
|
||||
return left; // ~"A" & ~"A" ~ ~"A"
|
||||
else
|
||||
return std::nullopt; // ~"A" & ~"B" ~ ~"A" & ~"B"
|
||||
}
|
||||
else if (auto [ns, np] = get2<SingletonType, PrimitiveType>(nlTy, nrTy); ns && np)
|
||||
{
|
||||
if (get<StringSingleton>(ns) && np->type == PrimitiveType::String)
|
||||
return right; // ~"A" & ~string ~ ~string
|
||||
else if (get<BooleanSingleton>(ns) && np->type == PrimitiveType::Boolean)
|
||||
return right; // ~false & ~boolean ~ ~boolean
|
||||
else
|
||||
return std::nullopt; // ~"A" | ~P ~ ~"A" & ~P
|
||||
}
|
||||
else if (auto [np, ns] = get2<PrimitiveType, SingletonType>(nlTy, nrTy); np && ns)
|
||||
return intersectionType(right, left); // ~P & ~S ~ ~S & ~P
|
||||
else
|
||||
return std::nullopt; // ~T & ~U ~ ~T & ~U
|
||||
}
|
||||
else if (auto nl = get<NegationType>(left))
|
||||
{
|
||||
// These should've been reduced already.
|
||||
@ -477,10 +504,10 @@ std::optional<TypeId> TypeReducer::intersectionType(TypeId left, TypeId right)
|
||||
}
|
||||
else if (auto [nc, c] = get2<ClassType, ClassType>(nlTy, right); nc && c)
|
||||
{
|
||||
if (isSubclass(nc, c))
|
||||
return std::nullopt; // ~Derived & Base ~ ~Derived & Base
|
||||
else if (isSubclass(c, nc))
|
||||
if (isSubclass(c, nc))
|
||||
return builtinTypes->neverType; // ~Base & Derived ~ never
|
||||
else if (isSubclass(nc, c))
|
||||
return std::nullopt; // ~Derived & Base ~ ~Derived & Base
|
||||
else
|
||||
return right; // ~Base & Unrelated ~ Unrelated
|
||||
}
|
||||
@ -499,7 +526,7 @@ std::optional<TypeId> TypeReducer::intersectionType(TypeId left, TypeId right)
|
||||
return right; // ~string & {} ~ {}
|
||||
}
|
||||
else
|
||||
return std::nullopt; // TODO
|
||||
return right; // ~T & U ~ U
|
||||
}
|
||||
else if (get<NegationType>(right))
|
||||
return intersectionType(right, left); // T & ~U ~ ~U & T
|
||||
@ -679,10 +706,10 @@ std::optional<TypeId> TypeReducer::unionType(TypeId left, TypeId right)
|
||||
}
|
||||
else if (auto [nc, c] = get2<ClassType, ClassType>(nlTy, right); nc && c)
|
||||
{
|
||||
if (isSubclass(nc, c))
|
||||
return builtinTypes->unknownType; // ~Derived | Base ~ unknown
|
||||
else if (isSubclass(c, nc))
|
||||
if (isSubclass(c, nc))
|
||||
return std::nullopt; // ~Base | Derived ~ ~Base | Derived
|
||||
else if (isSubclass(nc, c))
|
||||
return builtinTypes->unknownType; // ~Derived | Base ~ unknown
|
||||
else
|
||||
return left; // ~Base | Unrelated ~ ~Base
|
||||
}
|
||||
@ -777,22 +804,24 @@ TypeId TypeReducer::negationType(TypeId ty)
|
||||
if (!n)
|
||||
return arena->addType(NegationType{ty});
|
||||
|
||||
if (auto nn = get<NegationType>(n->ty))
|
||||
TypeId negatedTy = follow(n->ty);
|
||||
|
||||
if (auto nn = get<NegationType>(negatedTy))
|
||||
return nn->ty; // ~~T ~ T
|
||||
else if (get<NeverType>(n->ty))
|
||||
else if (get<NeverType>(negatedTy))
|
||||
return builtinTypes->unknownType; // ~never ~ unknown
|
||||
else if (get<UnknownType>(n->ty))
|
||||
else if (get<UnknownType>(negatedTy))
|
||||
return builtinTypes->neverType; // ~unknown ~ never
|
||||
else if (get<AnyType>(n->ty))
|
||||
else if (get<AnyType>(negatedTy))
|
||||
return builtinTypes->anyType; // ~any ~ any
|
||||
else if (auto ni = get<IntersectionType>(n->ty))
|
||||
else if (auto ni = get<IntersectionType>(negatedTy))
|
||||
{
|
||||
std::vector<TypeId> options;
|
||||
for (TypeId part : ni)
|
||||
options.push_back(negationType(arena->addType(NegationType{part})));
|
||||
return reduce(flatten<UnionType>(std::move(options))); // ~(T & U) ~ (~T | ~U)
|
||||
}
|
||||
else if (auto nu = get<UnionType>(n->ty))
|
||||
else if (auto nu = get<UnionType>(negatedTy))
|
||||
{
|
||||
std::vector<TypeId> parts;
|
||||
for (TypeId option : nu)
|
||||
@ -910,16 +939,26 @@ TypePackId TypeReducer::memoize(TypePackId tp, TypePackId reducedTp)
|
||||
|
||||
struct MarkCycles : TypeVisitor
|
||||
{
|
||||
DenseHashSet<TypeId> cyclicTypes{nullptr};
|
||||
DenseHashSet<const void*> cyclics{nullptr};
|
||||
|
||||
void cycle(TypeId ty) override
|
||||
{
|
||||
cyclicTypes.insert(ty);
|
||||
cyclics.insert(follow(ty));
|
||||
}
|
||||
|
||||
void cycle(TypePackId tp) override
|
||||
{
|
||||
cyclics.insert(follow(tp));
|
||||
}
|
||||
|
||||
bool visit(TypeId ty) override
|
||||
{
|
||||
return !cyclicTypes.find(ty);
|
||||
return !cyclics.find(follow(ty));
|
||||
}
|
||||
|
||||
bool visit(TypePackId tp) override
|
||||
{
|
||||
return !cyclics.find(follow(tp));
|
||||
}
|
||||
};
|
||||
|
||||
@ -942,8 +981,8 @@ std::optional<TypeId> TypeReduction::reduce(TypeId ty)
|
||||
return ty;
|
||||
else if (!options.allowTypeReductionsFromOtherArenas && ty->owningArena != arena)
|
||||
return ty;
|
||||
else if (auto ctx = memoizedTypes.find(ty); ctx && ctx->irreducible)
|
||||
return ctx->type;
|
||||
else if (auto memoized = memoizedof(ty))
|
||||
return *memoized;
|
||||
else if (hasExceededCartesianProductLimit(ty))
|
||||
return std::nullopt;
|
||||
|
||||
@ -952,7 +991,7 @@ std::optional<TypeId> TypeReduction::reduce(TypeId ty)
|
||||
MarkCycles finder;
|
||||
finder.traverse(ty);
|
||||
|
||||
TypeReducer reducer{arena, builtinTypes, handle, &memoizedTypes, &memoizedTypePacks, &finder.cyclicTypes};
|
||||
TypeReducer reducer{arena, builtinTypes, handle, &memoizedTypes, &memoizedTypePacks, &finder.cyclics};
|
||||
return reducer.reduce(ty);
|
||||
}
|
||||
catch (const RecursionLimitException&)
|
||||
@ -969,8 +1008,8 @@ std::optional<TypePackId> TypeReduction::reduce(TypePackId tp)
|
||||
return tp;
|
||||
else if (!options.allowTypeReductionsFromOtherArenas && tp->owningArena != arena)
|
||||
return tp;
|
||||
else if (auto ctx = memoizedTypePacks.find(tp); ctx && ctx->irreducible)
|
||||
return ctx->type;
|
||||
else if (auto memoized = memoizedof(tp))
|
||||
return *memoized;
|
||||
else if (hasExceededCartesianProductLimit(tp))
|
||||
return std::nullopt;
|
||||
|
||||
@ -979,7 +1018,7 @@ std::optional<TypePackId> TypeReduction::reduce(TypePackId tp)
|
||||
MarkCycles finder;
|
||||
finder.traverse(tp);
|
||||
|
||||
TypeReducer reducer{arena, builtinTypes, handle, &memoizedTypes, &memoizedTypePacks, &finder.cyclicTypes};
|
||||
TypeReducer reducer{arena, builtinTypes, handle, &memoizedTypes, &memoizedTypePacks, &finder.cyclics};
|
||||
return reducer.reduce(tp);
|
||||
}
|
||||
catch (const RecursionLimitException&)
|
||||
@ -1000,6 +1039,13 @@ std::optional<TypeFun> TypeReduction::reduce(const TypeFun& fun)
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
TypeReduction TypeReduction::fork(NotNull<TypeArena> arena, const TypeReductionOptions& opts) const
|
||||
{
|
||||
TypeReduction child{arena, builtinTypes, handle, opts};
|
||||
child.parent = this;
|
||||
return child;
|
||||
}
|
||||
|
||||
size_t TypeReduction::cartesianProductSize(TypeId ty) const
|
||||
{
|
||||
ty = follow(ty);
|
||||
@ -1047,4 +1093,24 @@ bool TypeReduction::hasExceededCartesianProductLimit(TypePackId tp) const
|
||||
return false;
|
||||
}
|
||||
|
||||
std::optional<TypeId> TypeReduction::memoizedof(TypeId ty) const
|
||||
{
|
||||
if (auto ctx = memoizedTypes.find(ty); ctx && ctx->irreducible)
|
||||
return ctx->type;
|
||||
else if (parent)
|
||||
return parent->memoizedof(ty);
|
||||
else
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<TypePackId> TypeReduction::memoizedof(TypePackId tp) const
|
||||
{
|
||||
if (auto ctx = memoizedTypePacks.find(tp); ctx && ctx->irreducible)
|
||||
return ctx->type;
|
||||
else if (parent)
|
||||
return parent->memoizedof(tp);
|
||||
else
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
} // namespace Luau
|
||||
|
@ -22,6 +22,7 @@ LUAU_FASTFLAGVARIABLE(LuauInstantiateInSubtyping, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauScalarShapeUnifyToMtOwner2, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUninhabitedSubAnything2, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauMaintainScopesInUnifier, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTableUnifyInstantiationFix, false)
|
||||
LUAU_FASTFLAG(LuauClassTypeVarsInSubstitution)
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
LUAU_FASTFLAG(LuauNegatedFunctionTypes)
|
||||
@ -600,11 +601,8 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
|
||||
else if (log.getMutable<ClassType>(subTy))
|
||||
tryUnifyWithClass(subTy, superTy, /*reversed*/ true);
|
||||
|
||||
else if (log.get<NegationType>(superTy))
|
||||
tryUnifyTypeWithNegation(subTy, superTy);
|
||||
|
||||
else if (log.get<NegationType>(subTy))
|
||||
tryUnifyNegationWithType(subTy, superTy);
|
||||
else if (log.get<NegationType>(superTy) || log.get<NegationType>(subTy))
|
||||
tryUnifyNegations(subTy, superTy);
|
||||
|
||||
else if (FFlag::LuauUninhabitedSubAnything2 && !normalizer->isInhabited(subTy))
|
||||
{
|
||||
@ -857,6 +855,22 @@ void Unifier::tryUnifyTypeWithIntersection(TypeId subTy, TypeId superTy, const I
|
||||
reportError(location, TypeMismatch{superTy, subTy, "Not all intersection parts are compatible.", *firstFailedOption, mismatchContext()});
|
||||
}
|
||||
|
||||
struct NegationTypeFinder : TypeOnceVisitor
|
||||
{
|
||||
bool found = false;
|
||||
|
||||
bool visit(TypeId ty) override
|
||||
{
|
||||
return !found;
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const NegationType&) override
|
||||
{
|
||||
found = true;
|
||||
return !found;
|
||||
}
|
||||
};
|
||||
|
||||
void Unifier::tryUnifyIntersectionWithType(TypeId subTy, const IntersectionType* uv, TypeId superTy, bool cacheEnabled, bool isFunctionCall)
|
||||
{
|
||||
// A & B <: T if A <: T or B <: T
|
||||
@ -881,6 +895,28 @@ void Unifier::tryUnifyIntersectionWithType(TypeId subTy, const IntersectionType*
|
||||
}
|
||||
}
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution && normalize)
|
||||
{
|
||||
// Sometimes a negation type is inside one of the types, e.g. { p: number } & { p: ~number }.
|
||||
NegationTypeFinder finder;
|
||||
finder.traverse(subTy);
|
||||
|
||||
if (finder.found)
|
||||
{
|
||||
// It is possible that A & B <: T even though A </: T and B </: T
|
||||
// for example (string?) & ~nil <: string.
|
||||
// We deal with this by type normalization.
|
||||
const NormalizedType* subNorm = normalizer->normalize(subTy);
|
||||
const NormalizedType* superNorm = normalizer->normalize(superTy);
|
||||
if (subNorm && superNorm)
|
||||
tryUnifyNormalizedTypes(subTy, superTy, *subNorm, *superNorm, "none of the intersection parts are compatible");
|
||||
else
|
||||
reportError(location, UnificationTooComplex{});
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<TxnLog> logs;
|
||||
|
||||
for (size_t i = 0; i < uv->parts.size(); ++i)
|
||||
@ -1728,9 +1764,10 @@ struct Resetter
|
||||
|
||||
void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
|
||||
{
|
||||
TypeId activeSubTy = subTy;
|
||||
TableType* superTable = log.getMutable<TableType>(superTy);
|
||||
TableType* subTable = log.getMutable<TableType>(subTy);
|
||||
TableType* instantiatedSubTable = subTable;
|
||||
TableType* instantiatedSubTable = subTable; // TODO: remove with FFlagLuauTableUnifyInstantiationFix
|
||||
|
||||
if (!superTable || !subTable)
|
||||
ice("passed non-table types to unifyTables");
|
||||
@ -1747,8 +1784,16 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
|
||||
std::optional<TypeId> instantiated = instantiation.substitute(subTy);
|
||||
if (instantiated.has_value())
|
||||
{
|
||||
subTable = log.getMutable<TableType>(*instantiated);
|
||||
instantiatedSubTable = subTable;
|
||||
if (FFlag::LuauTableUnifyInstantiationFix)
|
||||
{
|
||||
activeSubTy = *instantiated;
|
||||
subTable = log.getMutable<TableType>(activeSubTy);
|
||||
}
|
||||
else
|
||||
{
|
||||
subTable = log.getMutable<TableType>(*instantiated);
|
||||
instantiatedSubTable = subTable;
|
||||
}
|
||||
|
||||
if (!subTable)
|
||||
ice("instantiation made a table type into a non-table type in tryUnifyTables");
|
||||
@ -1838,7 +1883,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
|
||||
}
|
||||
else if (subTable->state == TableState::Free)
|
||||
{
|
||||
PendingType* pendingSub = log.queue(subTy);
|
||||
PendingType* pendingSub = log.queue(activeSubTy);
|
||||
TableType* ttv = getMutable<TableType>(pendingSub);
|
||||
LUAU_ASSERT(ttv);
|
||||
ttv->props[name] = prop;
|
||||
@ -1851,12 +1896,12 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
|
||||
// table. If we detect that this has happened, we start over, with the updated
|
||||
// txn log.
|
||||
TypeId superTyNew = FFlag::LuauScalarShapeUnifyToMtOwner2 ? log.follow(superTy) : superTy;
|
||||
TypeId subTyNew = FFlag::LuauScalarShapeUnifyToMtOwner2 ? log.follow(subTy) : subTy;
|
||||
TypeId subTyNew = FFlag::LuauScalarShapeUnifyToMtOwner2 ? log.follow(activeSubTy) : activeSubTy;
|
||||
|
||||
if (FFlag::LuauScalarShapeUnifyToMtOwner2)
|
||||
{
|
||||
// If one of the types stopped being a table altogether, we need to restart from the top
|
||||
if ((superTy != superTyNew || subTy != subTyNew) && errors.empty())
|
||||
if ((superTy != superTyNew || activeSubTy != subTyNew) && errors.empty())
|
||||
return tryUnify(subTy, superTy, false, isIntersection);
|
||||
}
|
||||
|
||||
@ -1864,7 +1909,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
|
||||
TableType* newSuperTable = log.getMutable<TableType>(superTyNew);
|
||||
TableType* newSubTable = log.getMutable<TableType>(subTyNew);
|
||||
|
||||
if (superTable != newSuperTable || (subTable != newSubTable && subTable != instantiatedSubTable))
|
||||
if (superTable != newSuperTable || (subTable != newSubTable && (FFlag::LuauTableUnifyInstantiationFix || subTable != instantiatedSubTable)))
|
||||
{
|
||||
if (errors.empty())
|
||||
return tryUnifyTables(subTy, superTy, isIntersection);
|
||||
@ -1922,12 +1967,12 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
|
||||
extraProperties.push_back(name);
|
||||
|
||||
TypeId superTyNew = FFlag::LuauScalarShapeUnifyToMtOwner2 ? log.follow(superTy) : superTy;
|
||||
TypeId subTyNew = FFlag::LuauScalarShapeUnifyToMtOwner2 ? log.follow(subTy) : subTy;
|
||||
TypeId subTyNew = FFlag::LuauScalarShapeUnifyToMtOwner2 ? log.follow(activeSubTy) : activeSubTy;
|
||||
|
||||
if (FFlag::LuauScalarShapeUnifyToMtOwner2)
|
||||
{
|
||||
// If one of the types stopped being a table altogether, we need to restart from the top
|
||||
if ((superTy != superTyNew || subTy != subTyNew) && errors.empty())
|
||||
if ((superTy != superTyNew || activeSubTy != subTyNew) && errors.empty())
|
||||
return tryUnify(subTy, superTy, false, isIntersection);
|
||||
}
|
||||
|
||||
@ -1936,7 +1981,8 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
|
||||
// txn log.
|
||||
TableType* newSuperTable = log.getMutable<TableType>(superTyNew);
|
||||
TableType* newSubTable = log.getMutable<TableType>(subTyNew);
|
||||
if (superTable != newSuperTable || (subTable != newSubTable && subTable != instantiatedSubTable))
|
||||
|
||||
if (superTable != newSuperTable || (subTable != newSubTable && (FFlag::LuauTableUnifyInstantiationFix || subTable != instantiatedSubTable)))
|
||||
{
|
||||
if (errors.empty())
|
||||
return tryUnifyTables(subTy, superTy, isIntersection);
|
||||
@ -1992,7 +2038,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
|
||||
if (FFlag::LuauScalarShapeUnifyToMtOwner2)
|
||||
{
|
||||
superTable = log.getMutable<TableType>(log.follow(superTy));
|
||||
subTable = log.getMutable<TableType>(log.follow(subTy));
|
||||
subTable = log.getMutable<TableType>(log.follow(activeSubTy));
|
||||
|
||||
if (!superTable || !subTable)
|
||||
return;
|
||||
@ -2000,7 +2046,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
|
||||
else
|
||||
{
|
||||
superTable = log.getMutable<TableType>(superTy);
|
||||
subTable = log.getMutable<TableType>(subTy);
|
||||
subTable = log.getMutable<TableType>(activeSubTy);
|
||||
}
|
||||
|
||||
if (!missingProperties.empty())
|
||||
@ -2313,11 +2359,10 @@ void Unifier::tryUnifyWithClass(TypeId subTy, TypeId superTy, bool reversed)
|
||||
return fail();
|
||||
}
|
||||
|
||||
void Unifier::tryUnifyTypeWithNegation(TypeId subTy, TypeId superTy)
|
||||
void Unifier::tryUnifyNegations(TypeId subTy, TypeId superTy)
|
||||
{
|
||||
const NegationType* ntv = get<NegationType>(superTy);
|
||||
if (!ntv)
|
||||
ice("tryUnifyTypeWithNegation superTy must be a negation type");
|
||||
if (!log.get<NegationType>(subTy) && !log.get<NegationType>(superTy))
|
||||
ice("tryUnifyNegations superTy or subTy must be a negation type");
|
||||
|
||||
const NormalizedType* subNorm = normalizer->normalize(subTy);
|
||||
const NormalizedType* superNorm = normalizer->normalize(superTy);
|
||||
@ -2331,16 +2376,6 @@ void Unifier::tryUnifyTypeWithNegation(TypeId subTy, TypeId superTy)
|
||||
reportError(location, TypeMismatch{superTy, subTy, mismatchContext()});
|
||||
}
|
||||
|
||||
void Unifier::tryUnifyNegationWithType(TypeId subTy, TypeId superTy)
|
||||
{
|
||||
const NegationType* ntv = get<NegationType>(subTy);
|
||||
if (!ntv)
|
||||
ice("tryUnifyNegationWithType subTy must be a negation type");
|
||||
|
||||
// TODO: ~T </: U iff T <: U
|
||||
reportError(location, TypeMismatch{superTy, subTy, mismatchContext()});
|
||||
}
|
||||
|
||||
static void queueTypePack(std::vector<TypeId>& queue, DenseHashSet<TypePackId>& seenTypePacks, Unifier& state, TypePackId a, TypePackId anyTypePack)
|
||||
{
|
||||
while (true)
|
||||
|
@ -251,7 +251,6 @@ enum class ConstantNumberParseResult
|
||||
Malformed,
|
||||
BinOverflow,
|
||||
HexOverflow,
|
||||
DoublePrefix,
|
||||
};
|
||||
|
||||
class AstExprConstantNumber : public AstExpr
|
||||
|
@ -14,15 +14,8 @@
|
||||
LUAU_FASTINTVARIABLE(LuauRecursionLimit, 1000)
|
||||
LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauErrorDoubleHexPrefix, false)
|
||||
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuaReportParseIntegerIssues, false)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauParserErrorsOnMissingDefaultTypePackArgument, false)
|
||||
|
||||
bool lua_telemetry_parsed_out_of_range_bin_integer = false;
|
||||
bool lua_telemetry_parsed_out_of_range_hex_integer = false;
|
||||
bool lua_telemetry_parsed_double_prefix_hex_integer = false;
|
||||
|
||||
#define ERROR_INVALID_INTERP_DOUBLE_BRACE "Double braces are not permitted within interpolated strings. Did you mean '\\{'?"
|
||||
|
||||
namespace Luau
|
||||
@ -2093,17 +2086,7 @@ static ConstantNumberParseResult parseInteger(double& result, const char* data,
|
||||
value = strtoull(data, &end, base);
|
||||
|
||||
if (errno == ERANGE)
|
||||
{
|
||||
if (DFFlag::LuaReportParseIntegerIssues)
|
||||
{
|
||||
if (base == 2)
|
||||
lua_telemetry_parsed_out_of_range_bin_integer = true;
|
||||
else
|
||||
lua_telemetry_parsed_out_of_range_hex_integer = true;
|
||||
}
|
||||
|
||||
return base == 2 ? ConstantNumberParseResult::BinOverflow : ConstantNumberParseResult::HexOverflow;
|
||||
}
|
||||
}
|
||||
|
||||
return ConstantNumberParseResult::Ok;
|
||||
@ -2117,18 +2100,7 @@ static ConstantNumberParseResult parseDouble(double& result, const char* data)
|
||||
|
||||
// hexadecimal literal
|
||||
if (data[0] == '0' && (data[1] == 'x' || data[1] == 'X') && data[2])
|
||||
{
|
||||
if (!FFlag::LuauErrorDoubleHexPrefix && data[2] == '0' && (data[3] == 'x' || data[3] == 'X'))
|
||||
{
|
||||
if (DFFlag::LuaReportParseIntegerIssues)
|
||||
lua_telemetry_parsed_double_prefix_hex_integer = true;
|
||||
|
||||
ConstantNumberParseResult parseResult = parseInteger(result, data + 2, 16);
|
||||
return parseResult == ConstantNumberParseResult::Malformed ? parseResult : ConstantNumberParseResult::DoublePrefix;
|
||||
}
|
||||
|
||||
return parseInteger(result, data, 16); // pass in '0x' prefix, it's handled by 'strtoull'
|
||||
}
|
||||
|
||||
char* end = nullptr;
|
||||
double value = strtod(data, &end);
|
||||
|
@ -109,8 +109,10 @@ public:
|
||||
void vdivsd(OperandX64 dst, OperandX64 src1, OperandX64 src2);
|
||||
|
||||
void vandpd(OperandX64 dst, OperandX64 src1, OperandX64 src2);
|
||||
void vandnpd(OperandX64 dst, OperandX64 src1, OperandX64 src2);
|
||||
|
||||
void vxorpd(OperandX64 dst, OperandX64 src1, OperandX64 src2);
|
||||
void vorpd(OperandX64 dst, OperandX64 src1, OperandX64 src2);
|
||||
|
||||
void vucomisd(OperandX64 src1, OperandX64 src2);
|
||||
|
||||
@ -137,6 +139,10 @@ public:
|
||||
void vmaxsd(OperandX64 dst, OperandX64 src1, OperandX64 src2);
|
||||
void vminsd(OperandX64 dst, OperandX64 src1, OperandX64 src2);
|
||||
|
||||
void vcmpltsd(OperandX64 dst, OperandX64 src1, OperandX64 src2);
|
||||
|
||||
void vblendvpd(RegisterX64 dst, RegisterX64 src1, OperandX64 mask, RegisterX64 src3);
|
||||
|
||||
|
||||
// Run final checks
|
||||
void finalize();
|
||||
@ -152,6 +158,7 @@ public:
|
||||
OperandX64 f32(float value);
|
||||
OperandX64 f64(double value);
|
||||
OperandX64 f32x4(float x, float y, float z, float w);
|
||||
OperandX64 f64x2(double x, double y);
|
||||
OperandX64 bytes(const void* ptr, size_t size, size_t align = 8);
|
||||
|
||||
void logAppend(const char* fmt, ...) LUAU_PRINTF_ATTR(2, 3);
|
||||
|
@ -3,8 +3,7 @@
|
||||
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/Bytecode.h"
|
||||
|
||||
#include "IrData.h"
|
||||
#include "Luau/IrData.h"
|
||||
|
||||
#include <vector>
|
||||
|
@ -1,7 +1,7 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "IrData.h"
|
||||
#include "Luau/IrData.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
@ -3,8 +3,7 @@
|
||||
|
||||
#include "Luau/Bytecode.h"
|
||||
#include "Luau/Common.h"
|
||||
|
||||
#include "IrData.h"
|
||||
#include "Luau/IrData.h"
|
||||
|
||||
namespace Luau
|
||||
{
|
@ -638,11 +638,21 @@ void AssemblyBuilderX64::vandpd(OperandX64 dst, OperandX64 src1, OperandX64 src2
|
||||
placeAvx("vandpd", dst, src1, src2, 0x54, false, AVX_0F, AVX_66);
|
||||
}
|
||||
|
||||
void AssemblyBuilderX64::vandnpd(OperandX64 dst, OperandX64 src1, OperandX64 src2)
|
||||
{
|
||||
placeAvx("vandnpd", dst, src1, src2, 0x55, false, AVX_0F, AVX_66);
|
||||
}
|
||||
|
||||
void AssemblyBuilderX64::vxorpd(OperandX64 dst, OperandX64 src1, OperandX64 src2)
|
||||
{
|
||||
placeAvx("vxorpd", dst, src1, src2, 0x57, false, AVX_0F, AVX_66);
|
||||
}
|
||||
|
||||
void AssemblyBuilderX64::vorpd(OperandX64 dst, OperandX64 src1, OperandX64 src2)
|
||||
{
|
||||
placeAvx("vorpd", dst, src1, src2, 0x56, false, AVX_0F, AVX_66);
|
||||
}
|
||||
|
||||
void AssemblyBuilderX64::vucomisd(OperandX64 src1, OperandX64 src2)
|
||||
{
|
||||
placeAvx("vucomisd", src1, src2, 0x2e, false, AVX_0F, AVX_66);
|
||||
@ -753,6 +763,17 @@ void AssemblyBuilderX64::vminsd(OperandX64 dst, OperandX64 src1, OperandX64 src2
|
||||
placeAvx("vminsd", dst, src1, src2, 0x5d, false, AVX_0F, AVX_F2);
|
||||
}
|
||||
|
||||
void AssemblyBuilderX64::vcmpltsd(OperandX64 dst, OperandX64 src1, OperandX64 src2)
|
||||
{
|
||||
placeAvx("vcmpltsd", dst, src1, src2, 0x01, 0xc2, false, AVX_0F, AVX_F2);
|
||||
}
|
||||
|
||||
void AssemblyBuilderX64::vblendvpd(RegisterX64 dst, RegisterX64 src1, OperandX64 mask, RegisterX64 src3)
|
||||
{
|
||||
// bits [7:4] of imm8 are used to select register for operand 4
|
||||
placeAvx("vblendvpd", dst, src1, mask, src3.index << 4, 0x4b, false, AVX_0F3A, AVX_66);
|
||||
}
|
||||
|
||||
void AssemblyBuilderX64::finalize()
|
||||
{
|
||||
code.resize(codePos - code.data());
|
||||
@ -834,6 +855,14 @@ OperandX64 AssemblyBuilderX64::f32x4(float x, float y, float z, float w)
|
||||
return OperandX64(SizeX64::xmmword, noreg, 1, rip, int32_t(pos - data.size()));
|
||||
}
|
||||
|
||||
OperandX64 AssemblyBuilderX64::f64x2(double x, double y)
|
||||
{
|
||||
size_t pos = allocateData(16, 16);
|
||||
writef64(&data[pos], x);
|
||||
writef64(&data[pos + 8], y);
|
||||
return OperandX64(SizeX64::xmmword, noreg, 1, rip, int32_t(pos - data.size()));
|
||||
}
|
||||
|
||||
OperandX64 AssemblyBuilderX64::bytes(const void* ptr, size_t size, size_t align)
|
||||
{
|
||||
size_t pos = allocateData(size, align);
|
||||
|
@ -5,6 +5,8 @@
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/CodeAllocator.h"
|
||||
#include "Luau/CodeBlockUnwind.h"
|
||||
#include "Luau/IrAnalysis.h"
|
||||
#include "Luau/IrBuilder.h"
|
||||
#include "Luau/UnwindBuilder.h"
|
||||
#include "Luau/UnwindBuilderDwarf2.h"
|
||||
#include "Luau/UnwindBuilderWin.h"
|
||||
@ -13,8 +15,6 @@
|
||||
#include "CodeGenX64.h"
|
||||
#include "EmitCommonX64.h"
|
||||
#include "EmitInstructionX64.h"
|
||||
#include "IrAnalysis.h"
|
||||
#include "IrBuilder.h"
|
||||
#include "IrLoweringX64.h"
|
||||
#include "NativeState.h"
|
||||
|
||||
|
@ -424,6 +424,197 @@ BuiltinImplResult emitBuiltinMathLog(AssemblyBuilderX64& build, int nparams, int
|
||||
return {BuiltinImplType::UsesFallback, 1};
|
||||
}
|
||||
|
||||
|
||||
BuiltinImplResult emitBuiltinMathLdexp(AssemblyBuilderX64& build, int nparams, int ra, int arg, OperandX64 args, int nresults, Label& fallback)
|
||||
{
|
||||
if (nparams < 2 || nresults > 1)
|
||||
return {BuiltinImplType::None, -1};
|
||||
|
||||
if (build.logText)
|
||||
build.logAppend("; inlined LBF_MATH_LDEXP\n");
|
||||
|
||||
jumpIfTagIsNot(build, arg, LUA_TNUMBER, fallback);
|
||||
|
||||
// TODO: jumpIfTagIsNot can be generalized to take OperandX64 and then we can use it here; let's wait until we see this more though
|
||||
build.cmp(dword[args + offsetof(TValue, tt)], LUA_TNUMBER);
|
||||
build.jcc(ConditionX64::NotEqual, fallback);
|
||||
|
||||
build.vmovsd(xmm0, luauRegValue(arg));
|
||||
|
||||
if (build.abi == ABIX64::Windows)
|
||||
build.vcvttsd2si(rArg2, qword[args + offsetof(TValue, value)]);
|
||||
else
|
||||
build.vcvttsd2si(rArg1, qword[args + offsetof(TValue, value)]);
|
||||
|
||||
build.call(qword[rNativeContext + offsetof(NativeContext, libm_ldexp)]);
|
||||
|
||||
build.vmovsd(luauRegValue(ra), xmm0);
|
||||
|
||||
if (ra != arg)
|
||||
build.mov(luauRegTag(ra), LUA_TNUMBER);
|
||||
|
||||
return {BuiltinImplType::UsesFallback, 1};
|
||||
}
|
||||
|
||||
BuiltinImplResult emitBuiltinMathRound(AssemblyBuilderX64& build, int nparams, int ra, int arg, OperandX64 args, int nresults, Label& fallback)
|
||||
{
|
||||
if (nparams < 1 || nresults > 1)
|
||||
return {BuiltinImplType::None, -1};
|
||||
|
||||
if (build.logText)
|
||||
build.logAppend("; inlined LBF_MATH_ROUND\n");
|
||||
|
||||
jumpIfTagIsNot(build, arg, LUA_TNUMBER, fallback);
|
||||
|
||||
build.vmovsd(xmm0, luauRegValue(arg));
|
||||
build.vandpd(xmm1, xmm0, build.f64x2(-0.0, -0.0));
|
||||
build.vmovsd(xmm2, build.i64(0x3fdfffffffffffff)); // 0.49999999999999994
|
||||
build.vorpd(xmm1, xmm1, xmm2);
|
||||
build.vaddsd(xmm0, xmm0, xmm1);
|
||||
build.vroundsd(xmm0, xmm0, xmm0, RoundingModeX64::RoundToZero);
|
||||
|
||||
build.vmovsd(luauRegValue(ra), xmm0);
|
||||
|
||||
if (ra != arg)
|
||||
build.mov(luauRegTag(ra), LUA_TNUMBER);
|
||||
|
||||
return {BuiltinImplType::UsesFallback, 1};
|
||||
}
|
||||
|
||||
BuiltinImplResult emitBuiltinMathFrexp(AssemblyBuilderX64& build, int nparams, int ra, int arg, OperandX64 args, int nresults, Label& fallback)
|
||||
{
|
||||
if (nparams < 1 || nresults > 2)
|
||||
return {BuiltinImplType::None, -1};
|
||||
|
||||
if (build.logText)
|
||||
build.logAppend("; inlined LBF_MATH_FREXP\n");
|
||||
|
||||
jumpIfTagIsNot(build, arg, LUA_TNUMBER, fallback);
|
||||
|
||||
build.vmovsd(xmm0, luauRegValue(arg));
|
||||
|
||||
if (build.abi == ABIX64::Windows)
|
||||
build.lea(rArg2, sTemporarySlot);
|
||||
else
|
||||
build.lea(rArg1, sTemporarySlot);
|
||||
|
||||
build.call(qword[rNativeContext + offsetof(NativeContext, libm_frexp)]);
|
||||
|
||||
build.vmovsd(luauRegValue(ra), xmm0);
|
||||
|
||||
if (ra != arg)
|
||||
build.mov(luauRegTag(ra), LUA_TNUMBER);
|
||||
|
||||
build.vcvtsi2sd(xmm0, xmm0, dword[sTemporarySlot + 0]);
|
||||
build.vmovsd(luauRegValue(ra + 1), xmm0);
|
||||
build.mov(luauRegTag(ra + 1), LUA_TNUMBER);
|
||||
|
||||
return {BuiltinImplType::UsesFallback, 2};
|
||||
}
|
||||
|
||||
BuiltinImplResult emitBuiltinMathModf(AssemblyBuilderX64& build, int nparams, int ra, int arg, OperandX64 args, int nresults, Label& fallback)
|
||||
{
|
||||
if (nparams < 1 || nresults > 2)
|
||||
return {BuiltinImplType::None, -1};
|
||||
|
||||
if (build.logText)
|
||||
build.logAppend("; inlined LBF_MATH_MODF\n");
|
||||
|
||||
jumpIfTagIsNot(build, arg, LUA_TNUMBER, fallback);
|
||||
|
||||
build.vmovsd(xmm0, luauRegValue(arg));
|
||||
|
||||
if (build.abi == ABIX64::Windows)
|
||||
build.lea(rArg2, sTemporarySlot);
|
||||
else
|
||||
build.lea(rArg1, sTemporarySlot);
|
||||
|
||||
build.call(qword[rNativeContext + offsetof(NativeContext, libm_modf)]);
|
||||
|
||||
build.vmovsd(xmm1, qword[sTemporarySlot + 0]);
|
||||
build.vmovsd(luauRegValue(ra), xmm1);
|
||||
|
||||
if (ra != arg)
|
||||
build.mov(luauRegTag(ra), LUA_TNUMBER);
|
||||
|
||||
build.vmovsd(luauRegValue(ra + 1), xmm0);
|
||||
build.mov(luauRegTag(ra + 1), LUA_TNUMBER);
|
||||
|
||||
return {BuiltinImplType::UsesFallback, 2};
|
||||
}
|
||||
|
||||
BuiltinImplResult emitBuiltinMathSign(AssemblyBuilderX64& build, int nparams, int ra, int arg, OperandX64 args, int nresults, Label& fallback)
|
||||
{
|
||||
if (nparams < 1 || nresults > 1)
|
||||
return {BuiltinImplType::None, -1};
|
||||
|
||||
if (build.logText)
|
||||
build.logAppend("; inlined LBF_MATH_SIGN\n");
|
||||
|
||||
jumpIfTagIsNot(build, arg, LUA_TNUMBER, fallback);
|
||||
|
||||
build.vmovsd(xmm0, luauRegValue(arg));
|
||||
build.vxorpd(xmm1, xmm1, xmm1);
|
||||
|
||||
// Set xmm2 to -1 if arg < 0, else 0
|
||||
build.vcmpltsd(xmm2, xmm0, xmm1);
|
||||
build.vmovsd(xmm3, build.f64(-1));
|
||||
build.vandpd(xmm2, xmm2, xmm3);
|
||||
|
||||
// Set mask bit to 1 if 0 < arg, else 0
|
||||
build.vcmpltsd(xmm0, xmm1, xmm0);
|
||||
|
||||
// Result = (mask-bit == 1) ? 1.0 : xmm2
|
||||
// If arg < 0 then xmm2 is -1 and mask-bit is 0, result is -1
|
||||
// If arg == 0 then xmm2 is 0 and mask-bit is 0, result is 0
|
||||
// If arg > 0 then xmm2 is 0 and mask-bit is 1, result is 1
|
||||
build.vblendvpd(xmm0, xmm2, build.f64x2(1, 1), xmm0);
|
||||
|
||||
build.vmovsd(luauRegValue(ra), xmm0);
|
||||
|
||||
if (ra != arg)
|
||||
build.mov(luauRegTag(ra), LUA_TNUMBER);
|
||||
|
||||
return {BuiltinImplType::UsesFallback, 1};
|
||||
}
|
||||
|
||||
BuiltinImplResult emitBuiltinMathClamp(AssemblyBuilderX64& build, int nparams, int ra, int arg, OperandX64 args, int nresults, Label& fallback)
|
||||
{
|
||||
if (nparams < 3 || nresults > 1)
|
||||
return {BuiltinImplType::None, -1};
|
||||
|
||||
if (build.logText)
|
||||
build.logAppend("; inlined LBF_MATH_CLAMP\n");
|
||||
|
||||
jumpIfTagIsNot(build, arg, LUA_TNUMBER, fallback);
|
||||
|
||||
// TODO: jumpIfTagIsNot can be generalized to take OperandX64 and then we can use it here; let's wait until we see this more though
|
||||
build.cmp(dword[args + offsetof(TValue, tt)], LUA_TNUMBER);
|
||||
build.jcc(ConditionX64::NotEqual, fallback);
|
||||
|
||||
// TODO: jumpIfTagIsNot can be generalized to take OperandX64 and then we can use it here; let's wait until we see this more though
|
||||
build.cmp(dword[args + sizeof(TValue) + offsetof(TValue, tt)], LUA_TNUMBER);
|
||||
build.jcc(ConditionX64::NotEqual, fallback);
|
||||
|
||||
RegisterX64 min = xmm1;
|
||||
RegisterX64 max = xmm2;
|
||||
build.vmovsd(min, qword[args + offsetof(TValue, value)]);
|
||||
build.vmovsd(max, qword[args + sizeof(TValue) + offsetof(TValue, value)]);
|
||||
|
||||
jumpOnNumberCmp(build, noreg, min, max, ConditionX64::NotLessEqual, fallback);
|
||||
|
||||
build.vmaxsd(xmm0, min, luauRegValue(arg));
|
||||
build.vminsd(xmm0, max, xmm0);
|
||||
|
||||
build.vmovsd(luauRegValue(ra), xmm0);
|
||||
|
||||
if (ra != arg)
|
||||
build.mov(luauRegTag(ra), LUA_TNUMBER);
|
||||
|
||||
return {BuiltinImplType::UsesFallback, 1};
|
||||
}
|
||||
|
||||
|
||||
BuiltinImplResult emitBuiltin(AssemblyBuilderX64& build, int bfid, int nparams, int ra, int arg, OperandX64 args, int nresults, Label& fallback)
|
||||
{
|
||||
switch (bfid)
|
||||
@ -476,6 +667,18 @@ BuiltinImplResult emitBuiltin(AssemblyBuilderX64& build, int bfid, int nparams,
|
||||
return emitBuiltinMathLog10(build, nparams, ra, arg, args, nresults, fallback);
|
||||
case LBF_MATH_LOG:
|
||||
return emitBuiltinMathLog(build, nparams, ra, arg, args, nresults, fallback);
|
||||
case LBF_MATH_LDEXP:
|
||||
return emitBuiltinMathLdexp(build, nparams, ra, arg, args, nresults, fallback);
|
||||
case LBF_MATH_ROUND:
|
||||
return emitBuiltinMathRound(build, nparams, ra, arg, args, nresults, fallback);
|
||||
case LBF_MATH_FREXP:
|
||||
return emitBuiltinMathFrexp(build, nparams, ra, arg, args, nresults, fallback);
|
||||
case LBF_MATH_MODF:
|
||||
return emitBuiltinMathModf(build, nparams, ra, arg, args, nresults, fallback);
|
||||
case LBF_MATH_SIGN:
|
||||
return emitBuiltinMathSign(build, nparams, ra, arg, args, nresults, fallback);
|
||||
case LBF_MATH_CLAMP:
|
||||
return emitBuiltinMathClamp(build, nparams, ra, arg, args, nresults, fallback);
|
||||
default:
|
||||
return {BuiltinImplType::None, -1};
|
||||
}
|
||||
|
@ -38,6 +38,7 @@ constexpr unsigned kLocalsSize = 24; // 3 extra slots for our custom locals
|
||||
|
||||
constexpr OperandX64 sClosure = qword[rsp + kStackSize + 0]; // Closure* cl
|
||||
constexpr OperandX64 sCode = qword[rsp + kStackSize + 8]; // Instruction* code
|
||||
constexpr OperandX64 sTemporarySlot = addr[rsp + kStackSize + 16];
|
||||
|
||||
// TODO: These should be replaced with a portable call function that checks the ABI at runtime and reorders moves accordingly to avoid conflicts
|
||||
#if defined(_WIN32)
|
||||
|
@ -1,8 +1,8 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "IrAnalysis.h"
|
||||
#include "Luau/IrAnalysis.h"
|
||||
|
||||
#include "IrData.h"
|
||||
#include "IrUtils.h"
|
||||
#include "Luau/IrData.h"
|
||||
#include "Luau/IrUtils.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "IrBuilder.h"
|
||||
#include "Luau/IrBuilder.h"
|
||||
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/IrUtils.h"
|
||||
|
||||
#include "CustomExecUtils.h"
|
||||
#include "IrTranslation.h"
|
||||
#include "IrUtils.h"
|
||||
|
||||
#include "lapi.h"
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "IrDump.h"
|
||||
#include "Luau/IrDump.h"
|
||||
|
||||
#include "IrUtils.h"
|
||||
#include "Luau/IrUtils.h"
|
||||
|
||||
#include "lua.h"
|
||||
|
||||
|
@ -3,11 +3,11 @@
|
||||
|
||||
#include "Luau/CodeGen.h"
|
||||
#include "Luau/DenseHash.h"
|
||||
#include "Luau/IrDump.h"
|
||||
#include "Luau/IrUtils.h"
|
||||
|
||||
#include "EmitCommonX64.h"
|
||||
#include "EmitInstructionX64.h"
|
||||
#include "IrDump.h"
|
||||
#include "IrUtils.h"
|
||||
#include "NativeState.h"
|
||||
|
||||
#include "lstate.h"
|
||||
|
@ -2,8 +2,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "Luau/AssemblyBuilderX64.h"
|
||||
|
||||
#include "IrData.h"
|
||||
#include "Luau/IrData.h"
|
||||
|
||||
#include <array>
|
||||
#include <initializer_list>
|
||||
|
@ -2,8 +2,7 @@
|
||||
#include "IrTranslation.h"
|
||||
|
||||
#include "Luau/Bytecode.h"
|
||||
|
||||
#include "IrBuilder.h"
|
||||
#include "Luau/IrBuilder.h"
|
||||
|
||||
#include "lobject.h"
|
||||
#include "ltm.h"
|
||||
|
@ -87,6 +87,10 @@ void initHelperFunctions(NativeState& data)
|
||||
data.context.libm_log = log;
|
||||
data.context.libm_log2 = log2;
|
||||
data.context.libm_log10 = log10;
|
||||
data.context.libm_ldexp = ldexp;
|
||||
data.context.libm_round = round;
|
||||
data.context.libm_frexp = frexp;
|
||||
data.context.libm_modf = modf;
|
||||
|
||||
data.context.libm_asin = asin;
|
||||
data.context.libm_sin = sin;
|
||||
|
@ -96,6 +96,10 @@ struct NativeContext
|
||||
double (*libm_log)(double) = nullptr;
|
||||
double (*libm_log2)(double) = nullptr;
|
||||
double (*libm_log10)(double) = nullptr;
|
||||
double (*libm_ldexp)(double, int) = nullptr;
|
||||
double (*libm_round)(double) = nullptr;
|
||||
double (*libm_frexp)(double, int*) = nullptr;
|
||||
double (*libm_modf)(double, double*) = nullptr;
|
||||
|
||||
// Helper functions
|
||||
bool (*forgLoopNodeIter)(lua_State* L, Table* h, int index, TValue* ra) = nullptr;
|
||||
|
@ -25,10 +25,6 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineThreshold, 25)
|
||||
LUAU_FASTINTVARIABLE(LuauCompileInlineThresholdMaxBoost, 300)
|
||||
LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauMultiAssignmentConflictFix, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSelfAssignmentSkip, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCompileInterpStringLimit, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
@ -1580,8 +1576,7 @@ struct Compiler
|
||||
|
||||
RegScope rs(this);
|
||||
|
||||
uint8_t baseReg = FFlag::LuauCompileInterpStringLimit ? allocReg(expr, unsigned(2 + expr->expressions.size))
|
||||
: allocReg(expr, uint8_t(2 + expr->expressions.size));
|
||||
uint8_t baseReg = allocReg(expr, unsigned(2 + expr->expressions.size));
|
||||
|
||||
emitLoadK(baseReg, formatStringIndex);
|
||||
|
||||
@ -2030,7 +2025,7 @@ struct Compiler
|
||||
if (int reg = getExprLocalReg(expr); reg >= 0)
|
||||
{
|
||||
// Optimization: we don't need to move if target happens to be in the same register
|
||||
if (!FFlag::LuauSelfAssignmentSkip || options.optimizationLevel == 0 || target != reg)
|
||||
if (options.optimizationLevel == 0 || target != reg)
|
||||
bytecode.emitABC(LOP_MOVE, target, uint8_t(reg), 0);
|
||||
}
|
||||
else
|
||||
@ -2982,48 +2977,31 @@ struct Compiler
|
||||
|
||||
Visitor visitor(this);
|
||||
|
||||
if (FFlag::LuauMultiAssignmentConflictFix)
|
||||
// mark any registers that are used *after* assignment as conflicting
|
||||
|
||||
// first we go through assignments to locals, since they are performed before assignments to other l-values
|
||||
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
|
||||
for (size_t i = 0; i < vars.size(); ++i)
|
||||
if (li.kind == LValue::Kind_Local)
|
||||
{
|
||||
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)
|
||||
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
|
||||
for (size_t i = vars.size(); i < values.size; ++i)
|
||||
values.data[i]->visit(&visitor);
|
||||
|
4
Makefile
4
Makefile
@ -127,7 +127,7 @@ $(ISOCLINE_OBJECTS): CXXFLAGS+=-Wno-unused-function -Iextern/isocline/include
|
||||
$(TESTS_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IAnalysis/include -ICodeGen/include -IVM/include -ICLI -Iextern -DDOCTEST_CONFIG_DOUBLE_STRINGIFY
|
||||
$(REPL_CLI_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IVM/include -ICodeGen/include -Iextern -Iextern/isocline/include
|
||||
$(ANALYZE_CLI_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -IAnalysis/include -Iextern
|
||||
$(FUZZ_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IAnalysis/include -IVM/include
|
||||
$(FUZZ_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IAnalysis/include -IVM/include -ICodeGen/include
|
||||
|
||||
$(TESTS_TARGET): LDFLAGS+=-lpthread
|
||||
$(REPL_CLI_TARGET): LDFLAGS+=-lpthread
|
||||
@ -192,7 +192,7 @@ $(TESTS_TARGET) $(REPL_CLI_TARGET) $(ANALYZE_CLI_TARGET):
|
||||
$(CXX) $^ $(LDFLAGS) -o $@
|
||||
|
||||
# executable targets for fuzzing
|
||||
fuzz-%: $(BUILD)/fuzz/%.cpp.o $(ANALYSIS_TARGET) $(COMPILER_TARGET) $(AST_TARGET) $(VM_TARGET)
|
||||
fuzz-%: $(BUILD)/fuzz/%.cpp.o $(ANALYSIS_TARGET) $(COMPILER_TARGET) $(AST_TARGET) $(CODEGEN_TARGET) $(VM_TARGET)
|
||||
$(CXX) $^ $(LDFLAGS) -o $@
|
||||
|
||||
fuzz-proto: $(BUILD)/fuzz/proto.cpp.o $(BUILD)/fuzz/protoprint.cpp.o $(BUILD)/fuzz/luau.pb.cpp.o $(ANALYSIS_TARGET) $(COMPILER_TARGET) $(AST_TARGET) $(VM_TARGET) | build/libprotobuf-mutator
|
||||
|
@ -63,6 +63,11 @@ target_sources(Luau.CodeGen PRIVATE
|
||||
CodeGen/include/Luau/CodeGen.h
|
||||
CodeGen/include/Luau/ConditionA64.h
|
||||
CodeGen/include/Luau/ConditionX64.h
|
||||
CodeGen/include/Luau/IrAnalysis.h
|
||||
CodeGen/include/Luau/IrBuilder.h
|
||||
CodeGen/include/Luau/IrDump.h
|
||||
CodeGen/include/Luau/IrData.h
|
||||
CodeGen/include/Luau/IrUtils.h
|
||||
CodeGen/include/Luau/Label.h
|
||||
CodeGen/include/Luau/OperandX64.h
|
||||
CodeGen/include/Luau/RegisterA64.h
|
||||
@ -100,13 +105,8 @@ target_sources(Luau.CodeGen PRIVATE
|
||||
CodeGen/src/EmitInstructionX64.h
|
||||
CodeGen/src/Fallbacks.h
|
||||
CodeGen/src/FallbacksProlog.h
|
||||
CodeGen/src/IrAnalysis.h
|
||||
CodeGen/src/IrBuilder.h
|
||||
CodeGen/src/IrDump.h
|
||||
CodeGen/src/IrData.h
|
||||
CodeGen/src/IrLoweringX64.h
|
||||
CodeGen/src/IrTranslation.h
|
||||
CodeGen/src/IrUtils.h
|
||||
CodeGen/src/NativeState.h
|
||||
)
|
||||
|
||||
@ -120,7 +120,7 @@ target_sources(Luau.Analysis PRIVATE
|
||||
Analysis/include/Luau/BuiltinDefinitions.h
|
||||
Analysis/include/Luau/Clone.h
|
||||
Analysis/include/Luau/Config.h
|
||||
Analysis/include/Luau/Connective.h
|
||||
Analysis/include/Luau/Refinement.h
|
||||
Analysis/include/Luau/Constraint.h
|
||||
Analysis/include/Luau/ConstraintGraphBuilder.h
|
||||
Analysis/include/Luau/ConstraintSolver.h
|
||||
@ -175,7 +175,7 @@ target_sources(Luau.Analysis PRIVATE
|
||||
Analysis/src/BuiltinDefinitions.cpp
|
||||
Analysis/src/Clone.cpp
|
||||
Analysis/src/Config.cpp
|
||||
Analysis/src/Connective.cpp
|
||||
Analysis/src/Refinement.cpp
|
||||
Analysis/src/Constraint.cpp
|
||||
Analysis/src/ConstraintGraphBuilder.cpp
|
||||
Analysis/src/ConstraintSolver.cpp
|
||||
|
@ -17,6 +17,8 @@
|
||||
#include <mach/mach_time.h>
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
#include <time.h>
|
||||
|
||||
static double clock_period()
|
||||
|
@ -8,8 +8,6 @@
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauStringFormatAnyFix, false)
|
||||
|
||||
// macro to `unsign' a character
|
||||
#define uchar(c) ((unsigned char)(c))
|
||||
|
||||
@ -1039,21 +1037,11 @@ static int str_format(lua_State* L)
|
||||
if (formatItemSize != 1)
|
||||
luaL_error(L, "'%%*' does not take a form");
|
||||
|
||||
if (FFlag::LuauStringFormatAnyFix)
|
||||
{
|
||||
size_t length;
|
||||
const char* string = luaL_tolstring(L, arg, &length);
|
||||
size_t length;
|
||||
const char* string = luaL_tolstring(L, arg, &length);
|
||||
|
||||
luaL_addlstring(&b, string, length, -2);
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t length;
|
||||
const char* string = luaL_tolstring(L, arg, &length);
|
||||
|
||||
luaL_addlstring(&b, string, length, -1);
|
||||
}
|
||||
luaL_addlstring(&b, string, length, -2);
|
||||
lua_pop(L, 1);
|
||||
|
||||
continue; // skip the `luaL_addlstring' at the end
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
#include "Luau/BuiltinDefinitions.h"
|
||||
#include "Luau/BytecodeBuilder.h"
|
||||
#include "Luau/CodeGen.h"
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/Compiler.h"
|
||||
#include "Luau/Frontend.h"
|
||||
@ -25,11 +26,13 @@ const bool kFuzzLinter = true;
|
||||
const bool kFuzzTypeck = true;
|
||||
const bool kFuzzVM = true;
|
||||
const bool kFuzzTranspile = true;
|
||||
const bool kFuzzCodegen = true;
|
||||
|
||||
// Should we generate type annotations?
|
||||
const bool kFuzzTypes = true;
|
||||
|
||||
static_assert(!(kFuzzVM && !kFuzzCompiler), "VM requires the compiler!");
|
||||
static_assert(!(kFuzzCodegen && !kFuzzVM), "Codegen requires the VM!");
|
||||
|
||||
std::vector<std::string> protoprint(const luau::ModuleSet& stat, bool types);
|
||||
|
||||
@ -83,6 +86,9 @@ lua_State* createGlobalState()
|
||||
{
|
||||
lua_State* L = lua_newstate(allocate, NULL);
|
||||
|
||||
if (kFuzzCodegen && Luau::CodeGen::isSupported())
|
||||
Luau::CodeGen::create(L);
|
||||
|
||||
lua_callbacks(L)->interrupt = interrupt;
|
||||
|
||||
luaL_openlibs(L);
|
||||
@ -349,20 +355,30 @@ DEFINE_PROTO_FUZZER(const luau::ModuleSet& message)
|
||||
{
|
||||
static lua_State* globalState = createGlobalState();
|
||||
|
||||
lua_State* L = lua_newthread(globalState);
|
||||
luaL_sandboxthread(L);
|
||||
auto runCode = [](const std::string& bytecode, bool useCodegen) {
|
||||
lua_State* L = lua_newthread(globalState);
|
||||
luaL_sandboxthread(L);
|
||||
|
||||
if (luau_load(L, "=fuzz", bytecode.data(), bytecode.size(), 0) == 0)
|
||||
{
|
||||
interruptDeadline = std::chrono::system_clock::now() + kInterruptTimeout;
|
||||
if (luau_load(L, "=fuzz", bytecode.data(), bytecode.size(), 0) == 0)
|
||||
{
|
||||
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_gc(globalState, LUA_GCCOLLECT, 0);
|
||||
LUAU_ASSERT(heapSize < 256 * 1024);
|
||||
lua_pop(globalState, 1);
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
@ -441,12 +441,16 @@ TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "AVXBinaryInstructionForms")
|
||||
SINGLE_COMPARE(vmulsd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0x2b, 0x59, 0xc6);
|
||||
SINGLE_COMPARE(vdivsd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0x2b, 0x5e, 0xc6);
|
||||
|
||||
SINGLE_COMPARE(vorpd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0x29, 0x56, 0xc6);
|
||||
SINGLE_COMPARE(vxorpd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0x29, 0x57, 0xc6);
|
||||
|
||||
SINGLE_COMPARE(vandpd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0x29, 0x54, 0xc6);
|
||||
SINGLE_COMPARE(vandnpd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0x29, 0x55, 0xc6);
|
||||
|
||||
SINGLE_COMPARE(vmaxsd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0x2b, 0x5f, 0xc6);
|
||||
SINGLE_COMPARE(vminsd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0x2b, 0x5d, 0xc6);
|
||||
|
||||
SINGLE_COMPARE(vcmpltsd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0x2b, 0xc2, 0xc6, 0x01);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "AVXUnaryMergeInstructionForms")
|
||||
@ -510,6 +514,7 @@ TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "AVXTernaryInstructionForms")
|
||||
SINGLE_COMPARE(
|
||||
vroundsd(xmm8, xmm13, xmmword[r13 + rdx], RoundingModeX64::RoundToPositiveInfinity), 0xc4, 0x43, 0x11, 0x0b, 0x44, 0x15, 0x00, 0x0a);
|
||||
SINGLE_COMPARE(vroundsd(xmm9, xmm14, xmmword[rcx + r10], RoundingModeX64::RoundToZero), 0xc4, 0x23, 0x09, 0x0b, 0x0c, 0x11, 0x0b);
|
||||
SINGLE_COMPARE(vblendvpd(xmm7, xmm12, xmmword[rcx + r10], xmm5), 0xc4, 0xa3, 0x19, 0x4b, 0x3c, 0x11, 0x50);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "MiscInstructions")
|
||||
@ -621,6 +626,7 @@ TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "Constants")
|
||||
build.vmovaps(xmm4, build.f32x4(1.0f, 2.0f, 4.0f, 8.0f));
|
||||
char arr[16] = "hello world!123";
|
||||
build.vmovupd(xmm5, build.bytes(arr, 16, 8));
|
||||
build.vmovapd(xmm5, build.f64x2(5.0, 6.0));
|
||||
build.ret();
|
||||
},
|
||||
{
|
||||
@ -630,9 +636,12 @@ TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "Constants")
|
||||
0xc4, 0xe1, 0x7b, 0x10, 0x1d, 0xcc, 0xff, 0xff, 0xff,
|
||||
0xc4, 0xe1, 0x78, 0x28, 0x25, 0xab, 0xff, 0xff, 0xff,
|
||||
0xc4, 0xe1, 0x79, 0x10, 0x2d, 0x92, 0xff, 0xff, 0xff,
|
||||
0xc4, 0xe1, 0x79, 0x28, 0x2d, 0x79, 0xff, 0xff, 0xff,
|
||||
0xc3
|
||||
},
|
||||
{
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x40,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x40,
|
||||
'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '!', '1', '2', '3', 0x0,
|
||||
0x00, 0x00, 0x80, 0x3f,
|
||||
0x00, 0x00, 0x00, 0x40,
|
||||
|
@ -15,7 +15,6 @@
|
||||
|
||||
LUAU_FASTFLAG(LuauTraceTypesInNonstrictMode2)
|
||||
LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel)
|
||||
LUAU_FASTFLAG(LuauFixAutocompleteInIf)
|
||||
LUAU_FASTFLAG(LuauFixAutocompleteInWhile)
|
||||
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.context, AutocompleteContext::Keyword);
|
||||
|
||||
if (FFlag::LuauFixAutocompleteInIf)
|
||||
{
|
||||
check(R"(
|
||||
if x t@1
|
||||
)");
|
||||
check(R"(
|
||||
if x t@1
|
||||
)");
|
||||
|
||||
auto ac3 = autocomplete('1');
|
||||
CHECK_EQ(3, ac3.entryMap.size());
|
||||
CHECK_EQ(ac3.entryMap.count("then"), 1);
|
||||
CHECK_EQ(ac3.entryMap.count("and"), 1);
|
||||
CHECK_EQ(ac3.entryMap.count("or"), 1);
|
||||
CHECK_EQ(ac3.context, AutocompleteContext::Keyword);
|
||||
}
|
||||
else
|
||||
{
|
||||
check(R"(
|
||||
if x t@1
|
||||
)");
|
||||
|
||||
auto ac3 = autocomplete('1');
|
||||
CHECK_EQ(1, ac3.entryMap.size());
|
||||
CHECK_EQ(ac3.entryMap.count("then"), 1);
|
||||
CHECK_EQ(ac3.context, AutocompleteContext::Keyword);
|
||||
}
|
||||
auto ac3 = autocomplete('1');
|
||||
CHECK_EQ(3, ac3.entryMap.size());
|
||||
CHECK_EQ(ac3.entryMap.count("then"), 1);
|
||||
CHECK_EQ(ac3.entryMap.count("and"), 1);
|
||||
CHECK_EQ(ac3.entryMap.count("or"), 1);
|
||||
CHECK_EQ(ac3.context, AutocompleteContext::Keyword);
|
||||
|
||||
check(R"(
|
||||
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.context, AutocompleteContext::Statement);
|
||||
|
||||
if (FFlag::LuauFixAutocompleteInIf)
|
||||
{
|
||||
check(R"(
|
||||
if t@1
|
||||
)");
|
||||
check(R"(
|
||||
if t@1
|
||||
)");
|
||||
|
||||
auto ac6 = autocomplete('1');
|
||||
CHECK_EQ(ac6.entryMap.count("true"), 1);
|
||||
CHECK_EQ(ac6.entryMap.count("false"), 1);
|
||||
CHECK_EQ(ac6.entryMap.count("then"), 0);
|
||||
CHECK_EQ(ac6.entryMap.count("function"), 1);
|
||||
CHECK_EQ(ac6.entryMap.count("else"), 0);
|
||||
CHECK_EQ(ac6.entryMap.count("elseif"), 0);
|
||||
CHECK_EQ(ac6.entryMap.count("end"), 0);
|
||||
CHECK_EQ(ac6.context, AutocompleteContext::Expression);
|
||||
}
|
||||
auto ac6 = autocomplete('1');
|
||||
CHECK_EQ(ac6.entryMap.count("true"), 1);
|
||||
CHECK_EQ(ac6.entryMap.count("false"), 1);
|
||||
CHECK_EQ(ac6.entryMap.count("then"), 0);
|
||||
CHECK_EQ(ac6.entryMap.count("function"), 1);
|
||||
CHECK_EQ(ac6.entryMap.count("else"), 0);
|
||||
CHECK_EQ(ac6.entryMap.count("elseif"), 0);
|
||||
CHECK_EQ(ac6.entryMap.count("end"), 0);
|
||||
CHECK_EQ(ac6.context, AutocompleteContext::Expression);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "autocomplete_until_in_repeat")
|
||||
@ -3428,6 +3410,8 @@ TEST_CASE_FIXTURE(ACFixture, "globals_are_order_independent")
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "type_reduction_is_hooked_up_to_autocomplete")
|
||||
{
|
||||
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
|
||||
|
||||
check(R"(
|
||||
type T = { x: (number & string)? }
|
||||
|
||||
@ -3447,15 +3431,13 @@ TEST_CASE_FIXTURE(ACFixture, "type_reduction_is_hooked_up_to_autocomplete")
|
||||
REQUIRE(ac1.entryMap.count("x"));
|
||||
std::optional<TypeId> ty1 = ac1.entryMap.at("x").type;
|
||||
REQUIRE(ty1);
|
||||
CHECK("(number & string)?" == toString(*ty1, opts));
|
||||
// CHECK("nil" == toString(*ty1, opts));
|
||||
CHECK("nil" == toString(*ty1, opts));
|
||||
|
||||
auto ac2 = autocomplete('2');
|
||||
REQUIRE(ac2.entryMap.count("thingamabob"));
|
||||
std::optional<TypeId> ty2 = ac2.entryMap.at("thingamabob").type;
|
||||
REQUIRE(ty2);
|
||||
CHECK("{| x: (number & string)? |}" == toString(*ty2, opts));
|
||||
// CHECK("{| x: nil |}" == toString(*ty2, opts));
|
||||
CHECK("{| x: nil |}" == toString(*ty2, opts));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "string_contents_is_available_to_callback")
|
||||
|
@ -1025,8 +1025,6 @@ L0: RETURN R0 0
|
||||
|
||||
TEST_CASE("AndOr")
|
||||
{
|
||||
ScopedFastFlag luauSelfAssignmentSkip{"LuauSelfAssignmentSkip", true};
|
||||
|
||||
// codegen for constant, local, global for and
|
||||
CHECK_EQ("\n" + compileFunction0("local a = 1 a = a and 2 return a"), R"(
|
||||
LOADN R0 1
|
||||
@ -1319,8 +1317,6 @@ RETURN R0 0
|
||||
|
||||
TEST_CASE("InterpStringRegisterLimit")
|
||||
{
|
||||
ScopedFastFlag luauCompileInterpStringLimit{"LuauCompileInterpStringLimit", true};
|
||||
|
||||
CHECK_THROWS_AS(compileFunction0(("local a = `" + rep("{1}", 254) + "`").c_str()), std::exception);
|
||||
CHECK_THROWS_AS(compileFunction0(("local a = `" + rep("{1}", 253) + "`").c_str()), std::exception);
|
||||
}
|
||||
@ -2262,8 +2258,6 @@ L1: RETURN R3 -1
|
||||
|
||||
TEST_CASE("UpvaluesLoopsBytecode")
|
||||
{
|
||||
ScopedFastFlag luauSelfAssignmentSkip{"LuauSelfAssignmentSkip", true};
|
||||
|
||||
CHECK_EQ("\n" + compileFunction(R"(
|
||||
function test()
|
||||
for i=1,10 do
|
||||
@ -5161,8 +5155,6 @@ RETURN R1 1
|
||||
|
||||
TEST_CASE("InlineMutate")
|
||||
{
|
||||
ScopedFastFlag luauSelfAssignmentSkip{"LuauSelfAssignmentSkip", true};
|
||||
|
||||
// if the argument is mutated, it gets a register even if the value is constant
|
||||
CHECK_EQ("\n" + compileFunction(R"(
|
||||
local function foo(a)
|
||||
@ -6756,8 +6748,6 @@ MOVE R1 R3
|
||||
RETURN R0 0
|
||||
)");
|
||||
|
||||
ScopedFastFlag luauMultiAssignmentConflictFix{"LuauMultiAssignmentConflictFix", true};
|
||||
|
||||
// because we perform assignments to complex l-values after assignments to locals, we make sure register conflicts are tracked accordingly
|
||||
CHECK_EQ("\n" + compileFunction0(R"(
|
||||
local a, b = ...
|
||||
@ -6795,8 +6785,6 @@ L0: RETURN R1 -1
|
||||
|
||||
TEST_CASE("SkipSelfAssignment")
|
||||
{
|
||||
ScopedFastFlag luauSelfAssignmentSkip{"LuauSelfAssignmentSkip", true};
|
||||
|
||||
CHECK_EQ("\n" + compileFunction0("local a a = a"), R"(
|
||||
LOADNIL R0
|
||||
RETURN R0 0
|
||||
|
@ -297,8 +297,6 @@ TEST_CASE("Clear")
|
||||
|
||||
TEST_CASE("Strings")
|
||||
{
|
||||
ScopedFastFlag luauStringFormatAnyFix{"LuauStringFormatAnyFix", true};
|
||||
|
||||
runConformance("strings.lua");
|
||||
}
|
||||
|
||||
|
@ -1706,23 +1706,6 @@ local _ = 0x10000000000000000
|
||||
CHECK_EQ(result.warnings[1].text, "Hexadecimal number literal exceeded available precision and has been truncated to 2^64");
|
||||
}
|
||||
|
||||
// TODO: remove with FFlagLuauErrorDoubleHexPrefix
|
||||
TEST_CASE_FIXTURE(Fixture, "IntegerParsingDoublePrefix")
|
||||
{
|
||||
ScopedFastFlag luauErrorDoubleHexPrefix{"LuauErrorDoubleHexPrefix", false}; // Lint will be available until we start rejecting code
|
||||
|
||||
LintResult result = lint(R"(
|
||||
local _ = 0x0x123
|
||||
local _ = 0x0xffffffffffffffffffffffffffffffffff
|
||||
)");
|
||||
|
||||
REQUIRE(2 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].text,
|
||||
"Hexadecimal number literal has a double prefix, which will fail to parse in the future; remove the extra 0x to fix");
|
||||
CHECK_EQ(result.warnings[1].text,
|
||||
"Hexadecimal number literal has a double prefix, which will fail to parse in the future; remove the extra 0x to fix");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "ComparisonPrecedence")
|
||||
{
|
||||
LintResult result = lint(R"(
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
#include "Fixture.h"
|
||||
|
||||
#include "Luau/AstQuery.h"
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "doctest.h"
|
||||
@ -9,6 +10,8 @@
|
||||
#include "Luau/Normalize.h"
|
||||
#include "Luau/BuiltinDefinitions.h"
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
namespace
|
||||
@ -377,9 +380,25 @@ struct NormalizeFixture : Fixture
|
||||
normalizer.clearCaches();
|
||||
CheckResult result = check("type _Res = " + annotation);
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
std::optional<TypeId> ty = lookupType("_Res");
|
||||
REQUIRE(ty);
|
||||
return normalizer.normalize(*ty);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
SourceModule* sourceModule = getMainSourceModule();
|
||||
REQUIRE(sourceModule);
|
||||
AstNode* node = findNodeAtPosition(*sourceModule, {0, 5});
|
||||
REQUIRE(node);
|
||||
AstStatTypeAlias* alias = node->as<AstStatTypeAlias>();
|
||||
REQUIRE(alias);
|
||||
TypeId* originalTy = getMainModule()->astOriginalResolvedTypes.find(alias->type);
|
||||
REQUIRE(originalTy);
|
||||
return normalizer.normalize(*originalTy);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::optional<TypeId> ty = lookupType("_Res");
|
||||
REQUIRE(ty);
|
||||
return normalizer.normalize(*ty);
|
||||
}
|
||||
}
|
||||
|
||||
TypeId normal(const std::string& annotation)
|
||||
|
@ -683,8 +683,6 @@ TEST_CASE_FIXTURE(Fixture, "parse_numbers_binary")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "parse_numbers_error")
|
||||
{
|
||||
ScopedFastFlag luauErrorDoubleHexPrefix{"LuauErrorDoubleHexPrefix", true};
|
||||
|
||||
CHECK_EQ(getParseError("return 0b123"), "Malformed number");
|
||||
CHECK_EQ(getParseError("return 123x"), "Malformed number");
|
||||
CHECK_EQ(getParseError("return 0xg"), "Malformed number");
|
||||
@ -693,13 +691,6 @@ TEST_CASE_FIXTURE(Fixture, "parse_numbers_error")
|
||||
CHECK_EQ(getParseError("return 0x0xffffffffffffffffffffffffffff"), "Malformed number");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "parse_numbers_error_soft")
|
||||
{
|
||||
ScopedFastFlag luauErrorDoubleHexPrefix{"LuauErrorDoubleHexPrefix", false};
|
||||
|
||||
CHECK_EQ(getParseError("return 0x0x0x0x0x0x0x0"), "Malformed number");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "break_return_not_last_error")
|
||||
{
|
||||
CHECK_EQ(getParseError("return 0 print(5)"), "Expected <eof>, got 'print'");
|
||||
|
@ -80,14 +80,9 @@ n1 [label="AnyType 1"];
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "bound")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
function a(): number return 444 end
|
||||
local b = a()
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
TypeArena arena;
|
||||
|
||||
std::optional<TypeId> ty = getType("b");
|
||||
REQUIRE(bool(ty));
|
||||
TypeId ty = arena.addType(BoundType{builtinTypes->numberType});
|
||||
|
||||
ToDotOptions opts;
|
||||
opts.showPointers = false;
|
||||
@ -96,7 +91,7 @@ n1 [label="BoundType 1"];
|
||||
n1 -> n2;
|
||||
n2 [label="number"];
|
||||
})",
|
||||
toDot(*ty, opts));
|
||||
toDot(ty, opts));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "function")
|
||||
@ -172,10 +167,9 @@ n3 [label="number"];
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "intersection")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local a: string & number -- uninhabited
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
TypeArena arena;
|
||||
|
||||
TypeId ty = arena.addType(IntersectionType{{builtinTypes->stringType, builtinTypes->numberType}});
|
||||
|
||||
ToDotOptions opts;
|
||||
opts.showPointers = false;
|
||||
@ -186,7 +180,7 @@ n2 [label="string"];
|
||||
n1 -> n3;
|
||||
n3 [label="number"];
|
||||
})",
|
||||
toDot(requireType("a"), opts));
|
||||
toDot(ty, opts));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "table")
|
||||
@ -396,44 +390,25 @@ n3 [label="number"];
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "bound_table")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local a = {x=2}
|
||||
local b
|
||||
b.x = 2
|
||||
b = a
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
TypeArena arena;
|
||||
|
||||
std::optional<TypeId> ty = getType("b");
|
||||
REQUIRE(bool(ty));
|
||||
TypeId ty = arena.addType(TableType{});
|
||||
getMutable<TableType>(ty)->props["x"] = {builtinTypes->numberType};
|
||||
|
||||
TypeId boundTy = arena.addType(TableType{});
|
||||
getMutable<TableType>(boundTy)->boundTo = ty;
|
||||
|
||||
ToDotOptions opts;
|
||||
opts.showPointers = false;
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ(R"(digraph graphname {
|
||||
n1 [label="BoundType 1"];
|
||||
n1 -> n2;
|
||||
n2 [label="TableType 2"];
|
||||
n2 -> n3 [label="boundTo"];
|
||||
n3 [label="TableType a"];
|
||||
n3 -> n4 [label="x"];
|
||||
n4 [label="number"];
|
||||
})",
|
||||
toDot(*ty, opts));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ(R"(digraph graphname {
|
||||
CHECK_EQ(R"(digraph graphname {
|
||||
n1 [label="TableType 1"];
|
||||
n1 -> n2 [label="boundTo"];
|
||||
n2 [label="TableType a"];
|
||||
n2 [label="TableType 2"];
|
||||
n2 -> n3 [label="x"];
|
||||
n3 [label="number"];
|
||||
})",
|
||||
toDot(*ty, opts));
|
||||
}
|
||||
toDot(boundTy, opts));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "builtintypes")
|
||||
|
@ -291,9 +291,9 @@ TEST_CASE_FIXTURE(Fixture, "quit_stringifying_type_when_length_is_exceeded")
|
||||
{
|
||||
o.maxTypeLength = 30;
|
||||
CHECK_EQ(toString(requireType("f0"), o), "() -> ()");
|
||||
CHECK_EQ(toString(requireType("f1"), o), "<a>(a) -> (() -> ()) | (a & ~(false?))... *TRUNCATED*");
|
||||
CHECK_EQ(toString(requireType("f2"), o), "<b>(b) -> (<a>(a) -> (() -> ()) | (a & ~(false?))... *TRUNCATED*");
|
||||
CHECK_EQ(toString(requireType("f3"), o), "<c>(c) -> (<b>(b) -> (<a>(a) -> (() -> ()) | (a & ~(false?))... *TRUNCATED*");
|
||||
CHECK_EQ(toString(requireType("f1"), o), "<a>(a) -> (() -> ()) | (a & ~false & ~nil)... *TRUNCATED*");
|
||||
CHECK_EQ(toString(requireType("f2"), o), "<b>(b) -> (<a>(a) -> (() -> ()) | (a & ~false & ~nil)... *TRUNCATED*");
|
||||
CHECK_EQ(toString(requireType("f3"), o), "<c>(c) -> (<b>(b) -> (<a>(a) -> (() -> ()) | (a & ~false & ~nil)... *TRUNCATED*");
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -321,9 +321,9 @@ TEST_CASE_FIXTURE(Fixture, "stringifying_type_is_still_capped_when_exhaustive")
|
||||
{
|
||||
o.maxTypeLength = 30;
|
||||
CHECK_EQ(toString(requireType("f0"), o), "() -> ()");
|
||||
CHECK_EQ(toString(requireType("f1"), o), "<a>(a) -> (() -> ()) | (a & ~(false?))... *TRUNCATED*");
|
||||
CHECK_EQ(toString(requireType("f2"), o), "<b>(b) -> (<a>(a) -> (() -> ()) | (a & ~(false?))... *TRUNCATED*");
|
||||
CHECK_EQ(toString(requireType("f3"), o), "<c>(c) -> (<b>(b) -> (<a>(a) -> (() -> ()) | (a & ~(false?))... *TRUNCATED*");
|
||||
CHECK_EQ(toString(requireType("f1"), o), "<a>(a) -> (() -> ()) | (a & ~false & ~nil)... *TRUNCATED*");
|
||||
CHECK_EQ(toString(requireType("f2"), o), "<b>(b) -> (<a>(a) -> (() -> ()) | (a & ~false & ~nil)... *TRUNCATED*");
|
||||
CHECK_EQ(toString(requireType("f3"), o), "<c>(c) -> (<b>(b) -> (<a>(a) -> (() -> ()) | (a & ~false & ~nil)... *TRUNCATED*");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -357,8 +357,6 @@ TEST_CASE_FIXTURE(Fixture, "cli_38393_recursive_intersection_oom")
|
||||
type t0<t0> = ((typeof(_))&((t0)&(((typeof(_))&(t0))->typeof(_))),{n163:any,})->(any,typeof(_))
|
||||
_(_)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "type_alias_fwd_declaration_is_precise")
|
||||
|
@ -132,22 +132,40 @@ TEST_CASE_FIXTURE(Fixture, "should_still_pick_an_overload_whose_arguments_are_un
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "propagates_name")
|
||||
{
|
||||
const std::string code = R"(
|
||||
type A={a:number}
|
||||
type B={b:string}
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CheckResult result = check(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 = c
|
||||
)");
|
||||
|
||||
local c:A&B
|
||||
local b:A&B=c
|
||||
)";
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
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")
|
||||
@ -161,17 +179,10 @@ TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_with_property_guarante
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
const IntersectionType* r = get<IntersectionType>(requireType("r"));
|
||||
REQUIRE(r);
|
||||
|
||||
TableType* a = getMutable<TableType>(r->parts[0]);
|
||||
REQUIRE(a);
|
||||
CHECK_EQ(typeChecker.numberType, a->props["y"].type);
|
||||
|
||||
TableType* b = getMutable<TableType>(r->parts[1]);
|
||||
REQUIRE(b);
|
||||
CHECK_EQ(typeChecker.numberType, b->props["y"].type);
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK("{| y: number |}" == toString(requireType("r")));
|
||||
else
|
||||
CHECK("{| y: number |} & {| y: number |}" == toString(requireType("r")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_works_at_arbitrary_depth")
|
||||
@ -207,7 +218,10 @@ TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_with_mixed_types")
|
||||
)");
|
||||
|
||||
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")
|
||||
@ -387,7 +401,10 @@ local a: XYZ = 3
|
||||
)");
|
||||
|
||||
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:
|
||||
Not all intersection parts are compatible. Type 'number' could not be converted into 'X')");
|
||||
}
|
||||
@ -404,7 +421,11 @@ local b: number = a
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Type 'X & Y & Z' could not be converted into 'number'; none of the intersection parts are compatible)");
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Type '{| x: number, y: number, z: number |}' could not be converted into 'number')");
|
||||
else
|
||||
CHECK_EQ(
|
||||
toString(result.errors[0]), R"(Type 'X & Y & Z' could not be converted into 'number'; none of the intersection parts are compatible)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "overload_is_not_a_function")
|
||||
@ -444,7 +465,11 @@ TEST_CASE_FIXTURE(Fixture, "intersect_bool_and_false")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type 'boolean & false' could not be converted into 'true'; none of the intersection parts are compatible");
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ(toString(result.errors[0]), "Type 'false' could not be converted into 'true'");
|
||||
else
|
||||
CHECK_EQ(
|
||||
toString(result.errors[0]), "Type 'boolean & false' could not be converted into 'true'; none of the intersection parts are compatible");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "intersect_false_and_bool_and_false")
|
||||
@ -456,9 +481,14 @@ TEST_CASE_FIXTURE(Fixture, "intersect_false_and_bool_and_false")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
// 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");
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ(toString(result.errors[0]), "Type 'false' could not be converted into 'true'");
|
||||
else
|
||||
{
|
||||
// TODO: odd stringification of `false & (boolean & false)`.)
|
||||
CHECK_EQ(toString(result.errors[0]),
|
||||
"Type 'boolean & false & false' could not be converted into 'true'; none of the intersection parts are compatible");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "intersect_saturate_overloaded_functions")
|
||||
@ -496,8 +526,21 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '{| p: number?, q: number?, r: number? |} & {| p: number?, q: string? |}' could not be converted into "
|
||||
"'{| p: nil |}'; none of the intersection parts are compatible");
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ(toString(result.errors[0]),
|
||||
"Type '{| p: number?, q: nil, r: number? |}' could not be converted into '{| p: nil |}'\n"
|
||||
"caused by:\n"
|
||||
" Property 'p' is not compatible. Type 'number?' could not be converted into 'nil'\n"
|
||||
"caused by:\n"
|
||||
" Not all union options are compatible. Type 'number' could not be converted into 'nil' in an invariant context");
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ(toString(result.errors[0]),
|
||||
"Type '{| p: number?, q: number?, r: number? |} & {| p: number?, q: string? |}' could not be converted into "
|
||||
"'{| p: nil |}'; none of the intersection parts are compatible");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_top_properties")
|
||||
@ -508,9 +551,35 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_top_properties")
|
||||
local z : { p : string?, q : number? } = x -- Not OK
|
||||
)");
|
||||
|
||||
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");
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
|
||||
CHECK_EQ(toString(result.errors[0]),
|
||||
"Type '{| p: number?, q: string? |}' could not be converted into '{| p: string?, q: number? |}'\n"
|
||||
"caused by:\n"
|
||||
" Property 'p' is not compatible. Type 'number?' could not be converted into 'string?'\n"
|
||||
"caused by:\n"
|
||||
" Not all union options are compatible. Type 'number' could not be converted into 'string?'\n"
|
||||
"caused by:\n"
|
||||
" None of the union options are compatible. For example: Type 'number' could not be converted into 'string' in an invariant context");
|
||||
|
||||
CHECK_EQ(toString(result.errors[1]),
|
||||
"Type '{| p: number?, q: string? |}' could not be converted into '{| p: string?, q: number? |}'\n"
|
||||
"caused by:\n"
|
||||
" Property 'q' is not compatible. Type 'string?' could not be converted into 'number?'\n"
|
||||
"caused by:\n"
|
||||
" Not all union options are compatible. Type 'string' could not be converted into 'number?'\n"
|
||||
"caused by:\n"
|
||||
" None of the union options are compatible. For example: Type 'string' could not be converted into 'number' in an invariant context");
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]),
|
||||
"Type '{| p: number?, q: any |} & {| p: unknown, q: string? |}' could not be converted into '{| p: string?, "
|
||||
"q: number? |}'; none of the intersection parts are compatible");
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
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");
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ(toString(result.errors[0]),
|
||||
"Type '((number?) -> {| p: number, q: number |}) & ((string?) -> {| p: number, r: number |})' could not be converted into "
|
||||
"'(number?) -> {| p: number, q: number, r: number |}'; none of the intersection parts are compatible");
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ(toString(result.errors[0]),
|
||||
"Type '((number?) -> {| p: number |} & {| q: number |}) & ((string?) -> {| p: number |} & {| r: number |})' could not be converted into "
|
||||
"'(number?) -> {| p: number, q: number, r: number |}'; none of the intersection parts are compatible");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic")
|
||||
@ -840,7 +918,7 @@ TEST_CASE_FIXTURE(Fixture, "less_greedy_unification_with_intersection_types")
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("({| x: number |} & {| x: string |}) -> {| x: number |} & {| x: string |}", toString(requireType("f")));
|
||||
CHECK_EQ("(never) -> never", toString(requireType("f")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "less_greedy_unification_with_intersection_types_2")
|
||||
@ -856,7 +934,7 @@ TEST_CASE_FIXTURE(Fixture, "less_greedy_unification_with_intersection_types_2")
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("({| x: number |} & {| x: string |}) -> number & string", toString(requireType("f")));
|
||||
CHECK_EQ("(never) -> never", toString(requireType("f")));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -661,4 +661,32 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "loop_iter_metamethod_ok_with_inference")
|
||||
CHECK(toString(requireType("b")) == "string");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "for_loop_lower_bound_is_string")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
for i: unknown = 1, 10 do end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "for_loop_lower_bound_is_string_2")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
for i: never = 1, 10 do end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ("Type 'number' could not be converted into 'never'", toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "for_loop_lower_bound_is_string_3")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
for i: number | string = 1, 10 do end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -25,16 +25,8 @@ TEST_CASE_FIXTURE(Fixture, "or_joins_types")
|
||||
local x:string|number = s
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ(toString(*requireType("s")), "(string & ~(false?)) | number");
|
||||
CHECK_EQ(toString(*requireType("x")), "number | string");
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ(toString(*requireType("s")), "number | string");
|
||||
CHECK_EQ(toString(*requireType("x")), "number | string");
|
||||
}
|
||||
CHECK_EQ(toString(*requireType("s")), "number | string");
|
||||
CHECK_EQ(toString(*requireType("x")), "number | string");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "or_joins_types_with_no_extras")
|
||||
@ -45,16 +37,8 @@ TEST_CASE_FIXTURE(Fixture, "or_joins_types_with_no_extras")
|
||||
local y = x or "s"
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ(toString(*requireType("s")), "(string & ~(false?)) | number");
|
||||
CHECK_EQ(toString(*requireType("y")), "((number | string) & ~(false?)) | string");
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ(toString(*requireType("s")), "number | string");
|
||||
CHECK_EQ(toString(*requireType("y")), "number | string");
|
||||
}
|
||||
CHECK_EQ(toString(*requireType("s")), "number | string");
|
||||
CHECK_EQ(toString(*requireType("y")), "number | string");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "or_joins_types_with_no_superfluous_union")
|
||||
@ -78,14 +62,7 @@ TEST_CASE_FIXTURE(Fixture, "and_does_not_always_add_boolean")
|
||||
local x:boolean|number = s
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ(toString(*requireType("s")), "((false?) & string) | number");
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ(toString(*requireType("s")), "number");
|
||||
}
|
||||
CHECK_EQ(toString(*requireType("s")), "number");
|
||||
}
|
||||
|
||||
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
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ(toString(*requireType("s")), "((((false?) & boolean) | string) & ~(false?)) | number");
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ(toString(*requireType("s")), "number | string");
|
||||
}
|
||||
CHECK_EQ(toString(*requireType("s")), "number | string");
|
||||
}
|
||||
|
||||
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]);
|
||||
REQUIRE(tm);
|
||||
CHECK_EQ(typeChecker.numberType, tm->wantedType);
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("((number & ~(false?)) | number)?", toString(tm->givenType));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("number?", toString(tm->givenType));
|
||||
}
|
||||
CHECK_EQ("number?", toString(tm->givenType));
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("((((false?) & ({| x: number? |}?)) | a) & ~(false?)) | number", toString(requireType("u")));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("number", toString(requireType("u")));
|
||||
}
|
||||
CHECK_EQ("number", toString(requireType("u")));
|
||||
}
|
||||
|
||||
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
|
||||
)");
|
||||
|
||||
CHECK("number?" == toString(requireType("x")));
|
||||
CHECK("number" == toString(requireType("y")));
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK("((false?) & (number?)) | number" == toString(requireType("x")));
|
||||
CHECK("((false?) & string) | number" == toString(requireType("y")));
|
||||
CHECK("((false?) & boolean) | number" == toString(requireType("z")));
|
||||
CHECK("((false?) & a) | number" == toString(requireType("w")));
|
||||
}
|
||||
CHECK("false | number" == toString(requireType("z")));
|
||||
else
|
||||
{
|
||||
CHECK("number?" == toString(requireType("x")));
|
||||
CHECK("number" == toString(requireType("y")));
|
||||
CHECK("boolean | number" == toString(requireType("z"))); // 'false' widened to boolean
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK("((false?) & a) | number" == toString(requireType("w")));
|
||||
else
|
||||
CHECK("(boolean | number)?" == toString(requireType("w")));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "reworked_or")
|
||||
@ -1133,24 +1085,20 @@ local e1 = e or 'e'
|
||||
local f1 = f or 'f'
|
||||
)");
|
||||
|
||||
CHECK("number | string" == toString(requireType("a1")));
|
||||
CHECK("number" == toString(requireType("b1")));
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK("((false | number) & ~(false?)) | string" == toString(requireType("a1")));
|
||||
CHECK("((number?) & ~(false?)) | number" == toString(requireType("b1")));
|
||||
CHECK("(boolean & ~(false?)) | string" == toString(requireType("c1")));
|
||||
CHECK("(true & ~(false?)) | string" == toString(requireType("d1")));
|
||||
CHECK("(false & ~(false?)) | string" == toString(requireType("e1")));
|
||||
CHECK("(nil & ~(false?)) | string" == toString(requireType("f1")));
|
||||
CHECK("string | true" == toString(requireType("c1")));
|
||||
CHECK("string | true" == toString(requireType("d1")));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK("number | string" == toString(requireType("a1")));
|
||||
CHECK("number" == toString(requireType("b1")));
|
||||
CHECK("boolean | string" == toString(requireType("c1"))); // 'true' widened to boolean
|
||||
CHECK("boolean | string" == toString(requireType("d1"))); // 'true' widened to boolean
|
||||
CHECK("string" == toString(requireType("e1")));
|
||||
CHECK("string" == toString(requireType("f1")));
|
||||
}
|
||||
CHECK("string" == toString(requireType("e1")));
|
||||
CHECK("string" == toString(requireType("f1")));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -594,28 +594,6 @@ return wrapStrictTable(Constants, "Constants")
|
||||
CHECK(get<AnyType>(*result));
|
||||
}
|
||||
|
||||
// We need a simplification step to make this do the right thing. ("normalization-lite")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_with_a_singleton_argument")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function foo(t, x)
|
||||
if x == "hi" or x == "bye" then
|
||||
table.insert(t, x)
|
||||
end
|
||||
|
||||
return t
|
||||
end
|
||||
|
||||
local t = foo({}, "hi")
|
||||
table.insert(t, "totally_unrelated_type" :: "totally_unrelated_type")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
// We'd really like for this to be {string}
|
||||
CHECK_EQ("{string | string}", toString(requireType("t")));
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
struct IsSubtypeFixture : Fixture
|
||||
@ -814,4 +792,44 @@ caused by:
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_with_a_singleton_argument")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function foo(t, x)
|
||||
if x == "hi" or x == "bye" then
|
||||
table.insert(t, x)
|
||||
end
|
||||
|
||||
return t
|
||||
end
|
||||
|
||||
local t = foo({}, "hi")
|
||||
table.insert(t, "totally_unrelated_type" :: "totally_unrelated_type")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("{string}", toString(requireType("t")));
|
||||
else
|
||||
{
|
||||
// We'd really like for this to be {string}
|
||||
CHECK_EQ("{string | string}", toString(requireType("t")));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "refine_unknown_to_table_then_clone_it")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: unknown)
|
||||
if typeof(x) == "table" then
|
||||
local cloned: {} = table.clone(x)
|
||||
end
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
// LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -36,7 +36,7 @@ std::optional<WithPredicate<TypePackId>> magicFunctionInstanceIsA(
|
||||
return WithPredicate<TypePackId>{booleanPack, {IsAPredicate{std::move(*lvalue), expr.location, tfun->type}}};
|
||||
}
|
||||
|
||||
std::vector<ConnectiveId> dcrMagicRefinementInstanceIsA(const MagicRefinementContext& ctx)
|
||||
std::vector<RefinementId> dcrMagicRefinementInstanceIsA(const MagicRefinementContext& ctx)
|
||||
{
|
||||
if (ctx.callSite->args.size != 1)
|
||||
return {};
|
||||
@ -54,7 +54,7 @@ std::vector<ConnectiveId> dcrMagicRefinementInstanceIsA(const MagicRefinementCon
|
||||
if (!tfun)
|
||||
return {};
|
||||
|
||||
return {ctx.connectiveArena->proposition(*def, tfun->type)};
|
||||
return {ctx.refinementArena->proposition(*def, tfun->type)};
|
||||
}
|
||||
|
||||
struct RefinementClassFixture : BuiltinsFixture
|
||||
@ -122,16 +122,8 @@ TEST_CASE_FIXTURE(Fixture, "is_truthy_constraint")
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("(string?) & ~(false?)", toString(requireTypeAtPosition({3, 26})));
|
||||
CHECK_EQ("(string?) & ~~(false?)", toString(requireTypeAtPosition({5, 26})));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("string", toString(requireTypeAtPosition({3, 26})));
|
||||
CHECK_EQ("nil", toString(requireTypeAtPosition({5, 26})));
|
||||
}
|
||||
CHECK_EQ("string", toString(requireTypeAtPosition({3, 26})));
|
||||
CHECK_EQ("nil", toString(requireTypeAtPosition({5, 26})));
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("(string?) & ~~(false?)", toString(requireTypeAtPosition({3, 26})));
|
||||
CHECK_EQ("(string?) & ~(false?)", toString(requireTypeAtPosition({5, 26})));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("nil", toString(requireTypeAtPosition({3, 26})));
|
||||
CHECK_EQ("string", toString(requireTypeAtPosition({5, 26})));
|
||||
}
|
||||
CHECK_EQ("nil", toString(requireTypeAtPosition({3, 26})));
|
||||
CHECK_EQ("string", toString(requireTypeAtPosition({5, 26})));
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("(string?) & ~~(false?)", toString(requireTypeAtPosition({3, 26})));
|
||||
CHECK_EQ("(string?) & ~(false?)", toString(requireTypeAtPosition({5, 26})));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("nil", toString(requireTypeAtPosition({3, 26})));
|
||||
CHECK_EQ("string", toString(requireTypeAtPosition({5, 26})));
|
||||
}
|
||||
CHECK_EQ("nil", toString(requireTypeAtPosition({3, 26})));
|
||||
CHECK_EQ("string", toString(requireTypeAtPosition({5, 26})));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "and_constraint")
|
||||
@ -202,16 +178,8 @@ TEST_CASE_FIXTURE(Fixture, "and_constraint")
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("(string?) & ~(false?)", toString(requireTypeAtPosition({3, 26})));
|
||||
CHECK_EQ("(number?) & ~(false?)", toString(requireTypeAtPosition({4, 26})));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("string", toString(requireTypeAtPosition({3, 26})));
|
||||
CHECK_EQ("number", toString(requireTypeAtPosition({4, 26})));
|
||||
}
|
||||
CHECK_EQ("string", toString(requireTypeAtPosition({3, 26})));
|
||||
CHECK_EQ("number", toString(requireTypeAtPosition({4, 26})));
|
||||
|
||||
CHECK_EQ("string?", toString(requireTypeAtPosition({6, 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("number?", toString(requireTypeAtPosition({4, 26})));
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("(string?) & ~(false?)", toString(requireTypeAtPosition({6, 26})));
|
||||
CHECK_EQ("(number?) & ~(false?)", toString(requireTypeAtPosition({7, 26})));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("string", toString(requireTypeAtPosition({6, 26})));
|
||||
CHECK_EQ("number", toString(requireTypeAtPosition({7, 26})));
|
||||
}
|
||||
CHECK_EQ("string", toString(requireTypeAtPosition({6, 26})));
|
||||
CHECK_EQ("number", toString(requireTypeAtPosition({7, 26})));
|
||||
}
|
||||
|
||||
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("number?", toString(requireTypeAtPosition({4, 26})));
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("(string?) & ~~(false?)", toString(requireTypeAtPosition({6, 26})));
|
||||
CHECK_EQ("(number?) & ~~(false?)", toString(requireTypeAtPosition({7, 26})));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("nil", toString(requireTypeAtPosition({6, 26})));
|
||||
CHECK_EQ("nil", toString(requireTypeAtPosition({7, 26})));
|
||||
}
|
||||
CHECK_EQ("nil", toString(requireTypeAtPosition({6, 26})));
|
||||
CHECK_EQ("nil", toString(requireTypeAtPosition({7, 26})));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "a_and_b_or_a_and_c")
|
||||
@ -297,26 +249,17 @@ TEST_CASE_FIXTURE(Fixture, "a_and_b_or_a_and_c")
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("(string?) & (~(false?) | ~(false?))", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("number?", toString(requireTypeAtPosition({4, 28})));
|
||||
CHECK_EQ("boolean", toString(requireTypeAtPosition({5, 28})));
|
||||
|
||||
CHECK_EQ("string?", toString(requireTypeAtPosition({7, 28})));
|
||||
CHECK_EQ("number?", toString(requireTypeAtPosition({8, 28})));
|
||||
CHECK_EQ("boolean", toString(requireTypeAtPosition({9, 28})));
|
||||
}
|
||||
CHECK_EQ("string", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("number?", toString(requireTypeAtPosition({4, 28})));
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("boolean", toString(requireTypeAtPosition({5, 28})));
|
||||
else
|
||||
{
|
||||
CHECK_EQ("string", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("number?", toString(requireTypeAtPosition({4, 28})));
|
||||
CHECK_EQ("true", toString(requireTypeAtPosition({5, 28}))); // oh no! :(
|
||||
|
||||
CHECK_EQ("string?", toString(requireTypeAtPosition({7, 28})));
|
||||
CHECK_EQ("number?", toString(requireTypeAtPosition({8, 28})));
|
||||
CHECK_EQ("boolean", toString(requireTypeAtPosition({9, 28})));
|
||||
}
|
||||
CHECK_EQ("string?", toString(requireTypeAtPosition({7, 28})));
|
||||
CHECK_EQ("number?", toString(requireTypeAtPosition({8, 28})));
|
||||
CHECK_EQ("boolean", toString(requireTypeAtPosition({9, 28})));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "type_assertion_expr_carry_its_constraints")
|
||||
@ -357,14 +300,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_in_if_condition_position")
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("any & number", toString(requireTypeAtPosition({3, 26})));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("number", toString(requireTypeAtPosition({3, 26})));
|
||||
}
|
||||
CHECK_EQ("number", toString(requireTypeAtPosition({3, 26})));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_in_assert_position")
|
||||
@ -433,8 +369,8 @@ TEST_CASE_FIXTURE(Fixture, "truthy_constraint_on_properties")
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK("{| x: number? |} & {| x: ~(false?) |}" == toString(requireTypeAtPosition({4, 23})));
|
||||
CHECK("(number?) & ~(false?)" == toString(requireTypeAtPosition({5, 26})));
|
||||
CHECK("{| x: number |}" == toString(requireTypeAtPosition({4, 23})));
|
||||
CHECK("number" == toString(requireTypeAtPosition({5, 26})));
|
||||
}
|
||||
|
||||
CHECK_EQ("number?", toString(requireType("bar")));
|
||||
@ -478,22 +414,11 @@ TEST_CASE_FIXTURE(Fixture, "lvalue_is_equal_to_another_lvalue")
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "((number | string)?) & unknown"); // a == b
|
||||
CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "(boolean?) & unknown"); // a == b
|
||||
CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "(number | string)?"); // a == b
|
||||
CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "boolean?"); // a == b
|
||||
|
||||
CHECK_EQ(toString(requireTypeAtPosition({5, 33})), "((number | string)?) & unknown"); // a ~= b
|
||||
CHECK_EQ(toString(requireTypeAtPosition({5, 36})), "(boolean?) & unknown"); // a ~= b
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "(number | string)?"); // a == b
|
||||
CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "boolean?"); // a == b
|
||||
|
||||
CHECK_EQ(toString(requireTypeAtPosition({5, 33})), "(number | string)?"); // a ~= b
|
||||
CHECK_EQ(toString(requireTypeAtPosition({5, 36})), "boolean?"); // a ~= b
|
||||
}
|
||||
CHECK_EQ(toString(requireTypeAtPosition({5, 33})), "(number | string)?"); // a ~= b
|
||||
CHECK_EQ(toString(requireTypeAtPosition({5, 36})), "boolean?"); // a ~= b
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "lvalue_is_equal_to_a_term")
|
||||
@ -510,16 +435,8 @@ TEST_CASE_FIXTURE(Fixture, "lvalue_is_equal_to_a_term")
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ(toString(requireTypeAtPosition({3, 28})), "((number | string)?) & unknown"); // a == 1
|
||||
CHECK_EQ(toString(requireTypeAtPosition({5, 28})), "((number | string)?) & unknown"); // a ~= 1
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ(toString(requireTypeAtPosition({3, 28})), "(number | string)?"); // a == 1;
|
||||
CHECK_EQ(toString(requireTypeAtPosition({5, 28})), "(number | string)?"); // a ~= 1
|
||||
}
|
||||
CHECK_EQ(toString(requireTypeAtPosition({3, 28})), "(number | string)?"); // a == 1;
|
||||
CHECK_EQ(toString(requireTypeAtPosition({5, 28})), "(number | string)?"); // a ~= 1
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "term_is_equal_to_an_lvalue")
|
||||
@ -538,8 +455,8 @@ TEST_CASE_FIXTURE(Fixture, "term_is_equal_to_an_lvalue")
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ(toString(requireTypeAtPosition({3, 28})), R"("hello" & ((number | string)?))"); // a == "hello"
|
||||
CHECK_EQ(toString(requireTypeAtPosition({5, 28})), R"(((number | string)?) & ~"hello")"); // a ~= "hello"
|
||||
CHECK_EQ(toString(requireTypeAtPosition({3, 28})), R"("hello")"); // a == "hello"
|
||||
CHECK_EQ(toString(requireTypeAtPosition({5, 28})), R"(((string & ~"hello") | number)?)"); // a ~= "hello"
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -562,16 +479,8 @@ TEST_CASE_FIXTURE(Fixture, "lvalue_is_not_nil")
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ(toString(requireTypeAtPosition({3, 28})), "((number | string)?) & ~nil"); // a ~= nil
|
||||
CHECK_EQ(toString(requireTypeAtPosition({5, 28})), "((number | string)?) & unknown"); // a == nil
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ(toString(requireTypeAtPosition({3, 28})), "number | string"); // a ~= nil
|
||||
CHECK_EQ(toString(requireTypeAtPosition({5, 28})), "(number | string)?"); // a == nil
|
||||
}
|
||||
CHECK_EQ(toString(requireTypeAtPosition({3, 28})), "number | string"); // a ~= nil
|
||||
CHECK_EQ(toString(requireTypeAtPosition({5, 28})), "(number | string)?"); // a == nil
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "free_type_is_equal_to_an_lvalue")
|
||||
@ -586,17 +495,8 @@ TEST_CASE_FIXTURE(Fixture, "free_type_is_equal_to_an_lvalue")
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
ToStringOptions opts;
|
||||
CHECK_EQ(toString(requireTypeAtPosition({3, 33}), opts), "a & unknown"); // a == b
|
||||
CHECK_EQ(toString(requireTypeAtPosition({3, 36}), opts), "(string?) & unknown"); // a == b
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "a"); // a == b
|
||||
CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "string?"); // a == b
|
||||
}
|
||||
CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "a"); // a == b
|
||||
CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "string?"); // a == b
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "unknown_lvalue_is_not_synonymous_with_other_on_not_equal")
|
||||
@ -611,16 +511,8 @@ TEST_CASE_FIXTURE(Fixture, "unknown_lvalue_is_not_synonymous_with_other_on_not_e
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "any & unknown"); // a ~= b
|
||||
CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "({| x: number |}?) & unknown"); // a ~= b
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "any"); // a ~= b
|
||||
CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "{| x: number |}?"); // a ~= b
|
||||
}
|
||||
CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "any"); // a ~= b
|
||||
CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "{| x: number |}?"); // a ~= b
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "string_not_equal_to_string_or_nil")
|
||||
@ -639,22 +531,11 @@ TEST_CASE_FIXTURE(Fixture, "string_not_equal_to_string_or_nil")
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ(toString(requireTypeAtPosition({6, 29})), "string & unknown"); // a ~= b
|
||||
CHECK_EQ(toString(requireTypeAtPosition({6, 32})), "(string?) & unknown"); // a ~= b
|
||||
CHECK_EQ(toString(requireTypeAtPosition({6, 29})), "string"); // a ~= b
|
||||
CHECK_EQ(toString(requireTypeAtPosition({6, 32})), "string?"); // a ~= b
|
||||
|
||||
CHECK_EQ(toString(requireTypeAtPosition({8, 29})), "string & unknown"); // a == b
|
||||
CHECK_EQ(toString(requireTypeAtPosition({8, 32})), "(string?) & unknown"); // a == b
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ(toString(requireTypeAtPosition({6, 29})), "string"); // a ~= b
|
||||
CHECK_EQ(toString(requireTypeAtPosition({6, 32})), "string?"); // a ~= b
|
||||
|
||||
CHECK_EQ(toString(requireTypeAtPosition({8, 29})), "string"); // a == b
|
||||
CHECK_EQ(toString(requireTypeAtPosition({8, 32})), "string?"); // a == b
|
||||
}
|
||||
CHECK_EQ(toString(requireTypeAtPosition({8, 29})), "string"); // a == b
|
||||
CHECK_EQ(toString(requireTypeAtPosition({8, 32})), "string?"); // a == b
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "narrow_property_of_a_bounded_variable")
|
||||
@ -729,16 +610,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_not_to_be_string")
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("(boolean | number | string) & ~string", toString(requireTypeAtPosition({3, 28}))); // type(x) ~= "string"
|
||||
CHECK_EQ("(boolean | number | string) & string", toString(requireTypeAtPosition({5, 28}))); // type(x) == "string"
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("boolean | number", toString(requireTypeAtPosition({3, 28}))); // type(x) ~= "string"
|
||||
CHECK_EQ("string", toString(requireTypeAtPosition({5, 28}))); // type(x) == "string"
|
||||
}
|
||||
CHECK_EQ("boolean | number", toString(requireTypeAtPosition({3, 28}))); // type(x) ~= "string"
|
||||
CHECK_EQ("string", toString(requireTypeAtPosition({5, 28}))); // type(x) == "string"
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_narrows_for_table")
|
||||
@ -773,16 +646,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_narrows_for_functions")
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("(((number) -> string) | string) & function", toString(requireTypeAtPosition({3, 28}))); // type(x) == "function"
|
||||
CHECK_EQ("(((number) -> string) | string) & ~function", toString(requireTypeAtPosition({5, 28}))); // type(x) ~= "function"
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("(number) -> string", toString(requireTypeAtPosition({3, 28}))); // type(x) == "function"
|
||||
CHECK_EQ("string", toString(requireTypeAtPosition({5, 28}))); // type(x) ~= "function"
|
||||
}
|
||||
CHECK_EQ("(number) -> string", toString(requireTypeAtPosition({3, 28}))); // type(x) == "function"
|
||||
CHECK_EQ("string", toString(requireTypeAtPosition({5, 28}))); // type(x) ~= "function"
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "type_guard_can_filter_for_intersection_of_tables")
|
||||
@ -821,16 +686,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_guard_can_filter_for_overloaded_functio
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("((((number) -> string) & ((string) -> number))?) & function", toString(requireTypeAtPosition({4, 28})));
|
||||
CHECK_EQ("((((number) -> string) & ((string) -> number))?) & ~function", toString(requireTypeAtPosition({6, 28})));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("((number) -> string) & ((string) -> number)", toString(requireTypeAtPosition({4, 28})));
|
||||
CHECK_EQ("nil", toString(requireTypeAtPosition({6, 28})));
|
||||
}
|
||||
CHECK_EQ("((number) -> string) & ((string) -> number)", toString(requireTypeAtPosition({4, 28})));
|
||||
CHECK_EQ("nil", toString(requireTypeAtPosition({6, 28})));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "type_guard_narrowed_into_nothingness")
|
||||
@ -898,16 +755,8 @@ TEST_CASE_FIXTURE(Fixture, "not_a_and_not_b")
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("(number?) & ~~(false?)", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("(number?) & ~~(false?)", toString(requireTypeAtPosition({4, 28})));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("nil", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("nil", toString(requireTypeAtPosition({4, 28})));
|
||||
}
|
||||
CHECK_EQ("nil", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("nil", toString(requireTypeAtPosition({4, 28})));
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("(number?) & ~~(false?)", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("(number?) & ~~(false?)", toString(requireTypeAtPosition({4, 28})));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("nil", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("nil", toString(requireTypeAtPosition({4, 28})));
|
||||
}
|
||||
CHECK_EQ("nil", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("nil", toString(requireTypeAtPosition({4, 28})));
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("(number | string) & any", toString(requireTypeAtPosition({3, 28})));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("number | string", toString(requireTypeAtPosition({3, 28})));
|
||||
}
|
||||
CHECK_EQ("number | string", toString(requireTypeAtPosition({3, 28})));
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("((number | string)?) & ~(false?)", toString(requireTypeAtPosition({3, 18})));
|
||||
CHECK_EQ("((number | string)?) & ~(false?) & number", toString(requireTypeAtPosition({5, 18})));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("number | string", toString(requireTypeAtPosition({3, 18})));
|
||||
CHECK_EQ("number", toString(requireTypeAtPosition({5, 18})));
|
||||
}
|
||||
CHECK_EQ("number | string", toString(requireTypeAtPosition({3, 18})));
|
||||
CHECK_EQ("number", toString(requireTypeAtPosition({5, 18})));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "merge_should_be_fully_agnostic_of_hashmap_ordering")
|
||||
@ -1012,14 +838,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "merge_should_be_fully_agnostic_of_hashmap_or
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("(string | table) & (string | {| x: string |}) & string", toString(requireTypeAtPosition({6, 28})));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("string", toString(requireTypeAtPosition({6, 28})));
|
||||
}
|
||||
CHECK_EQ("string", toString(requireTypeAtPosition({6, 28})));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "refine_the_correct_types_opposite_of_when_a_is_not_number_or_string")
|
||||
@ -1036,16 +855,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "refine_the_correct_types_opposite_of_when_a_
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("(boolean | number | string) & ~number & ~string", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("(boolean | number | string) & (number | string)", toString(requireTypeAtPosition({5, 28})));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("boolean", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("number | string", toString(requireTypeAtPosition({5, 28})));
|
||||
}
|
||||
CHECK_EQ("boolean", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("number | string", toString(requireTypeAtPosition({5, 28})));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "is_truthy_constraint_ifelse_expression")
|
||||
@ -1058,16 +869,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "is_truthy_constraint_ifelse_expression")
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("(string?) & ~(false?)", toString(requireTypeAtPosition({2, 29})));
|
||||
CHECK_EQ("(string?) & ~~(false?)", toString(requireTypeAtPosition({2, 45})));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("string", toString(requireTypeAtPosition({2, 29})));
|
||||
CHECK_EQ("nil", toString(requireTypeAtPosition({2, 45})));
|
||||
}
|
||||
CHECK_EQ("string", toString(requireTypeAtPosition({2, 29})));
|
||||
CHECK_EQ("nil", toString(requireTypeAtPosition({2, 45})));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "invert_is_truthy_constraint_ifelse_expression")
|
||||
@ -1080,16 +883,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "invert_is_truthy_constraint_ifelse_expressio
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("(string?) & ~~(false?)", toString(requireTypeAtPosition({2, 42})));
|
||||
CHECK_EQ("(string?) & ~(false?)", toString(requireTypeAtPosition({2, 50})));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("nil", toString(requireTypeAtPosition({2, 42})));
|
||||
CHECK_EQ("string", toString(requireTypeAtPosition({2, 50})));
|
||||
}
|
||||
CHECK_EQ("nil", toString(requireTypeAtPosition({2, 42})));
|
||||
CHECK_EQ("string", toString(requireTypeAtPosition({2, 50})));
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
CHECK_EQ("number", toString(requireTypeAtPosition({6, 49})));
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("any & number", toString(requireTypeAtPosition({6, 49})));
|
||||
CHECK_EQ("any & ~number", toString(requireTypeAtPosition({6, 66})));
|
||||
}
|
||||
CHECK_EQ("~number", toString(requireTypeAtPosition({6, 66})));
|
||||
else
|
||||
{
|
||||
CHECK_EQ("number", toString(requireTypeAtPosition({6, 49})));
|
||||
CHECK_EQ("any", toString(requireTypeAtPosition({6, 66})));
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
CHECK_EQ(R"({| tag: "exists", x: string |})", toString(requireTypeAtPosition({5, 28})));
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ(R"(({| tag: "exists", x: string |} | {| tag: "missing", x: nil |}) & {| x: ~(false?) |})", toString(requireTypeAtPosition({5, 28})));
|
||||
CHECK_EQ(
|
||||
R"(({| tag: "exists", x: string |} | {| tag: "missing", x: nil |}) & {| x: ~~(false?) |})", toString(requireTypeAtPosition({7, 28})));
|
||||
}
|
||||
CHECK_EQ(R"({| tag: "missing", x: nil |})", toString(requireTypeAtPosition({7, 28})));
|
||||
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})));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "discriminate_tag")
|
||||
@ -1229,8 +1013,8 @@ TEST_CASE_FIXTURE(Fixture, "discriminate_tag")
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ(R"((Cat | Dog) & {| tag: "Cat" |})", toString(requireTypeAtPosition({7, 33})));
|
||||
CHECK_EQ(R"((Cat | Dog) & {| tag: ~"Cat" |} & {| tag: "Dog" |})", toString(requireTypeAtPosition({9, 33})));
|
||||
CHECK_EQ(R"({| catfood: string, name: string, tag: "Cat" |})", toString(requireTypeAtPosition({7, 33})));
|
||||
CHECK_EQ(R"({| dogfood: string, name: string, tag: "Dog" |})", toString(requireTypeAtPosition({9, 33})));
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -1259,8 +1043,8 @@ TEST_CASE_FIXTURE(Fixture, "discriminate_tag_with_implicit_else")
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ(R"((Cat | Dog) & {| tag: "Cat" |})", toString(requireTypeAtPosition({7, 33})));
|
||||
CHECK_EQ(R"((Cat | Dog) & {| tag: ~"Cat" |})", toString(requireTypeAtPosition({9, 33})));
|
||||
CHECK_EQ(R"({| catfood: string, name: string, tag: "Cat" |})", toString(requireTypeAtPosition({7, 33})));
|
||||
CHECK_EQ(R"({| dogfood: string, name: string, tag: "Dog" |})", toString(requireTypeAtPosition({9, 33})));
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -1294,16 +1078,8 @@ TEST_CASE_FIXTURE(Fixture, "narrow_boolean_to_true_or_false")
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("boolean & ~(false?)", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("boolean & ~~(false?)", toString(requireTypeAtPosition({5, 28})));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("true", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("false", toString(requireTypeAtPosition({5, 28})));
|
||||
}
|
||||
CHECK_EQ("true", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("false", toString(requireTypeAtPosition({5, 28})));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "discriminate_on_properties_of_disjoint_tables_where_that_property_is_true_or_false")
|
||||
@ -1355,16 +1131,8 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "discriminate_from_isa_of_x")
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ(R"(({| tag: "Folder", x: Folder |} | {| tag: "Part", x: Part |}) & {| x: Part |})", toString(requireTypeAtPosition({5, 28})));
|
||||
CHECK_EQ(R"(({| tag: "Folder", x: Folder |} | {| tag: "Part", x: Part |}) & {| x: ~Part |})", toString(requireTypeAtPosition({7, 28})));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ(R"({| tag: "Part", x: Part |})", toString(requireTypeAtPosition({5, 28})));
|
||||
CHECK_EQ(R"({| tag: "Folder", x: Folder |})", toString(requireTypeAtPosition({7, 28})));
|
||||
}
|
||||
CHECK_EQ(R"({| tag: "Part", x: Part |})", toString(requireTypeAtPosition({5, 28})));
|
||||
CHECK_EQ(R"({| tag: "Folder", x: Folder |})", toString(requireTypeAtPosition({7, 28})));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(RefinementClassFixture, "typeguard_cast_free_table_to_vector")
|
||||
@ -1406,16 +1174,8 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "typeguard_cast_instance_or_vector3_to
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("(Instance | Vector3) & Vector3", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("(Instance | Vector3) & ~Vector3", toString(requireTypeAtPosition({5, 28})));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("Vector3", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("Instance", toString(requireTypeAtPosition({5, 28})));
|
||||
}
|
||||
CHECK_EQ("Vector3", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("Instance", toString(requireTypeAtPosition({5, 28})));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(RefinementClassFixture, "type_narrow_for_all_the_userdata")
|
||||
@ -1452,8 +1212,8 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "type_narrow_but_the_discriminant_type
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("(Instance | Vector3 | number | string) & never", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("(Instance | Vector3 | number | string) & ~never", toString(requireTypeAtPosition({5, 28})));
|
||||
CHECK_EQ("never", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("Instance | Vector3 | number | string", toString(requireTypeAtPosition({5, 28})));
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -1476,16 +1236,8 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "eliminate_subclasses_of_instance")
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("(Folder | Part | string) & Instance", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("(Folder | Part | string) & ~Instance", toString(requireTypeAtPosition({5, 28})));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("Folder | Part", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("string", toString(requireTypeAtPosition({5, 28})));
|
||||
}
|
||||
CHECK_EQ("Folder | Part", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("string", toString(requireTypeAtPosition({5, 28})));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(RefinementClassFixture, "narrow_from_subclasses_of_instance_or_string_or_vector3")
|
||||
@ -1502,16 +1254,8 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "narrow_from_subclasses_of_instance_or
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("(Folder | Part | Vector3 | string) & Instance", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("(Folder | Part | Vector3 | string) & ~Instance", toString(requireTypeAtPosition({5, 28})));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("Folder | Part", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("Vector3 | string", toString(requireTypeAtPosition({5, 28})));
|
||||
}
|
||||
CHECK_EQ("Folder | Part", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("Vector3 | string", toString(requireTypeAtPosition({5, 28})));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(RefinementClassFixture, "x_as_any_if_x_is_instance_elseif_x_is_table")
|
||||
@ -1556,16 +1300,8 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "refine_param_of_type_instance_without
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("Folder & Instance", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("Instance & ~Folder & table", toString(requireTypeAtPosition({5, 28})));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("Folder", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("never", toString(requireTypeAtPosition({5, 28})));
|
||||
}
|
||||
CHECK_EQ("Folder", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("never", toString(requireTypeAtPosition({5, 28})));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(RefinementClassFixture, "refine_param_of_type_folder_or_part_without_using_typeof")
|
||||
@ -1582,16 +1318,8 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "refine_param_of_type_folder_or_part_w
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("(Folder | Part) & Folder", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("(Folder | Part) & ~Folder", toString(requireTypeAtPosition({5, 28})));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("Folder", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("Part", toString(requireTypeAtPosition({5, 28})));
|
||||
}
|
||||
CHECK_EQ("Folder", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("Part", toString(requireTypeAtPosition({5, 28})));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(RefinementClassFixture, "isa_type_refinement_must_be_known_ahead_of_time")
|
||||
@ -1610,16 +1338,8 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "isa_type_refinement_must_be_known_ahe
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("Instance", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("Instance", toString(requireTypeAtPosition({5, 28})));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("Instance", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("Instance", toString(requireTypeAtPosition({5, 28})));
|
||||
}
|
||||
CHECK_EQ("Instance", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("Instance", toString(requireTypeAtPosition({5, 28})));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(RefinementClassFixture, "x_is_not_instance_or_else_not_part")
|
||||
@ -1673,8 +1393,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "refine_unknowns")
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("unknown & string", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("unknown & ~string", toString(requireTypeAtPosition({5, 28})));
|
||||
CHECK_EQ("string", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("~string", toString(requireTypeAtPosition({5, 28})));
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -1714,14 +1434,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "what_nonsensical_condition")
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("a & number & string", toString(requireTypeAtPosition({3, 28})));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("never", toString(requireTypeAtPosition({3, 28})));
|
||||
}
|
||||
CHECK_EQ("never", toString(requireTypeAtPosition({3, 28})));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "else_with_no_explicit_expression_should_also_refine_the_tagged_union")
|
||||
@ -1752,7 +1465,30 @@ local _ = _ ~= _ or _ or _
|
||||
end
|
||||
)");
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
// Without a realistic motivating case, it's hard to tell if it's important for this to work without errors.
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK(get<NormalizationTooComplex>(result.errors[0]));
|
||||
}
|
||||
else
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "refine_unknown_to_table_then_take_the_length")
|
||||
{
|
||||
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: unknown)
|
||||
if typeof(x) == "table" then
|
||||
local len = #x
|
||||
end
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK_EQ("table", toString(requireTypeAtPosition({3, 29})));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -3435,4 +3435,62 @@ _ = _._
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_table_unify_instantiated_table")
|
||||
{
|
||||
ScopedFastFlag sff[]{
|
||||
{"LuauInstantiateInSubtyping", true},
|
||||
{"LuauScalarShapeUnifyToMtOwner2", true},
|
||||
{"LuauTableUnifyInstantiationFix", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function _(...)
|
||||
end
|
||||
local function l0():typeof(_()()[_()()[_]])
|
||||
end
|
||||
return _[_()()[_]] <= _
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "fuzz_table_unify_instantiated_table_with_prop_realloc")
|
||||
{
|
||||
ScopedFastFlag sff[]{
|
||||
{"LuauInstantiateInSubtyping", true},
|
||||
{"LuauScalarShapeUnifyToMtOwner2", true},
|
||||
{"LuauTableUnifyInstantiationFix", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function _(l0,l0)
|
||||
do
|
||||
_ = _().n0
|
||||
end
|
||||
l0(_()._,_)
|
||||
end
|
||||
_(_,function(...)
|
||||
end)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_table_unify_prop_realloc")
|
||||
{
|
||||
// For this test, we don't need LuauInstantiateInSubtyping
|
||||
ScopedFastFlag sff[]{
|
||||
{"LuauScalarShapeUnifyToMtOwner2", true},
|
||||
{"LuauTableUnifyInstantiationFix", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
n3,_ = nil
|
||||
_ = _[""]._,_[l0][_._][{[_]=_,_=_,}][_G].number
|
||||
_ = {_,}
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -1014,7 +1014,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_packs_with_tails_in_vararg_adjustment")
|
||||
end)
|
||||
return result
|
||||
end
|
||||
end
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
@ -116,11 +116,23 @@ TEST_CASE_FIXTURE(Fixture, "type_packs_containing_never_is_itself_uninhabitable"
|
||||
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("never", toString(requireType("y")));
|
||||
CHECK_EQ("never", toString(requireType("z")));
|
||||
CHECK_EQ("string", toString(requireType("x")));
|
||||
CHECK_EQ("never", toString(requireType("y")));
|
||||
CHECK_EQ("*error-type*", toString(requireType("z")));
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("never", toString(requireType("x")));
|
||||
CHECK_EQ("never", toString(requireType("y")));
|
||||
CHECK_EQ("never", toString(requireType("z")));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "type_packs_containing_never_is_itself_uninhabitable2")
|
||||
@ -135,10 +147,20 @@ TEST_CASE_FIXTURE(Fixture, "type_packs_containing_never_is_itself_uninhabitable2
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("never", toString(requireType("x1")));
|
||||
CHECK_EQ("never", toString(requireType("x2")));
|
||||
CHECK_EQ("never", toString(requireType("y1")));
|
||||
CHECK_EQ("never", toString(requireType("y2")));
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("string", toString(requireType("x1")));
|
||||
CHECK_EQ("never", toString(requireType("x2")));
|
||||
CHECK_EQ("never", toString(requireType("y1")));
|
||||
CHECK_EQ("string", toString(requireType("y2")));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("never", toString(requireType("x1")));
|
||||
CHECK_EQ("never", toString(requireType("x2")));
|
||||
CHECK_EQ("never", toString(requireType("y1")));
|
||||
CHECK_EQ("never", toString(requireType("y2")));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "index_on_never")
|
||||
@ -290,8 +312,14 @@ TEST_CASE_FIXTURE(Fixture, "dont_unify_operands_if_one_of_the_operand_is_never_i
|
||||
)");
|
||||
|
||||
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")
|
||||
|
@ -482,6 +482,24 @@ TEST_CASE_FIXTURE(ReductionFixture, "intersections_without_negations")
|
||||
CHECK("{| [string]: number, p: string |}" == toStringFull(ty));
|
||||
}
|
||||
|
||||
SUBCASE("array_number_and_array_string")
|
||||
{
|
||||
TypeId ty = reductionof("{number} & {string}");
|
||||
CHECK("{never}" == toStringFull(ty));
|
||||
}
|
||||
|
||||
SUBCASE("array_string_and_array_string")
|
||||
{
|
||||
TypeId ty = reductionof("{string} & {string}");
|
||||
CHECK("{string}" == toStringFull(ty));
|
||||
}
|
||||
|
||||
SUBCASE("array_string_or_number_and_array_string")
|
||||
{
|
||||
TypeId ty = reductionof("{string | number} & {string}");
|
||||
CHECK("{string}" == toStringFull(ty));
|
||||
}
|
||||
|
||||
SUBCASE("fresh_type_and_string")
|
||||
{
|
||||
TypeId freshTy = arena.freshType(nullptr);
|
||||
@ -690,7 +708,7 @@ TEST_CASE_FIXTURE(ReductionFixture, "intersections_with_negations")
|
||||
SUBCASE("string_and_not_error")
|
||||
{
|
||||
TypeId ty = reductionof("string & Not<err>");
|
||||
CHECK("string & ~*error-type*" == toStringFull(ty));
|
||||
CHECK("string" == toStringFull(ty));
|
||||
}
|
||||
|
||||
SUBCASE("table_p_string_and_table_p_not_number")
|
||||
@ -711,6 +729,12 @@ TEST_CASE_FIXTURE(ReductionFixture, "intersections_with_negations")
|
||||
CHECK("{| x: {| p: string |} |}" == toStringFull(ty));
|
||||
}
|
||||
|
||||
SUBCASE("table_or_nil_and_truthy")
|
||||
{
|
||||
TypeId ty = reductionof("({ x: number | string }?) & Not<false?>");
|
||||
CHECK("{| x: number | string |}" == toString(ty));
|
||||
}
|
||||
|
||||
SUBCASE("not_top_table_and_table")
|
||||
{
|
||||
TypeId ty = reductionof("Not<tbl> & {}");
|
||||
@ -1251,6 +1275,12 @@ TEST_CASE_FIXTURE(ReductionFixture, "tables")
|
||||
TypeId ty = reductionof("{ x: { y: string & number } }");
|
||||
CHECK("never" == toStringFull(ty));
|
||||
}
|
||||
|
||||
SUBCASE("array_of_never")
|
||||
{
|
||||
TypeId ty = reductionof("{never}");
|
||||
CHECK("{never}" == toStringFull(ty));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ReductionFixture, "metatables")
|
||||
|
@ -1,6 +1,5 @@
|
||||
AnnotationTests.corecursive_types_error_on_tight_loop
|
||||
AnnotationTests.duplicate_type_param_name
|
||||
AnnotationTests.for_loop_counter_annotation_is_checked
|
||||
AnnotationTests.generic_aliases_are_cloned_properly
|
||||
AnnotationTests.occurs_check_on_cyclic_intersection_type
|
||||
AnnotationTests.occurs_check_on_cyclic_union_type
|
||||
@ -18,12 +17,8 @@ AutocompleteTest.keyword_methods
|
||||
AutocompleteTest.no_incompatible_self_calls
|
||||
AutocompleteTest.no_wrong_compatible_self_calls_with_generics
|
||||
AutocompleteTest.string_singleton_as_table_key
|
||||
AutocompleteTest.suggest_external_module_type
|
||||
AutocompleteTest.suggest_table_keys
|
||||
AutocompleteTest.type_correct_argument_type_suggestion
|
||||
AutocompleteTest.type_correct_expected_argument_type_pack_suggestion
|
||||
AutocompleteTest.type_correct_expected_argument_type_suggestion
|
||||
AutocompleteTest.type_correct_expected_argument_type_suggestion_optional
|
||||
AutocompleteTest.type_correct_expected_argument_type_suggestion_self
|
||||
AutocompleteTest.type_correct_expected_return_type_pack_suggestion
|
||||
AutocompleteTest.type_correct_expected_return_type_suggestion
|
||||
@ -118,37 +113,28 @@ ParserTests.parse_nesting_based_end_detection_failsafe_earlier
|
||||
ParserTests.parse_nesting_based_end_detection_local_function
|
||||
ProvisionalTests.assign_table_with_refined_property_with_a_similar_type_is_illegal
|
||||
ProvisionalTests.bail_early_if_unification_is_too_complicated
|
||||
ProvisionalTests.discriminate_from_x_not_equal_to_nil
|
||||
ProvisionalTests.do_not_ice_when_trying_to_pick_first_of_generic_type_pack
|
||||
ProvisionalTests.error_on_eq_metamethod_returning_a_type_other_than_boolean
|
||||
ProvisionalTests.free_options_cannot_be_unified_together
|
||||
ProvisionalTests.generic_type_leak_to_module_interface_variadic
|
||||
ProvisionalTests.greedy_inference_with_shared_self_triggers_function_with_no_returns
|
||||
ProvisionalTests.lvalue_equals_another_lvalue_with_no_overlap
|
||||
ProvisionalTests.pcall_returns_at_least_two_value_but_function_returns_nothing
|
||||
ProvisionalTests.refine_unknown_to_table_then_clone_it
|
||||
ProvisionalTests.setmetatable_constrains_free_type_into_free_table
|
||||
ProvisionalTests.specialization_binds_with_prototypes_too_early
|
||||
ProvisionalTests.table_insert_with_a_singleton_argument
|
||||
ProvisionalTests.typeguard_inference_incomplete
|
||||
ProvisionalTests.weirditer_should_not_loop_forever
|
||||
RefinementTest.apply_refinements_on_astexprindexexpr_whose_subscript_expr_is_constant_string
|
||||
RefinementTest.call_an_incompatible_function_after_using_typeguard
|
||||
RefinementTest.correctly_lookup_property_whose_base_was_previously_refined2
|
||||
RefinementTest.discriminate_on_properties_of_disjoint_tables_where_that_property_is_true_or_false
|
||||
RefinementTest.discriminate_tag
|
||||
RefinementTest.else_with_no_explicit_expression_should_also_refine_the_tagged_union
|
||||
RefinementTest.falsiness_of_TruthyPredicate_narrows_into_nil
|
||||
RefinementTest.narrow_property_of_a_bounded_variable
|
||||
RefinementTest.nonoptional_type_can_narrow_to_nil_if_sense_is_true
|
||||
RefinementTest.refine_a_property_not_to_be_nil_through_an_intersection_table
|
||||
RefinementTest.refine_unknowns
|
||||
RefinementTest.type_guard_can_filter_for_intersection_of_tables
|
||||
RefinementTest.type_guard_narrowed_into_nothingness
|
||||
RefinementTest.type_narrow_for_all_the_userdata
|
||||
RefinementTest.type_narrow_to_vector
|
||||
RefinementTest.typeguard_cast_free_table_to_vector
|
||||
RefinementTest.typeguard_in_assert_position
|
||||
RefinementTest.typeguard_narrows_for_table
|
||||
RefinementTest.x_as_any_if_x_is_instance_elseif_x_is_table
|
||||
RefinementTest.x_is_not_instance_or_else_not_part
|
||||
RuntimeLimits.typescript_port_of_Result_type
|
||||
@ -178,6 +164,8 @@ TableTests.found_like_key_in_table_function_call
|
||||
TableTests.found_like_key_in_table_property_access
|
||||
TableTests.found_multiple_like_keys
|
||||
TableTests.function_calls_produces_sealed_table_given_unsealed_table
|
||||
TableTests.fuzz_table_unify_instantiated_table
|
||||
TableTests.fuzz_table_unify_instantiated_table_with_prop_realloc
|
||||
TableTests.generic_table_instantiation_potential_regression
|
||||
TableTests.give_up_after_one_metatable_index_look_up
|
||||
TableTests.indexer_on_sealed_table_must_unify_with_free_table
|
||||
@ -220,9 +208,9 @@ TableTests.table_param_row_polymorphism_3
|
||||
TableTests.table_simple_call
|
||||
TableTests.table_subtyping_with_extra_props_dont_report_multiple_errors
|
||||
TableTests.table_subtyping_with_missing_props_dont_report_multiple_errors
|
||||
TableTests.table_unification_4
|
||||
TableTests.tc_member_function
|
||||
TableTests.tc_member_function_2
|
||||
TableTests.unification_of_unions_in_a_self_referential_type
|
||||
TableTests.unifying_tables_shouldnt_uaf1
|
||||
TableTests.unifying_tables_shouldnt_uaf2
|
||||
TableTests.used_colon_correctly
|
||||
@ -357,9 +345,7 @@ TypeInferOperators.disallow_string_and_types_without_metatables_from_arithmetic_
|
||||
TypeInferOperators.in_nonstrict_mode_strip_nil_from_intersections_when_considering_relational_operators
|
||||
TypeInferOperators.infer_any_in_all_modes_when_lhs_is_unknown
|
||||
TypeInferOperators.operator_eq_completely_incompatible
|
||||
TypeInferOperators.or_joins_types_with_no_superfluous_union
|
||||
TypeInferOperators.produce_the_correct_error_message_when_comparing_a_table_with_a_metatable_with_one_that_does_not
|
||||
TypeInferOperators.refine_and_or
|
||||
TypeInferOperators.typecheck_overloaded_multiply_that_is_an_intersection
|
||||
TypeInferOperators.typecheck_overloaded_multiply_that_is_an_intersection_on_rhs
|
||||
TypeInferOperators.UnknownGlobalCompoundAssign
|
||||
@ -368,16 +354,8 @@ TypeInferOperators.unrelated_primitives_cannot_be_compared
|
||||
TypeInferPrimitives.CheckMethodsOfNumber
|
||||
TypeInferPrimitives.string_index
|
||||
TypeInferUnknownNever.assign_to_global_which_is_never
|
||||
TypeInferUnknownNever.assign_to_local_which_is_never
|
||||
TypeInferUnknownNever.assign_to_prop_which_is_never
|
||||
TypeInferUnknownNever.assign_to_subscript_which_is_never
|
||||
TypeInferUnknownNever.call_never
|
||||
TypeInferUnknownNever.dont_unify_operands_if_one_of_the_operand_is_never_in_any_ordering_operators
|
||||
TypeInferUnknownNever.index_on_union_of_tables_for_properties_that_is_sorta_never
|
||||
TypeInferUnknownNever.math_operators_and_never
|
||||
TypeInferUnknownNever.type_packs_containing_never_is_itself_uninhabitable
|
||||
TypeInferUnknownNever.type_packs_containing_never_is_itself_uninhabitable2
|
||||
TypeInferUnknownNever.unary_minus_of_never
|
||||
TypePackTests.detect_cyclic_typepacks2
|
||||
TypePackTests.pack_tail_unification_check
|
||||
TypePackTests.self_and_varargs_should_work
|
||||
@ -401,24 +379,19 @@ TypePackTests.type_pack_type_parameters
|
||||
TypePackTests.unify_variadic_tails_in_arguments
|
||||
TypePackTests.unify_variadic_tails_in_arguments_free
|
||||
TypePackTests.variadic_packs
|
||||
TypeReductionTests.negations
|
||||
TypeSingletons.function_call_with_singletons
|
||||
TypeSingletons.function_call_with_singletons_mismatch
|
||||
TypeSingletons.indexing_on_string_singletons
|
||||
TypeSingletons.indexing_on_union_of_string_singletons
|
||||
TypeSingletons.overloaded_function_call_with_singletons
|
||||
TypeSingletons.overloaded_function_call_with_singletons_mismatch
|
||||
TypeSingletons.return_type_of_f_is_not_widened
|
||||
TypeSingletons.table_properties_singleton_strings_mismatch
|
||||
TypeSingletons.table_properties_type_error_escapes
|
||||
TypeSingletons.taking_the_length_of_string_singleton
|
||||
TypeSingletons.taking_the_length_of_union_of_string_singleton
|
||||
TypeSingletons.widen_the_supertype_if_it_is_free_and_subtype_has_singleton
|
||||
TypeSingletons.widening_happens_almost_everywhere
|
||||
TypeSingletons.widening_happens_almost_everywhere_except_for_tables
|
||||
UnionTypes.index_on_a_union_type_with_missing_property
|
||||
UnionTypes.index_on_a_union_type_with_one_optional_property
|
||||
UnionTypes.index_on_a_union_type_with_one_property_of_type_any
|
||||
UnionTypes.optional_assignment_errors
|
||||
UnionTypes.optional_call_error
|
||||
UnionTypes.optional_field_access_error
|
||||
|
Loading…
Reference in New Issue
Block a user