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