mirror of
https://github.com/luau-lang/luau.git
synced 2024-11-15 14:25:44 +08:00
* Progress toward a diffing algorithm for types. We hope that this will be useful for writing clearer error messages.
* Add a missing recursion limiter in `Unifier::tryUnifyTables`. This was causing a crash in certain situations. * Luau heap graph enumeration improvements: * Weak references are not reported * Added tag as a fallback name of non-string table links * Included top Luau function information in thread name to understand where thread might be suspended * Constant folding for `math.pi` and `math.huge` at -O2 * Optimize `string.format` and `%*` * This change makes string interpolation 1.5x-2x faster depending on the number and type of formatted components, assuming a few are using primitive types, and reduces associated GC pressure. New solver * Initial work toward tracking the upper and lower bounds of types more accurately. JIT * Add IrCmd::CHECK_TRUTHY for improved assert fast-calls * Do not compute type map for modules without types * Capture metatable+readonly state for NEW_TABLE IR instructions * Replace JUMP_CMP_ANY with CMP_ANY and existing JUMP_EQ_INT * Add support for exits to VM with reentry lock in VmExit
This commit is contained in:
parent
5e1aca164c
commit
25cc75b096
@ -13,6 +13,7 @@
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/TypeUtils.h"
|
||||
#include "Luau/Variant.h"
|
||||
#include "Normalize.h"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
@ -86,6 +87,8 @@ struct ConstraintGraphBuilder
|
||||
// It is pretty uncommon for constraint generation to itself produce errors, but it can happen.
|
||||
std::vector<TypeError> errors;
|
||||
|
||||
// Needed to be able to enable error-suppression preservation for immediate refinements.
|
||||
NotNull<Normalizer> normalizer;
|
||||
// Needed to resolve modules to make 'require' import types properly.
|
||||
NotNull<ModuleResolver> moduleResolver;
|
||||
// Occasionally constraint generation needs to produce an ICE.
|
||||
@ -98,7 +101,7 @@ struct ConstraintGraphBuilder
|
||||
|
||||
DcrLogger* logger;
|
||||
|
||||
ConstraintGraphBuilder(ModulePtr module, TypeArena* arena, NotNull<ModuleResolver> moduleResolver, NotNull<BuiltinTypes> builtinTypes,
|
||||
ConstraintGraphBuilder(ModulePtr module, NotNull<Normalizer> normalizer, NotNull<ModuleResolver> moduleResolver, NotNull<BuiltinTypes> builtinTypes,
|
||||
NotNull<InternalErrorReporter> ice, const ScopePtr& globalScope, std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
|
||||
DcrLogger* logger, NotNull<DataFlowGraph> dfg, std::vector<RequireCycle> requireCycles);
|
||||
|
||||
|
@ -204,7 +204,7 @@ struct ConstraintSolver
|
||||
* @param subType the sub-type to unify.
|
||||
* @param superType the super-type to unify.
|
||||
*/
|
||||
ErrorVec unify(TypeId subType, TypeId superType, NotNull<Scope> scope);
|
||||
ErrorVec unify(NotNull<Scope> scope, Location location, TypeId subType, TypeId superType);
|
||||
|
||||
/**
|
||||
* Creates a new Unifier and performs a single unification operation. Commits
|
||||
@ -212,7 +212,7 @@ struct ConstraintSolver
|
||||
* @param subPack the sub-type pack to unify.
|
||||
* @param superPack the super-type pack to unify.
|
||||
*/
|
||||
ErrorVec unify(TypePackId subPack, TypePackId superPack, NotNull<Scope> scope);
|
||||
ErrorVec unify(NotNull<Scope> scope, Location location, TypePackId subPack, TypePackId superPack);
|
||||
|
||||
/** Pushes a new solver constraint to the solver.
|
||||
* @param cv the body of the constraint.
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
#include "Luau/DenseHash.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/UnifierSharedState.h"
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
@ -151,8 +152,31 @@ struct DifferEnvironment
|
||||
{
|
||||
TypeId rootLeft;
|
||||
TypeId rootRight;
|
||||
|
||||
DenseHashMap<TypeId, TypeId> genericMatchedPairs;
|
||||
DenseHashMap<TypePackId, TypePackId> genericTpMatchedPairs;
|
||||
|
||||
DifferEnvironment(TypeId rootLeft, TypeId rootRight)
|
||||
: rootLeft(rootLeft)
|
||||
, rootRight(rootRight)
|
||||
, genericMatchedPairs(nullptr)
|
||||
, genericTpMatchedPairs(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
bool isProvenEqual(TypeId left, TypeId right) const;
|
||||
bool isAssumedEqual(TypeId left, TypeId right) const;
|
||||
void recordProvenEqual(TypeId left, TypeId right);
|
||||
void pushVisiting(TypeId left, TypeId right);
|
||||
void popVisiting();
|
||||
std::vector<std::pair<TypeId, TypeId>>::const_reverse_iterator visitingBegin() const;
|
||||
std::vector<std::pair<TypeId, TypeId>>::const_reverse_iterator visitingEnd() const;
|
||||
|
||||
private:
|
||||
// TODO: consider using DenseHashSet
|
||||
std::unordered_set<std::pair<TypeId, TypeId>, TypeIdPairHash> provenEqual;
|
||||
// Ancestors of current types
|
||||
std::unordered_set<std::pair<TypeId, TypeId>, TypeIdPairHash> visiting;
|
||||
std::vector<std::pair<TypeId, TypeId>> visitingStack;
|
||||
};
|
||||
DifferResult diff(TypeId ty1, TypeId ty2);
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/NotNull.h"
|
||||
#include "Luau/Substitution.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/Unifiable.h"
|
||||
@ -8,15 +9,18 @@
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct TypeArena;
|
||||
struct BuiltinTypes;
|
||||
struct TxnLog;
|
||||
struct TypeArena;
|
||||
struct TypeCheckLimits;
|
||||
|
||||
// A substitution which replaces generic types in a given set by free types.
|
||||
struct ReplaceGenerics : Substitution
|
||||
{
|
||||
ReplaceGenerics(const TxnLog* log, TypeArena* arena, TypeLevel level, Scope* scope, const std::vector<TypeId>& generics,
|
||||
ReplaceGenerics(const TxnLog* log, TypeArena* arena, NotNull<BuiltinTypes> builtinTypes, TypeLevel level, Scope* scope, const std::vector<TypeId>& generics,
|
||||
const std::vector<TypePackId>& genericPacks)
|
||||
: Substitution(log, arena)
|
||||
, builtinTypes(builtinTypes)
|
||||
, level(level)
|
||||
, scope(scope)
|
||||
, generics(generics)
|
||||
@ -24,6 +28,8 @@ struct ReplaceGenerics : Substitution
|
||||
{
|
||||
}
|
||||
|
||||
NotNull<BuiltinTypes> builtinTypes;
|
||||
|
||||
TypeLevel level;
|
||||
Scope* scope;
|
||||
std::vector<TypeId> generics;
|
||||
@ -38,13 +44,16 @@ struct ReplaceGenerics : Substitution
|
||||
// A substitution which replaces generic functions by monomorphic functions
|
||||
struct Instantiation : Substitution
|
||||
{
|
||||
Instantiation(const TxnLog* log, TypeArena* arena, TypeLevel level, Scope* scope)
|
||||
Instantiation(const TxnLog* log, TypeArena* arena, NotNull<BuiltinTypes> builtinTypes, TypeLevel level, Scope* scope)
|
||||
: Substitution(log, arena)
|
||||
, builtinTypes(builtinTypes)
|
||||
, level(level)
|
||||
, scope(scope)
|
||||
{
|
||||
}
|
||||
|
||||
NotNull<BuiltinTypes> builtinTypes;
|
||||
|
||||
TypeLevel level;
|
||||
Scope* scope;
|
||||
bool ignoreChildren(TypeId ty) override;
|
||||
@ -54,4 +63,20 @@ struct Instantiation : Substitution
|
||||
TypePackId clean(TypePackId tp) override;
|
||||
};
|
||||
|
||||
/** Attempt to instantiate a type. Only used under local type inference.
|
||||
*
|
||||
* When given a generic function type, instantiate() will return a copy with the
|
||||
* generics replaced by fresh types. Instantiation will return the same TypeId
|
||||
* back if the function does not have any generics.
|
||||
*
|
||||
* All higher order generics are left as-is. For example, instantiation of
|
||||
* <X>(<Y>(Y) -> (X, Y)) -> (X, Y) is (<Y>(Y) -> ('x, Y)) -> ('x, Y)
|
||||
*
|
||||
* We substitute the generic X for the free 'x, but leave the generic Y alone.
|
||||
*
|
||||
* Instantiation fails only when processing the type causes internal recursion
|
||||
* limits to be exceeded.
|
||||
*/
|
||||
std::optional<TypeId> instantiate(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, NotNull<TypeCheckLimits> limits, NotNull<Scope> scope, TypeId ty);
|
||||
|
||||
} // namespace Luau
|
||||
|
@ -77,13 +77,15 @@ using TypeId = const Type*;
|
||||
|
||||
using Name = std::string;
|
||||
|
||||
// A free type var is one whose exact shape has yet to be fully determined.
|
||||
// A free type is one whose exact shape has yet to be fully determined.
|
||||
struct FreeType
|
||||
{
|
||||
explicit FreeType(TypeLevel level);
|
||||
explicit FreeType(Scope* scope);
|
||||
FreeType(Scope* scope, TypeLevel level);
|
||||
|
||||
FreeType(Scope* scope, TypeId lowerBound, TypeId upperBound);
|
||||
|
||||
int index;
|
||||
TypeLevel level;
|
||||
Scope* scope = nullptr;
|
||||
@ -92,6 +94,10 @@ struct FreeType
|
||||
// recursive type alias whose definitions haven't been
|
||||
// resolved yet.
|
||||
bool forwardedTypeAlias = false;
|
||||
|
||||
// Only used under local type inference
|
||||
TypeId lowerBound = nullptr;
|
||||
TypeId upperBound = nullptr;
|
||||
};
|
||||
|
||||
struct GenericType
|
||||
@ -994,6 +1000,8 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
TypeId freshType(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, Scope* scope);
|
||||
|
||||
using TypeIdPredicate = std::function<std::optional<TypeId>(TypeId)>;
|
||||
std::vector<TypeId> filterMap(TypeId type, TypeIdPredicate predicate);
|
||||
|
||||
|
@ -9,10 +9,12 @@
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct DcrLogger;
|
||||
struct BuiltinTypes;
|
||||
struct DcrLogger;
|
||||
struct TypeCheckLimits;
|
||||
struct UnifierSharedState;
|
||||
|
||||
void check(NotNull<BuiltinTypes> builtinTypes, NotNull<struct UnifierSharedState> sharedState, DcrLogger* logger, const SourceModule& sourceModule,
|
||||
void check(NotNull<BuiltinTypes> builtinTypes, NotNull<UnifierSharedState> sharedState, NotNull<TypeCheckLimits> limits, DcrLogger* logger, const SourceModule& sourceModule,
|
||||
Module* module);
|
||||
|
||||
} // namespace Luau
|
||||
|
75
Analysis/include/Luau/Unifier2.h
Normal file
75
Analysis/include/Luau/Unifier2.h
Normal file
@ -0,0 +1,75 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Luau/DenseHash.h"
|
||||
#include "Luau/NotNull.h"
|
||||
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
using TypeId = const struct Type*;
|
||||
using TypePackId = const struct TypePackVar*;
|
||||
|
||||
struct BuiltinTypes;
|
||||
struct InternalErrorReporter;
|
||||
struct Scope;
|
||||
struct TypeArena;
|
||||
|
||||
enum class OccursCheckResult
|
||||
{
|
||||
Pass,
|
||||
Fail
|
||||
};
|
||||
|
||||
struct Unifier2
|
||||
{
|
||||
NotNull<TypeArena> arena;
|
||||
NotNull<BuiltinTypes> builtinTypes;
|
||||
NotNull<InternalErrorReporter> ice;
|
||||
|
||||
int recursionCount = 0;
|
||||
int recursionLimit = 0;
|
||||
|
||||
Unifier2(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, NotNull<InternalErrorReporter> ice);
|
||||
|
||||
/** Attempt to commit the subtype relation subTy <: superTy to the type
|
||||
* graph.
|
||||
*
|
||||
* @returns true if successful.
|
||||
*
|
||||
* Note that incoherent types can and will successfully be unified. We stop
|
||||
* when we *cannot know* how to relate the provided types, not when doing so
|
||||
* would narrow something down to never or broaden it to unknown.
|
||||
*
|
||||
* Presently, the only way unification can fail is if we attempt to bind one
|
||||
* free TypePack to another and encounter an occurs check violation.
|
||||
*/
|
||||
bool unify(TypeId subTy, TypeId superTy);
|
||||
|
||||
// TODO think about this one carefully. We don't do unions or intersections of type packs
|
||||
bool unify(TypePackId subTp, TypePackId superTp);
|
||||
|
||||
std::optional<TypeId> generalize(NotNull<Scope> scope, TypeId ty);
|
||||
private:
|
||||
|
||||
/**
|
||||
* @returns simplify(left | right)
|
||||
*/
|
||||
TypeId mkUnion(TypeId left, TypeId right);
|
||||
|
||||
/**
|
||||
* @returns simplify(left & right)
|
||||
*/
|
||||
TypeId mkIntersection(TypeId left, TypeId right);
|
||||
|
||||
// Returns true if needle occurs within haystack already. ie if we bound
|
||||
// needle to haystack, would a cyclic TypePack result?
|
||||
OccursCheckResult occursCheck(DenseHashSet<TypePackId>& seen, TypePackId needle, TypePackId haystack);
|
||||
};
|
||||
|
||||
}
|
@ -1,13 +1,13 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Common.h"
|
||||
#include <initializer_list>
|
||||
#include <new>
|
||||
#include <type_traits>
|
||||
#include <initializer_list>
|
||||
#include <stddef.h>
|
||||
#include <utility>
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
@ -14,7 +14,7 @@
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauReadWriteProperties)
|
||||
LUAU_FASTFLAGVARIABLE(LuauDisableCompletionOutsideQuotes, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAnonymousAutofilled, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauAnonymousAutofilled1, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauAutocompleteLastTypecheck, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAutocompleteHideSelfArg, false)
|
||||
|
||||
@ -618,7 +618,7 @@ std::optional<TypeId> getLocalTypeInScopeAt(const Module& module, Position posit
|
||||
template <typename T>
|
||||
static std::optional<std::string> tryToStringDetailed(const ScopePtr& scope, T ty, bool functionTypeArguments)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauAnonymousAutofilled);
|
||||
LUAU_ASSERT(FFlag::LuauAnonymousAutofilled1);
|
||||
ToStringOptions opts;
|
||||
opts.useLineBreaks = false;
|
||||
opts.hideTableKind = true;
|
||||
@ -637,7 +637,7 @@ static std::optional<Name> tryGetTypeNameInScope(ScopePtr scope, TypeId ty, bool
|
||||
if (!canSuggestInferredType(scope, ty))
|
||||
return std::nullopt;
|
||||
|
||||
if (FFlag::LuauAnonymousAutofilled)
|
||||
if (FFlag::LuauAnonymousAutofilled1)
|
||||
{
|
||||
return tryToStringDetailed(scope, ty, functionTypeArguments);
|
||||
}
|
||||
@ -1419,7 +1419,7 @@ static AutocompleteResult autocompleteWhileLoopKeywords(std::vector<AstNode*> an
|
||||
|
||||
static std::string makeAnonymous(const ScopePtr& scope, const FunctionType& funcTy)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauAnonymousAutofilled);
|
||||
LUAU_ASSERT(FFlag::LuauAnonymousAutofilled1);
|
||||
std::string result = "function(";
|
||||
|
||||
auto [args, tail] = Luau::flatten(funcTy.argTypes);
|
||||
@ -1485,7 +1485,7 @@ static std::string makeAnonymous(const ScopePtr& scope, const FunctionType& func
|
||||
|
||||
static std::optional<AutocompleteEntry> makeAnonymousAutofilled(const ModulePtr& module, Position position, const AstNode* node, const std::vector<AstNode*>& ancestry)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauAnonymousAutofilled);
|
||||
LUAU_ASSERT(FFlag::LuauAnonymousAutofilled1);
|
||||
const AstExprCall* call = node->as<AstExprCall>();
|
||||
if (!call && ancestry.size() > 1)
|
||||
call = ancestry[ancestry.size() - 2]->as<AstExprCall>();
|
||||
@ -1803,7 +1803,7 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
|
||||
|
||||
if (node->asExpr())
|
||||
{
|
||||
if (FFlag::LuauAnonymousAutofilled)
|
||||
if (FFlag::LuauAnonymousAutofilled1)
|
||||
{
|
||||
AutocompleteResult ret = autocompleteExpression(sourceModule, *module, builtinTypes, typeArena, ancestry, position);
|
||||
if (std::optional<AutocompleteEntry> generated = makeAnonymousAutofilled(module, position, node, ancestry))
|
||||
|
@ -9,6 +9,7 @@
|
||||
LUAU_FASTFLAG(DebugLuauCopyBeforeNormalizing)
|
||||
LUAU_FASTFLAG(DebugLuauReadWriteProperties)
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
LUAU_FASTINTVARIABLE(LuauTypeCloneRecursionLimit, 300)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCloneCyclicUnions, false)
|
||||
|
||||
@ -204,6 +205,13 @@ void TypeCloner::defaultClone(const T& t)
|
||||
|
||||
void TypeCloner::operator()(const FreeType& t)
|
||||
{
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
FreeType ft{t.scope, clone(t.lowerBound, dest, cloneState), clone(t.upperBound, dest, cloneState)};
|
||||
TypeId res = dest.addType(ft);
|
||||
seenTypes[typeId] = res;
|
||||
}
|
||||
else
|
||||
defaultClone(t);
|
||||
}
|
||||
|
||||
@ -363,7 +371,10 @@ void TypeCloner::operator()(const UnionType& t)
|
||||
{
|
||||
if (FFlag::LuauCloneCyclicUnions)
|
||||
{
|
||||
TypeId result = dest.addType(FreeType{nullptr});
|
||||
// We're just using this FreeType as a placeholder until we've finished
|
||||
// cloning the parts of this union so it is okay that its bounds are
|
||||
// nullptr. We'll never indirect them.
|
||||
TypeId result = dest.addType(FreeType{nullptr, /*lowerBound*/nullptr, /*upperBound*/nullptr});
|
||||
seenTypes[typeId] = result;
|
||||
|
||||
std::vector<TypeId> options;
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include <algorithm>
|
||||
|
||||
LUAU_FASTINT(LuauCheckRecursionLimit);
|
||||
LUAU_FASTFLAG(DebugLuauLogSolverToJson);
|
||||
LUAU_FASTFLAG(DebugLuauMagicTypes);
|
||||
LUAU_FASTFLAG(LuauParseDeclareClassIndexer);
|
||||
|
||||
@ -137,15 +138,16 @@ void forEachConstraint(const Checkpoint& start, const Checkpoint& end, const Con
|
||||
|
||||
} // namespace
|
||||
|
||||
ConstraintGraphBuilder::ConstraintGraphBuilder(ModulePtr module, TypeArena* arena, NotNull<ModuleResolver> moduleResolver,
|
||||
ConstraintGraphBuilder::ConstraintGraphBuilder(ModulePtr module, NotNull<Normalizer> normalizer, NotNull<ModuleResolver> moduleResolver,
|
||||
NotNull<BuiltinTypes> builtinTypes, NotNull<InternalErrorReporter> ice, const ScopePtr& globalScope,
|
||||
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope, DcrLogger* logger, NotNull<DataFlowGraph> dfg,
|
||||
std::vector<RequireCycle> requireCycles)
|
||||
: module(module)
|
||||
, builtinTypes(builtinTypes)
|
||||
, arena(arena)
|
||||
, arena(normalizer->arena)
|
||||
, rootScope(nullptr)
|
||||
, dfg(dfg)
|
||||
, normalizer(normalizer)
|
||||
, moduleResolver(moduleResolver)
|
||||
, ice(ice)
|
||||
, globalScope(globalScope)
|
||||
@ -158,7 +160,7 @@ ConstraintGraphBuilder::ConstraintGraphBuilder(ModulePtr module, TypeArena* aren
|
||||
|
||||
TypeId ConstraintGraphBuilder::freshType(const ScopePtr& scope)
|
||||
{
|
||||
return arena->addType(FreeType{scope.get()});
|
||||
return Luau::freshType(arena, builtinTypes, scope.get());
|
||||
}
|
||||
|
||||
TypePackId ConstraintGraphBuilder::freshTypePack(const ScopePtr& scope)
|
||||
@ -414,7 +416,22 @@ void ConstraintGraphBuilder::applyRefinements(const ScopePtr& scope, Location lo
|
||||
ty = r;
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (shouldSuppressErrors(normalizer, ty))
|
||||
{
|
||||
case ErrorSuppression::DoNotSuppress:
|
||||
ty = simplifyIntersection(builtinTypes, arena, ty, dt).result;
|
||||
break;
|
||||
case ErrorSuppression::Suppress:
|
||||
ty = simplifyIntersection(builtinTypes, arena, ty, dt).result;
|
||||
ty = simplifyUnion(builtinTypes, arena, ty, builtinTypes->errorType).result;
|
||||
break;
|
||||
case ErrorSuppression::NormalizationFailed:
|
||||
reportError(location, NormalizationTooComplex{});
|
||||
ty = simplifyIntersection(builtinTypes, arena, ty, dt).result;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scope->dcrRefinements[def] = ty;
|
||||
@ -777,7 +794,7 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatForIn* f
|
||||
}
|
||||
|
||||
// It is always ok to provide too few variables, so we give this pack a free tail.
|
||||
TypePackId variablePack = arena->addTypePack(std::move(variableTypes), arena->addTypePack(FreeTypePack{loopScope.get()}));
|
||||
TypePackId variablePack = arena->addTypePack(std::move(variableTypes), freshTypePack(loopScope));
|
||||
|
||||
addConstraint(
|
||||
loopScope, getLocation(forIn->values), IterableConstraint{iterator, variablePack, forIn->values.data[0], &module->astForInNextTypes});
|
||||
@ -982,6 +999,7 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatBlock* b
|
||||
return flow;
|
||||
}
|
||||
|
||||
// TODO Clip?
|
||||
static void bindFreeType(TypeId a, TypeId b)
|
||||
{
|
||||
FreeType* af = getMutable<FreeType>(a);
|
||||
@ -1488,7 +1506,7 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCa
|
||||
if (selfTy)
|
||||
args.push_back(*selfTy);
|
||||
else
|
||||
args.push_back(arena->freshType(scope.get()));
|
||||
args.push_back(freshType(scope));
|
||||
}
|
||||
else if (i < exprArgs.size() - 1 || !(arg->is<AstExprCall>() || arg->is<AstExprVarargs>()))
|
||||
{
|
||||
@ -2148,6 +2166,8 @@ TypeId ConstraintGraphBuilder::checkLValue(const ScopePtr& scope, AstExpr* expr)
|
||||
|
||||
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprTable* expr, std::optional<TypeId> expectedType)
|
||||
{
|
||||
const bool expectedTypeIsFree = expectedType && get<FreeType>(follow(*expectedType));
|
||||
|
||||
TypeId ty = arena->addType(TableType{});
|
||||
TableType* ttv = getMutable<TableType>(ty);
|
||||
LUAU_ASSERT(ttv);
|
||||
@ -2192,7 +2212,7 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprTable* exp
|
||||
if (item.kind == AstExprTable::Item::Kind::General || item.kind == AstExprTable::Item::Kind::List)
|
||||
isIndexedResultType = true;
|
||||
|
||||
if (item.key && expectedType)
|
||||
if (item.key && expectedType && !expectedTypeIsFree)
|
||||
{
|
||||
if (auto stringKey = item.key->as<AstExprConstantString>())
|
||||
{
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include "Luau/TypeFamily.h"
|
||||
#include "Luau/TypeUtils.h"
|
||||
#include "Luau/Unifier.h"
|
||||
#include "Luau/Unifier2.h"
|
||||
#include "Luau/VisitType.h"
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolver, false);
|
||||
@ -441,6 +442,17 @@ void ConstraintSolver::finalizeModule()
|
||||
{
|
||||
rootScope->returnType = anyifyModuleReturnTypePackGenerics(*returnType);
|
||||
}
|
||||
|
||||
Unifier2 u2{NotNull{arena}, builtinTypes, NotNull{&iceReporter}};
|
||||
|
||||
for (auto& [name, binding] : rootScope->bindings)
|
||||
{
|
||||
auto generalizedTy = u2.generalize(rootScope, binding.typeId);
|
||||
if (generalizedTy)
|
||||
binding.typeId = *generalizedTy;
|
||||
else
|
||||
reportError(CodeTooComplex{}, binding.location);
|
||||
}
|
||||
}
|
||||
|
||||
bool ConstraintSolver::tryDispatch(NotNull<const Constraint> constraint, bool force)
|
||||
@ -526,19 +538,28 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
|
||||
else if (get<PendingExpansionType>(generalizedType))
|
||||
return block(generalizedType, constraint);
|
||||
|
||||
std::optional<QuantifierResult> generalized = quantify(arena, c.sourceType, constraint->scope);
|
||||
std::optional<QuantifierResult> generalized;
|
||||
|
||||
Unifier2 u2{NotNull{arena}, builtinTypes, NotNull{&iceReporter}};
|
||||
|
||||
std::optional<TypeId> generalizedTy = u2.generalize(constraint->scope, c.sourceType);
|
||||
if (generalizedTy)
|
||||
generalized = QuantifierResult{*generalizedTy}; // FIXME insertedGenerics and insertedGenericPacks
|
||||
else
|
||||
reportError(CodeTooComplex{}, constraint->location);
|
||||
|
||||
if (generalized)
|
||||
{
|
||||
if (get<BlockedType>(generalizedType))
|
||||
asMutable(generalizedType)->ty.emplace<BoundType>(generalized->result);
|
||||
else
|
||||
unify(generalizedType, generalized->result, constraint->scope);
|
||||
unify(constraint->scope, constraint->location, generalizedType, generalized->result);
|
||||
|
||||
for (auto [free, gen] : generalized->insertedGenerics.pairings)
|
||||
unify(free, gen, constraint->scope);
|
||||
unify(constraint->scope, constraint->location, free, gen);
|
||||
|
||||
for (auto [free, gen] : generalized->insertedGenericPacks.pairings)
|
||||
unify(free, gen, constraint->scope);
|
||||
unify(constraint->scope, constraint->location, free, gen);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -560,12 +581,8 @@ bool ConstraintSolver::tryDispatch(const InstantiationConstraint& c, NotNull<con
|
||||
if (!blockOnPendingTypes(c.superType, constraint))
|
||||
return false;
|
||||
|
||||
Instantiation inst(TxnLog::empty(), arena, TypeLevel{}, constraint->scope);
|
||||
|
||||
if (limits.instantiationChildLimit)
|
||||
inst.childLimit = *limits.instantiationChildLimit;
|
||||
|
||||
std::optional<TypeId> instantiated = inst.substitute(c.superType);
|
||||
// TODO childLimit
|
||||
std::optional<TypeId> instantiated = instantiate(builtinTypes, NotNull{arena}, NotNull{&limits}, constraint->scope, c.superType);
|
||||
|
||||
LUAU_ASSERT(get<BlockedType>(c.subType));
|
||||
|
||||
@ -634,7 +651,8 @@ bool ConstraintSolver::tryDispatch(const UnaryConstraint& c, NotNull<const Const
|
||||
TypePackId argPack = arena->addTypePack(TypePack{{operandType}, {}});
|
||||
TypePackId retPack = arena->addTypePack(BlockedTypePack{});
|
||||
|
||||
asMutable(c.resultType)->ty.emplace<FreeType>(constraint->scope);
|
||||
TypeId res = freshType(arena, builtinTypes, constraint->scope);
|
||||
asMutable(c.resultType)->ty.emplace<BoundType>(res);
|
||||
|
||||
pushConstraint(constraint->scope, constraint->location, PackSubtypeConstraint{retPack, arena->addTypePack(TypePack{{c.resultType}})});
|
||||
|
||||
@ -722,12 +740,7 @@ bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNull<const Cons
|
||||
|
||||
if (mm)
|
||||
{
|
||||
Instantiation instantiation{TxnLog::empty(), arena, TypeLevel{}, constraint->scope};
|
||||
|
||||
if (limits.instantiationChildLimit)
|
||||
instantiation.childLimit = *limits.instantiationChildLimit;
|
||||
|
||||
std::optional<TypeId> instantiatedMm = instantiation.substitute(*mm);
|
||||
std::optional<TypeId> instantiatedMm = instantiate(builtinTypes, arena, NotNull{&limits}, constraint->scope, *mm);
|
||||
if (!instantiatedMm)
|
||||
{
|
||||
reportError(CodeTooComplex{}, constraint->location);
|
||||
@ -750,7 +763,7 @@ bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNull<const Cons
|
||||
inferredArgs = arena->addTypePack({leftType, rightType});
|
||||
}
|
||||
|
||||
unify(inferredArgs, ftv->argTypes, constraint->scope);
|
||||
unify(constraint->scope, constraint->location, inferredArgs, ftv->argTypes);
|
||||
|
||||
TypeId mmResult;
|
||||
|
||||
@ -802,14 +815,14 @@ bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNull<const Cons
|
||||
// We want to check if the left type has tops because `any` is a valid type for the lhs
|
||||
if (normLeftTy && (normLeftTy->isExactlyNumber() || get<AnyType>(normLeftTy->tops)))
|
||||
{
|
||||
unify(leftType, rightType, constraint->scope);
|
||||
unify(constraint->scope, constraint->location, leftType, rightType);
|
||||
asMutable(resultType)->ty.emplace<BoundType>(anyPresent ? builtinTypes->anyType : leftType);
|
||||
unblock(resultType, constraint->location);
|
||||
return true;
|
||||
}
|
||||
else if (get<NeverType>(leftType) || get<NeverType>(rightType))
|
||||
{
|
||||
unify(leftType, rightType, constraint->scope);
|
||||
unify(constraint->scope, constraint->location, leftType, rightType);
|
||||
asMutable(resultType)->ty.emplace<BoundType>(builtinTypes->neverType);
|
||||
unblock(resultType, constraint->location);
|
||||
return true;
|
||||
@ -826,14 +839,14 @@ bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNull<const Cons
|
||||
const NormalizedType* leftNormTy = normalizer->normalize(leftType);
|
||||
if (leftNormTy && leftNormTy->isSubtypeOfString())
|
||||
{
|
||||
unify(leftType, rightType, constraint->scope);
|
||||
unify(constraint->scope, constraint->location, leftType, rightType);
|
||||
asMutable(resultType)->ty.emplace<BoundType>(anyPresent ? builtinTypes->anyType : leftType);
|
||||
unblock(resultType, constraint->location);
|
||||
return true;
|
||||
}
|
||||
else if (get<NeverType>(leftType) || get<NeverType>(rightType))
|
||||
{
|
||||
unify(leftType, rightType, constraint->scope);
|
||||
unify(constraint->scope, constraint->location, leftType, rightType);
|
||||
asMutable(resultType)->ty.emplace<BoundType>(builtinTypes->neverType);
|
||||
unblock(resultType, constraint->location);
|
||||
return true;
|
||||
@ -909,8 +922,8 @@ bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNull<const Cons
|
||||
}
|
||||
|
||||
// We failed to either evaluate a metamethod or invoke primitive behavior.
|
||||
unify(leftType, errorRecoveryType(), constraint->scope);
|
||||
unify(rightType, errorRecoveryType(), constraint->scope);
|
||||
unify(constraint->scope, constraint->location, leftType, errorRecoveryType());
|
||||
unify(constraint->scope, constraint->location, rightType, errorRecoveryType());
|
||||
asMutable(resultType)->ty.emplace<BoundType>(errorRecoveryType());
|
||||
unblock(resultType, constraint->location);
|
||||
|
||||
@ -979,7 +992,7 @@ bool ConstraintSolver::tryDispatch(const IterableConstraint& c, NotNull<const Co
|
||||
Anyification anyify{arena, constraint->scope, builtinTypes, &iceReporter, errorRecoveryType(), errorRecoveryTypePack()};
|
||||
std::optional<TypePackId> anyified = anyify.substitute(c.variables);
|
||||
LUAU_ASSERT(anyified);
|
||||
unify(*anyified, c.variables, constraint->scope);
|
||||
unify(constraint->scope, constraint->location, *anyified, c.variables);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -1330,11 +1343,6 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
|
||||
TypeId normFnTy = normalizer->typeFromNormal(*normFn);
|
||||
std::vector<TypeId> overloads = flattenIntersection(normFnTy);
|
||||
|
||||
Instantiation inst(TxnLog::empty(), arena, TypeLevel{}, constraint->scope);
|
||||
|
||||
if (limits.instantiationChildLimit)
|
||||
inst.childLimit = *limits.instantiationChildLimit;
|
||||
|
||||
std::vector<TypeId> arityMatchingOverloads;
|
||||
std::optional<TxnLog> bestOverloadLog;
|
||||
|
||||
@ -1342,7 +1350,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
|
||||
{
|
||||
overload = follow(overload);
|
||||
|
||||
std::optional<TypeId> instantiated = inst.substitute(overload);
|
||||
std::optional<TypeId> instantiated = instantiate(builtinTypes, arena, NotNull{&limits}, constraint->scope, overload);
|
||||
|
||||
if (!instantiated.has_value())
|
||||
{
|
||||
@ -1451,7 +1459,8 @@ bool ConstraintSolver::tryDispatch(const HasPropConstraint& c, NotNull<const Con
|
||||
{
|
||||
TableType& ttv = asMutable(subjectType)->ty.emplace<TableType>(TableState::Free, TypeLevel{}, constraint->scope);
|
||||
ttv.props[c.prop] = Property{c.resultType};
|
||||
asMutable(c.resultType)->ty.emplace<FreeType>(constraint->scope);
|
||||
TypeId res = freshType(arena, builtinTypes, constraint->scope);
|
||||
asMutable(c.resultType)->ty.emplace<BoundType>(res);
|
||||
unblock(c.resultType, constraint->location);
|
||||
return true;
|
||||
}
|
||||
@ -1579,7 +1588,7 @@ bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull<const Con
|
||||
if (existingPropType)
|
||||
{
|
||||
if (!isBlocked(c.propType))
|
||||
unify(c.propType, *existingPropType, constraint->scope);
|
||||
unify(constraint->scope, constraint->location, c.propType, *existingPropType);
|
||||
bind(c.resultType, c.subjectType);
|
||||
unblock(c.resultType, constraint->location);
|
||||
return true;
|
||||
@ -1590,7 +1599,7 @@ bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull<const Con
|
||||
|
||||
if (get<FreeType>(subjectType))
|
||||
{
|
||||
TypeId ty = arena->freshType(constraint->scope);
|
||||
TypeId ty = freshType(arena, builtinTypes, constraint->scope);
|
||||
|
||||
// Mint a chain of free tables per c.path
|
||||
for (auto it = rbegin(c.path); it != rend(c.path); ++it)
|
||||
@ -1661,7 +1670,8 @@ bool ConstraintSolver::tryDispatch(const SetIndexerConstraint& c, NotNull<const
|
||||
tt->indexer = TableIndexer{c.indexType, c.propType};
|
||||
|
||||
asMutable(c.resultType)->ty.emplace<BoundType>(subjectType);
|
||||
asMutable(c.propType)->ty.emplace<FreeType>(scope);
|
||||
TypeId propType = freshType(arena, builtinTypes, scope);
|
||||
asMutable(c.propType)->ty.emplace<BoundType>(propType);
|
||||
unblock(c.propType, constraint->location);
|
||||
unblock(c.resultType, constraint->location);
|
||||
|
||||
@ -1672,7 +1682,7 @@ bool ConstraintSolver::tryDispatch(const SetIndexerConstraint& c, NotNull<const
|
||||
if (tt->indexer)
|
||||
{
|
||||
// TODO This probably has to be invariant.
|
||||
unify(c.indexType, tt->indexer->indexType, constraint->scope);
|
||||
unify(constraint->scope, constraint->location, c.indexType, tt->indexer->indexType);
|
||||
asMutable(c.propType)->ty.emplace<BoundType>(tt->indexer->indexResultType);
|
||||
asMutable(c.resultType)->ty.emplace<BoundType>(subjectType);
|
||||
unblock(c.propType, constraint->location);
|
||||
@ -1681,12 +1691,13 @@ bool ConstraintSolver::tryDispatch(const SetIndexerConstraint& c, NotNull<const
|
||||
}
|
||||
else if (tt->state == TableState::Free || tt->state == TableState::Unsealed)
|
||||
{
|
||||
TypeId promotedIndexTy = arena->freshType(tt->scope);
|
||||
unify(c.indexType, promotedIndexTy, constraint->scope);
|
||||
TypeId promotedIndexTy = freshType(arena, builtinTypes, tt->scope);
|
||||
unify(constraint->scope, constraint->location, c.indexType, promotedIndexTy);
|
||||
|
||||
auto mtt = getMutable<TableType>(subjectType);
|
||||
mtt->indexer = TableIndexer{promotedIndexTy, c.propType};
|
||||
asMutable(c.propType)->ty.emplace<FreeType>(tt->scope);
|
||||
TypeId propType = freshType(arena, builtinTypes, tt->scope);
|
||||
asMutable(c.propType)->ty.emplace<BoundType>(propType);
|
||||
asMutable(c.resultType)->ty.emplace<BoundType>(subjectType);
|
||||
unblock(c.propType, constraint->location);
|
||||
unblock(c.resultType, constraint->location);
|
||||
@ -1754,14 +1765,15 @@ bool ConstraintSolver::tryDispatch(const UnpackConstraint& c, NotNull<const Cons
|
||||
if (follow(srcTy) == *destIter)
|
||||
{
|
||||
// Cyclic type dependency. (????)
|
||||
asMutable(*destIter)->ty.emplace<FreeType>(constraint->scope);
|
||||
TypeId f = freshType(arena, builtinTypes, constraint->scope);
|
||||
asMutable(*destIter)->ty.emplace<BoundType>(f);
|
||||
}
|
||||
else
|
||||
asMutable(*destIter)->ty.emplace<BoundType>(srcTy);
|
||||
unblock(*destIter, constraint->location);
|
||||
}
|
||||
else
|
||||
unify(*destIter, srcTy, constraint->scope);
|
||||
unify(constraint->scope, constraint->location, *destIter, srcTy);
|
||||
|
||||
++destIter;
|
||||
++i;
|
||||
@ -1889,7 +1901,10 @@ bool ConstraintSolver::tryDispatch(const RefineConstraint& c, NotNull<const Cons
|
||||
* was offered.
|
||||
*/
|
||||
if (get<AnyType>(follow(c.discriminant)))
|
||||
asMutable(c.resultType)->ty.emplace<FreeType>(constraint->scope);
|
||||
{
|
||||
TypeId f = freshType(arena, builtinTypes, constraint->scope);
|
||||
asMutable(c.resultType)->ty.emplace<BoundType>(f);
|
||||
}
|
||||
else
|
||||
asMutable(c.resultType)->ty.emplace<BoundType>(c.discriminant);
|
||||
|
||||
@ -1999,7 +2014,7 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl
|
||||
if (!anyified)
|
||||
reportError(CodeTooComplex{}, constraint->location);
|
||||
else
|
||||
unify(*anyified, ty, constraint->scope);
|
||||
unify(constraint->scope, constraint->location, *anyified, ty);
|
||||
};
|
||||
|
||||
auto unknownify = [&](auto ty) {
|
||||
@ -2008,7 +2023,7 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl
|
||||
if (!anyified)
|
||||
reportError(CodeTooComplex{}, constraint->location);
|
||||
else
|
||||
unify(*anyified, ty, constraint->scope);
|
||||
unify(constraint->scope, constraint->location, *anyified, ty);
|
||||
};
|
||||
|
||||
auto errorify = [&](auto ty) {
|
||||
@ -2017,7 +2032,7 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl
|
||||
if (!errorified)
|
||||
reportError(CodeTooComplex{}, constraint->location);
|
||||
else
|
||||
unify(*errorified, ty, constraint->scope);
|
||||
unify(constraint->scope, constraint->location, *errorified, ty);
|
||||
};
|
||||
|
||||
auto neverify = [&](auto ty) {
|
||||
@ -2026,7 +2041,7 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl
|
||||
if (!neverified)
|
||||
reportError(CodeTooComplex{}, constraint->location);
|
||||
else
|
||||
unify(*neverified, ty, constraint->scope);
|
||||
unify(constraint->scope, constraint->location, *neverified, ty);
|
||||
};
|
||||
|
||||
if (get<AnyType>(iteratorTy))
|
||||
@ -2065,7 +2080,7 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl
|
||||
if (iteratorTable->indexer)
|
||||
{
|
||||
TypePackId expectedVariablePack = arena->addTypePack({iteratorTable->indexer->indexType, iteratorTable->indexer->indexResultType});
|
||||
unify(c.variables, expectedVariablePack, constraint->scope);
|
||||
unify(constraint->scope, constraint->location, c.variables, expectedVariablePack);
|
||||
}
|
||||
else
|
||||
errorify(c.variables);
|
||||
@ -2077,17 +2092,12 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl
|
||||
return block(*iterFn, constraint);
|
||||
}
|
||||
|
||||
Instantiation instantiation(TxnLog::empty(), arena, TypeLevel{}, constraint->scope);
|
||||
|
||||
if (limits.instantiationChildLimit)
|
||||
instantiation.childLimit = *limits.instantiationChildLimit;
|
||||
|
||||
if (std::optional<TypeId> instantiatedIterFn = instantiation.substitute(*iterFn))
|
||||
if (std::optional<TypeId> instantiatedIterFn = instantiate(builtinTypes, arena, NotNull{&limits}, constraint->scope, *iterFn))
|
||||
{
|
||||
if (auto iterFtv = get<FunctionType>(*instantiatedIterFn))
|
||||
{
|
||||
TypePackId expectedIterArgs = arena->addTypePack({iteratorTy});
|
||||
unify(iterFtv->argTypes, expectedIterArgs, constraint->scope);
|
||||
unify(constraint->scope, constraint->location, iterFtv->argTypes, expectedIterArgs);
|
||||
|
||||
TypePack iterRets = extendTypePack(*arena, builtinTypes, iterFtv->retTypes, 2);
|
||||
|
||||
@ -2099,11 +2109,11 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl
|
||||
}
|
||||
|
||||
TypeId nextFn = iterRets.head[0];
|
||||
TypeId table = iterRets.head.size() == 2 ? iterRets.head[1] : arena->freshType(constraint->scope);
|
||||
TypeId table = iterRets.head.size() == 2 ? iterRets.head[1] : freshType(arena, builtinTypes, constraint->scope);
|
||||
|
||||
if (std::optional<TypeId> instantiatedNextFn = instantiation.substitute(nextFn))
|
||||
if (std::optional<TypeId> instantiatedNextFn = instantiate(builtinTypes, arena, NotNull{&limits}, constraint->scope, nextFn))
|
||||
{
|
||||
const TypeId firstIndex = arena->freshType(constraint->scope);
|
||||
const TypeId firstIndex = freshType(arena, builtinTypes, constraint->scope);
|
||||
|
||||
// nextTy : (iteratorTy, indexTy?) -> (indexTy, valueTailTy...)
|
||||
const TypePackId nextArgPack = arena->addTypePack({table, arena->addType(UnionType{{firstIndex, builtinTypes->nilType}})});
|
||||
@ -2111,7 +2121,7 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl
|
||||
const TypePackId nextRetPack = arena->addTypePack(TypePack{{firstIndex}, valueTailTy});
|
||||
|
||||
const TypeId expectedNextTy = arena->addType(FunctionType{nextArgPack, nextRetPack});
|
||||
unify(*instantiatedNextFn, expectedNextTy, constraint->scope);
|
||||
unify(constraint->scope, constraint->location, *instantiatedNextFn, expectedNextTy);
|
||||
|
||||
pushConstraint(constraint->scope, constraint->location, PackSubtypeConstraint{c.variables, nextRetPack});
|
||||
}
|
||||
@ -2165,7 +2175,7 @@ bool ConstraintSolver::tryDispatchIterableFunction(
|
||||
TypeId retIndex;
|
||||
if (isNil(firstIndexTy) || isOptional(firstIndexTy))
|
||||
{
|
||||
firstIndex = arena->addType(UnionType{{arena->freshType(constraint->scope), builtinTypes->nilType}});
|
||||
firstIndex = arena->addType(UnionType{{freshType(arena, builtinTypes, constraint->scope), builtinTypes->nilType}});
|
||||
retIndex = firstIndex;
|
||||
}
|
||||
else
|
||||
@ -2180,7 +2190,7 @@ bool ConstraintSolver::tryDispatchIterableFunction(
|
||||
const TypePackId nextRetPack = arena->addTypePack(TypePack{{retIndex}, valueTailTy});
|
||||
|
||||
const TypeId expectedNextTy = arena->addType(FunctionType{TypeLevel{}, constraint->scope, nextArgPack, nextRetPack});
|
||||
ErrorVec errors = unify(nextTy, expectedNextTy, constraint->scope);
|
||||
ErrorVec errors = unify(constraint->scope, constraint->location, nextTy, expectedNextTy);
|
||||
|
||||
// if there are no errors from unifying the two, we can pass forward the expected type as our selected resolution.
|
||||
if (errors.empty())
|
||||
@ -2241,7 +2251,7 @@ std::pair<std::vector<TypeId>, std::optional<TypeId>> ConstraintSolver::lookupTa
|
||||
return {{}, ttv->indexer->indexResultType};
|
||||
else if (ttv->state == TableState::Free)
|
||||
{
|
||||
TypeId result = arena->freshType(ttv->scope);
|
||||
TypeId result = freshType(arena, builtinTypes, ttv->scope);
|
||||
ttv->props[propName] = Property{result};
|
||||
return {{}, result};
|
||||
}
|
||||
@ -2309,7 +2319,7 @@ std::pair<std::vector<TypeId>, std::optional<TypeId>> ConstraintSolver::lookupTa
|
||||
TableType* tt = &asMutable(subjectType)->ty.emplace<TableType>();
|
||||
tt->state = TableState::Free;
|
||||
tt->scope = scope;
|
||||
TypeId propType = arena->freshType(scope);
|
||||
TypeId propType = freshType(arena, builtinTypes, scope);
|
||||
tt->props[propName] = Property{propType};
|
||||
|
||||
return {{}, propType};
|
||||
@ -2376,49 +2386,22 @@ std::pair<std::vector<TypeId>, std::optional<TypeId>> ConstraintSolver::lookupTa
|
||||
return {{}, std::nullopt};
|
||||
}
|
||||
|
||||
static TypeId getErrorType(NotNull<BuiltinTypes> builtinTypes, TypeId)
|
||||
{
|
||||
return builtinTypes->errorRecoveryType();
|
||||
}
|
||||
|
||||
static TypePackId getErrorType(NotNull<BuiltinTypes> builtinTypes, TypePackId)
|
||||
{
|
||||
return builtinTypes->errorRecoveryTypePack();
|
||||
}
|
||||
|
||||
template<typename TID>
|
||||
bool ConstraintSolver::tryUnify(NotNull<const Constraint> constraint, TID subTy, TID superTy)
|
||||
{
|
||||
Unifier u{normalizer, constraint->scope, constraint->location, Covariant};
|
||||
u.enableNewSolver();
|
||||
Unifier2 u2{NotNull{arena}, builtinTypes, NotNull{&iceReporter}};
|
||||
|
||||
u.tryUnify(subTy, superTy);
|
||||
bool success = u2.unify(subTy, superTy);
|
||||
|
||||
if (!u.blockedTypes.empty() || !u.blockedTypePacks.empty())
|
||||
if (!success)
|
||||
{
|
||||
for (TypeId bt : u.blockedTypes)
|
||||
block(bt, constraint);
|
||||
for (TypePackId btp : u.blockedTypePacks)
|
||||
block(btp, constraint);
|
||||
return false;
|
||||
// Unification only fails when doing so would fail the occurs check.
|
||||
// ie create a self-bound type or a cyclic type pack
|
||||
reportError(OccursCheckFailed{}, constraint->location);
|
||||
}
|
||||
|
||||
if (const auto& e = hasUnificationTooComplex(u.errors))
|
||||
reportError(*e);
|
||||
|
||||
if (!u.errors.empty())
|
||||
{
|
||||
TID errorType = getErrorType(builtinTypes, TID{});
|
||||
u.tryUnify(subTy, errorType);
|
||||
u.tryUnify(superTy, errorType);
|
||||
}
|
||||
|
||||
const auto [changedTypes, changedPacks] = u.log.getChanges();
|
||||
|
||||
u.log.commit();
|
||||
|
||||
unblock(changedTypes, constraint->location);
|
||||
unblock(changedPacks, constraint->location);
|
||||
unblock(subTy, constraint->location);
|
||||
unblock(superTy, constraint->location);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -2636,31 +2619,22 @@ bool ConstraintSolver::isBlocked(NotNull<const Constraint> constraint)
|
||||
return blockedIt != blockedConstraints.end() && blockedIt->second > 0;
|
||||
}
|
||||
|
||||
ErrorVec ConstraintSolver::unify(TypeId subType, TypeId superType, NotNull<Scope> scope)
|
||||
ErrorVec ConstraintSolver::unify(NotNull<Scope> scope, Location location, TypeId subType, TypeId superType)
|
||||
{
|
||||
Unifier u{normalizer, scope, Location{}, Covariant};
|
||||
u.enableNewSolver();
|
||||
Unifier2 u2{NotNull{arena}, builtinTypes, NotNull{&iceReporter}};
|
||||
|
||||
u.tryUnify(subType, superType);
|
||||
const bool ok = u2.unify(subType, superType);
|
||||
|
||||
if (!u.errors.empty())
|
||||
{
|
||||
TypeId errorType = errorRecoveryType();
|
||||
u.tryUnify(subType, errorType);
|
||||
u.tryUnify(superType, errorType);
|
||||
if (!ok)
|
||||
reportError(UnificationTooComplex{}, location);
|
||||
|
||||
unblock(subType, Location{});
|
||||
unblock(superType, Location{});
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto [changedTypes, changedPacks] = u.log.getChanges();
|
||||
|
||||
u.log.commit();
|
||||
|
||||
unblock(changedTypes, Location{});
|
||||
unblock(changedPacks, Location{});
|
||||
|
||||
return std::move(u.errors);
|
||||
}
|
||||
|
||||
ErrorVec ConstraintSolver::unify(TypePackId subPack, TypePackId superPack, NotNull<Scope> scope)
|
||||
ErrorVec ConstraintSolver::unify(NotNull<Scope> scope, Location location, TypePackId subPack, TypePackId superPack)
|
||||
{
|
||||
UnifierSharedState sharedState{&iceReporter};
|
||||
Unifier u{normalizer, scope, Location{}, Covariant};
|
||||
|
@ -199,6 +199,13 @@ std::string getDevFixFriendlyName(TypeId ty)
|
||||
else if (table->syntheticName.has_value())
|
||||
return *table->syntheticName;
|
||||
}
|
||||
if (auto metatable = get<MetatableType>(ty))
|
||||
{
|
||||
if (metatable->syntheticName.has_value())
|
||||
{
|
||||
return *metatable->syntheticName;
|
||||
}
|
||||
}
|
||||
// else if (auto primitive = get<PrimitiveType>(ty))
|
||||
//{
|
||||
// return "<unlabeled-symbol>";
|
||||
@ -246,11 +253,13 @@ void DifferResult::wrapDiffPath(DiffPathNode node)
|
||||
|
||||
static DifferResult diffUsingEnv(DifferEnvironment& env, TypeId left, TypeId right);
|
||||
static DifferResult diffTable(DifferEnvironment& env, TypeId left, TypeId right);
|
||||
static DifferResult diffMetatable(DifferEnvironment& env, TypeId left, TypeId right);
|
||||
static DifferResult diffPrimitive(DifferEnvironment& env, TypeId left, TypeId right);
|
||||
static DifferResult diffSingleton(DifferEnvironment& env, TypeId left, TypeId right);
|
||||
static DifferResult diffFunction(DifferEnvironment& env, TypeId left, TypeId right);
|
||||
static DifferResult diffGeneric(DifferEnvironment& env, TypeId left, TypeId right);
|
||||
static DifferResult diffNegation(DifferEnvironment& env, TypeId left, TypeId right);
|
||||
static DifferResult diffClass(DifferEnvironment& env, TypeId left, TypeId right);
|
||||
struct FindSeteqCounterexampleResult
|
||||
{
|
||||
// nullopt if no counterexample found
|
||||
@ -269,6 +278,7 @@ static DifferResult diffTpi(DifferEnvironment& env, DiffError::Kind possibleNonN
|
||||
static DifferResult diffCanonicalTpShape(DifferEnvironment& env, DiffError::Kind possibleNonNormalErrorKind,
|
||||
const std::pair<std::vector<TypeId>, std::optional<TypePackId>>& left, const std::pair<std::vector<TypeId>, std::optional<TypePackId>>& right);
|
||||
static DifferResult diffHandleFlattenedTail(DifferEnvironment& env, DiffError::Kind possibleNonNormalErrorKind, TypePackId left, TypePackId right);
|
||||
static DifferResult diffGenericTp(DifferEnvironment& env, TypePackId left, TypePackId right);
|
||||
|
||||
static DifferResult diffTable(DifferEnvironment& env, TypeId left, TypeId right)
|
||||
{
|
||||
@ -315,6 +325,28 @@ static DifferResult diffTable(DifferEnvironment& env, TypeId left, TypeId right)
|
||||
return DifferResult{};
|
||||
}
|
||||
|
||||
static DifferResult diffMetatable(DifferEnvironment& env, TypeId left, TypeId right)
|
||||
{
|
||||
const MetatableType* leftMetatable = get<MetatableType>(left);
|
||||
const MetatableType* rightMetatable = get<MetatableType>(right);
|
||||
LUAU_ASSERT(leftMetatable);
|
||||
LUAU_ASSERT(rightMetatable);
|
||||
|
||||
DifferResult diffRes = diffUsingEnv(env, leftMetatable->table, rightMetatable->table);
|
||||
if (diffRes.diffError.has_value())
|
||||
{
|
||||
return diffRes;
|
||||
}
|
||||
|
||||
diffRes = diffUsingEnv(env, leftMetatable->metatable, rightMetatable->metatable);
|
||||
if (diffRes.diffError.has_value())
|
||||
{
|
||||
diffRes.wrapDiffPath(DiffPathNode::constructWithTableProperty("__metatable"));
|
||||
return diffRes;
|
||||
}
|
||||
return DifferResult{};
|
||||
}
|
||||
|
||||
static DifferResult diffPrimitive(DifferEnvironment& env, TypeId left, TypeId right)
|
||||
{
|
||||
const PrimitiveType* leftPrimitive = get<PrimitiveType>(left);
|
||||
@ -420,6 +452,27 @@ static DifferResult diffNegation(DifferEnvironment& env, TypeId left, TypeId rig
|
||||
return differResult;
|
||||
}
|
||||
|
||||
static DifferResult diffClass(DifferEnvironment& env, TypeId left, TypeId right)
|
||||
{
|
||||
const ClassType* leftClass = get<ClassType>(left);
|
||||
const ClassType* rightClass = get<ClassType>(right);
|
||||
LUAU_ASSERT(leftClass);
|
||||
LUAU_ASSERT(rightClass);
|
||||
|
||||
if (leftClass == rightClass)
|
||||
{
|
||||
return DifferResult{};
|
||||
}
|
||||
|
||||
return DifferResult{DiffError{
|
||||
DiffError::Kind::Normal,
|
||||
DiffPathNodeLeaf::detailsNormal(left),
|
||||
DiffPathNodeLeaf::detailsNormal(right),
|
||||
getDevFixFriendlyName(env.rootLeft),
|
||||
getDevFixFriendlyName(env.rootRight),
|
||||
}};
|
||||
}
|
||||
|
||||
static FindSeteqCounterexampleResult findSeteqCounterexample(
|
||||
DifferEnvironment& env, const std::vector<TypeId>& left, const std::vector<TypeId>& right)
|
||||
{
|
||||
@ -438,8 +491,8 @@ static FindSeteqCounterexampleResult findSeteqCounterexample(
|
||||
unmatchedRightIdxIt++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// unmatchedRightIdxIt is matched with current leftIdx
|
||||
env.recordProvenEqual(left[leftIdx], right[*unmatchedRightIdxIt]);
|
||||
leftIdxIsMatched = true;
|
||||
unmatchedRightIdxIt = unmatchedRightIdxes.erase(unmatchedRightIdxIt);
|
||||
}
|
||||
@ -537,6 +590,10 @@ static DifferResult diffUsingEnv(DifferEnvironment& env, TypeId left, TypeId rig
|
||||
|
||||
// Both left and right are the same variant
|
||||
|
||||
// Check cycles & caches
|
||||
if (env.isAssumedEqual(left, right) || env.isProvenEqual(left, right))
|
||||
return DifferResult{};
|
||||
|
||||
if (isSimple(left))
|
||||
{
|
||||
if (auto lp = get<PrimitiveType>(left))
|
||||
@ -550,39 +607,89 @@ static DifferResult diffUsingEnv(DifferEnvironment& env, TypeId left, TypeId rig
|
||||
// Both left and right must be Any if either is Any for them to be equal!
|
||||
return DifferResult{};
|
||||
}
|
||||
else if (auto lu = get<UnknownType>(left))
|
||||
{
|
||||
return DifferResult{};
|
||||
}
|
||||
else if (auto ln = get<NeverType>(left))
|
||||
{
|
||||
return DifferResult{};
|
||||
}
|
||||
else if (auto ln = get<NegationType>(left))
|
||||
{
|
||||
return diffNegation(env, left, right);
|
||||
}
|
||||
else if (auto lc = get<ClassType>(left))
|
||||
{
|
||||
return diffClass(env, left, right);
|
||||
}
|
||||
|
||||
throw InternalCompilerError{"Unimplemented Simple TypeId variant for diffing"};
|
||||
}
|
||||
|
||||
// Both left and right are the same non-Simple
|
||||
// Non-simple types must record visits in the DifferEnvironment
|
||||
env.pushVisiting(left, right);
|
||||
|
||||
if (auto lt = get<TableType>(left))
|
||||
{
|
||||
return diffTable(env, left, right);
|
||||
DifferResult diffRes = diffTable(env, left, right);
|
||||
if (!diffRes.diffError.has_value())
|
||||
{
|
||||
env.recordProvenEqual(left, right);
|
||||
}
|
||||
env.popVisiting();
|
||||
return diffRes;
|
||||
}
|
||||
if (auto lm = get<MetatableType>(left))
|
||||
{
|
||||
env.popVisiting();
|
||||
return diffMetatable(env, left, right);
|
||||
}
|
||||
if (auto lf = get<FunctionType>(left))
|
||||
{
|
||||
return diffFunction(env, left, right);
|
||||
DifferResult diffRes = diffFunction(env, left, right);
|
||||
if (!diffRes.diffError.has_value())
|
||||
{
|
||||
env.recordProvenEqual(left, right);
|
||||
}
|
||||
env.popVisiting();
|
||||
return diffRes;
|
||||
}
|
||||
if (auto lg = get<GenericType>(left))
|
||||
{
|
||||
return diffGeneric(env, left, right);
|
||||
DifferResult diffRes = diffGeneric(env, left, right);
|
||||
if (!diffRes.diffError.has_value())
|
||||
{
|
||||
env.recordProvenEqual(left, right);
|
||||
}
|
||||
env.popVisiting();
|
||||
return diffRes;
|
||||
}
|
||||
if (auto lu = get<UnionType>(left))
|
||||
{
|
||||
return diffUnion(env, left, right);
|
||||
DifferResult diffRes = diffUnion(env, left, right);
|
||||
if (!diffRes.diffError.has_value())
|
||||
{
|
||||
env.recordProvenEqual(left, right);
|
||||
}
|
||||
env.popVisiting();
|
||||
return diffRes;
|
||||
}
|
||||
if (auto li = get<IntersectionType>(left))
|
||||
{
|
||||
return diffIntersection(env, left, right);
|
||||
DifferResult diffRes = diffIntersection(env, left, right);
|
||||
if (!diffRes.diffError.has_value())
|
||||
{
|
||||
env.recordProvenEqual(left, right);
|
||||
}
|
||||
env.popVisiting();
|
||||
return diffRes;
|
||||
}
|
||||
if (auto le = get<Luau::Unifiable::Error>(left))
|
||||
{
|
||||
// TODO: return debug-friendly result state
|
||||
env.popVisiting();
|
||||
return DifferResult{};
|
||||
}
|
||||
|
||||
@ -658,7 +765,13 @@ static DifferResult diffHandleFlattenedTail(DifferEnvironment& env, DiffError::K
|
||||
|
||||
if (left->ty.index() != right->ty.index())
|
||||
{
|
||||
throw InternalCompilerError{"Unhandled case where the tail of 2 normalized typepacks have different variants"};
|
||||
return DifferResult{DiffError{
|
||||
DiffError::Kind::Normal,
|
||||
DiffPathNodeLeaf::detailsNormal(env.visitingBegin()->first),
|
||||
DiffPathNodeLeaf::detailsNormal(env.visitingBegin()->second),
|
||||
getDevFixFriendlyName(env.rootLeft),
|
||||
getDevFixFriendlyName(env.rootRight),
|
||||
}};
|
||||
}
|
||||
|
||||
// Both left and right are the same variant
|
||||
@ -688,13 +801,116 @@ static DifferResult diffHandleFlattenedTail(DifferEnvironment& env, DiffError::K
|
||||
}
|
||||
}
|
||||
}
|
||||
if (auto lg = get<GenericTypePack>(left))
|
||||
{
|
||||
DifferResult diffRes = diffGenericTp(env, left, right);
|
||||
if (!diffRes.diffError.has_value())
|
||||
return DifferResult{};
|
||||
switch (possibleNonNormalErrorKind)
|
||||
{
|
||||
case DiffError::Kind::LengthMismatchInFnArgs:
|
||||
{
|
||||
diffRes.wrapDiffPath(DiffPathNode::constructWithKind(DiffPathNode::Kind::FunctionArgument));
|
||||
return diffRes;
|
||||
}
|
||||
case DiffError::Kind::LengthMismatchInFnRets:
|
||||
{
|
||||
diffRes.wrapDiffPath(DiffPathNode::constructWithKind(DiffPathNode::Kind::FunctionReturn));
|
||||
return diffRes;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw InternalCompilerError{"Unhandled flattened tail case for GenericTypePack"};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw InternalCompilerError{"Unhandled tail type pack variant for flattened tails"};
|
||||
}
|
||||
|
||||
static DifferResult diffGenericTp(DifferEnvironment& env, TypePackId left, TypePackId right)
|
||||
{
|
||||
LUAU_ASSERT(get<GenericTypePack>(left));
|
||||
LUAU_ASSERT(get<GenericTypePack>(right));
|
||||
// Try to pair up the generics
|
||||
bool isLeftFree = !env.genericTpMatchedPairs.contains(left);
|
||||
bool isRightFree = !env.genericTpMatchedPairs.contains(right);
|
||||
if (isLeftFree && isRightFree)
|
||||
{
|
||||
env.genericTpMatchedPairs[left] = right;
|
||||
env.genericTpMatchedPairs[right] = left;
|
||||
return DifferResult{};
|
||||
}
|
||||
else if (isLeftFree || isRightFree)
|
||||
{
|
||||
return DifferResult{DiffError{
|
||||
DiffError::Kind::IncompatibleGeneric,
|
||||
DiffPathNodeLeaf::nullopts(),
|
||||
DiffPathNodeLeaf::nullopts(),
|
||||
getDevFixFriendlyName(env.rootLeft),
|
||||
getDevFixFriendlyName(env.rootRight),
|
||||
}};
|
||||
}
|
||||
|
||||
// Both generics are already paired up
|
||||
if (*env.genericTpMatchedPairs.find(left) == right)
|
||||
return DifferResult{};
|
||||
|
||||
return DifferResult{DiffError{
|
||||
DiffError::Kind::IncompatibleGeneric,
|
||||
DiffPathNodeLeaf::nullopts(),
|
||||
DiffPathNodeLeaf::nullopts(),
|
||||
getDevFixFriendlyName(env.rootLeft),
|
||||
getDevFixFriendlyName(env.rootRight),
|
||||
}};
|
||||
}
|
||||
|
||||
bool DifferEnvironment::isProvenEqual(TypeId left, TypeId right) const
|
||||
{
|
||||
return provenEqual.find({left, right}) != provenEqual.end();
|
||||
}
|
||||
|
||||
bool DifferEnvironment::isAssumedEqual(TypeId left, TypeId right) const
|
||||
{
|
||||
return visiting.find({left, right}) != visiting.end();
|
||||
}
|
||||
|
||||
void DifferEnvironment::recordProvenEqual(TypeId left, TypeId right)
|
||||
{
|
||||
provenEqual.insert({left, right});
|
||||
provenEqual.insert({right, left});
|
||||
}
|
||||
|
||||
void DifferEnvironment::pushVisiting(TypeId left, TypeId right)
|
||||
{
|
||||
LUAU_ASSERT(visiting.find({left, right}) == visiting.end());
|
||||
LUAU_ASSERT(visiting.find({right, left}) == visiting.end());
|
||||
visitingStack.push_back({left, right});
|
||||
visiting.insert({left, right});
|
||||
visiting.insert({right, left});
|
||||
}
|
||||
|
||||
void DifferEnvironment::popVisiting()
|
||||
{
|
||||
auto tyPair = visitingStack.back();
|
||||
visiting.erase({tyPair.first, tyPair.second});
|
||||
visiting.erase({tyPair.second, tyPair.first});
|
||||
visitingStack.pop_back();
|
||||
}
|
||||
|
||||
std::vector<std::pair<TypeId, TypeId>>::const_reverse_iterator DifferEnvironment::visitingBegin() const
|
||||
{
|
||||
return visitingStack.crbegin();
|
||||
}
|
||||
|
||||
std::vector<std::pair<TypeId, TypeId>>::const_reverse_iterator DifferEnvironment::visitingEnd() const
|
||||
{
|
||||
return visitingStack.crend();
|
||||
}
|
||||
|
||||
DifferResult diff(TypeId ty1, TypeId ty2)
|
||||
{
|
||||
DifferEnvironment differEnv{ty1, ty2, DenseHashMap<TypeId, TypeId>{nullptr}};
|
||||
DifferEnvironment differEnv{ty1, ty2};
|
||||
return diffUsingEnv(differEnv, ty1, ty2);
|
||||
}
|
||||
|
||||
@ -702,7 +918,8 @@ bool isSimple(TypeId ty)
|
||||
{
|
||||
ty = follow(ty);
|
||||
// TODO: think about GenericType, etc.
|
||||
return get<PrimitiveType>(ty) || get<SingletonType>(ty) || get<AnyType>(ty) || get<NegationType>(ty);
|
||||
return get<PrimitiveType>(ty) || get<SingletonType>(ty) || get<AnyType>(ty) || get<NegationType>(ty) || get<ClassType>(ty) ||
|
||||
get<UnknownType>(ty) || get<NeverType>(ty);
|
||||
}
|
||||
|
||||
} // namespace Luau
|
||||
|
@ -1182,7 +1182,7 @@ ModulePtr check(const SourceModule& sourceModule, const std::vector<RequireCycle
|
||||
|
||||
Normalizer normalizer{&result->internalTypes, builtinTypes, NotNull{&unifierState}};
|
||||
|
||||
ConstraintGraphBuilder cgb{result, &result->internalTypes, moduleResolver, builtinTypes, iceHandler, parentScope, std::move(prepareModuleScope),
|
||||
ConstraintGraphBuilder cgb{result, NotNull{&normalizer}, moduleResolver, builtinTypes, iceHandler, parentScope, std::move(prepareModuleScope),
|
||||
logger.get(), NotNull{&dfg}, requireCycles};
|
||||
|
||||
cgb.visit(sourceModule.root);
|
||||
@ -1229,7 +1229,7 @@ ModulePtr check(const SourceModule& sourceModule, const std::vector<RequireCycle
|
||||
}
|
||||
else
|
||||
{
|
||||
Luau::check(builtinTypes, NotNull{&unifierState}, logger.get(), sourceModule, result.get());
|
||||
Luau::check(builtinTypes, NotNull{&unifierState}, NotNull{&limits}, logger.get(), sourceModule, result.get());
|
||||
}
|
||||
|
||||
// It would be nice if we could freeze the arenas before doing type
|
||||
|
@ -1,8 +1,13 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/Instantiation.h"
|
||||
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/TxnLog.h"
|
||||
#include "Luau/TypeArena.h"
|
||||
#include "Luau/TypeCheckLimits.h"
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -52,7 +57,7 @@ TypeId Instantiation::clean(TypeId ty)
|
||||
|
||||
// Annoyingly, we have to do this even if there are no generics,
|
||||
// to replace any generic tables.
|
||||
ReplaceGenerics replaceGenerics{log, arena, level, scope, ftv->generics, ftv->genericPacks};
|
||||
ReplaceGenerics replaceGenerics{log, arena, builtinTypes, level, scope, ftv->generics, ftv->genericPacks};
|
||||
|
||||
// TODO: What to do if this returns nullopt?
|
||||
// We don't have access to the error-reporting machinery
|
||||
@ -118,9 +123,17 @@ TypeId ReplaceGenerics::clean(TypeId ty)
|
||||
clone.definitionLocation = ttv->definitionLocation;
|
||||
return addType(std::move(clone));
|
||||
}
|
||||
else if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
TypeId res = freshType(NotNull{arena}, builtinTypes, scope);
|
||||
getMutable<FreeType>(res)->level = level;
|
||||
return res;
|
||||
}
|
||||
else
|
||||
{
|
||||
return addType(FreeType{scope, level});
|
||||
}
|
||||
}
|
||||
|
||||
TypePackId ReplaceGenerics::clean(TypePackId tp)
|
||||
{
|
||||
@ -128,4 +141,75 @@ TypePackId ReplaceGenerics::clean(TypePackId tp)
|
||||
return addTypePack(TypePackVar(FreeTypePack{scope, level}));
|
||||
}
|
||||
|
||||
struct Replacer : Substitution
|
||||
{
|
||||
DenseHashMap<TypeId, TypeId> replacements;
|
||||
DenseHashMap<TypePackId, TypePackId> replacementPacks;
|
||||
|
||||
Replacer(NotNull<TypeArena> arena, DenseHashMap<TypeId, TypeId> replacements, DenseHashMap<TypePackId, TypePackId> replacementPacks)
|
||||
: Substitution(TxnLog::empty(), arena)
|
||||
, replacements(std::move(replacements))
|
||||
, replacementPacks(std::move(replacementPacks))
|
||||
{
|
||||
}
|
||||
|
||||
bool isDirty(TypeId ty) override
|
||||
{
|
||||
return replacements.find(ty) != nullptr;
|
||||
}
|
||||
|
||||
bool isDirty(TypePackId tp) override
|
||||
{
|
||||
return replacementPacks.find(tp) != nullptr;
|
||||
}
|
||||
|
||||
TypeId clean(TypeId ty) override
|
||||
{
|
||||
return replacements[ty];
|
||||
}
|
||||
|
||||
TypePackId clean(TypePackId tp) override
|
||||
{
|
||||
return replacementPacks[tp];
|
||||
}
|
||||
};
|
||||
|
||||
std::optional<TypeId> instantiate(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, NotNull<TypeCheckLimits> limits, NotNull<Scope> scope, TypeId ty)
|
||||
{
|
||||
ty = follow(ty);
|
||||
|
||||
const FunctionType* ft = get<FunctionType>(ty);
|
||||
if (!ft)
|
||||
return ty;
|
||||
|
||||
if (ft->generics.empty() && ft->genericPacks.empty())
|
||||
return ty;
|
||||
|
||||
DenseHashMap<TypeId, TypeId> replacements{nullptr};
|
||||
DenseHashMap<TypePackId, TypePackId> replacementPacks{nullptr};
|
||||
|
||||
for (TypeId g : ft->generics)
|
||||
replacements[g] = freshType(arena, builtinTypes, scope);
|
||||
|
||||
for (TypePackId g : ft->genericPacks)
|
||||
replacementPacks[g] = arena->freshTypePack(scope);
|
||||
|
||||
Replacer r{arena, std::move(replacements), std::move(replacementPacks)};
|
||||
|
||||
if (limits->instantiationChildLimit)
|
||||
r.childLimit = *limits->instantiationChildLimit;
|
||||
|
||||
std::optional<TypeId> res = r.substitute(ty);
|
||||
if (!res)
|
||||
return res;
|
||||
|
||||
FunctionType* ft2 = getMutable<FunctionType>(*res);
|
||||
LUAU_ASSERT(ft != ft2);
|
||||
|
||||
ft2->generics.clear();
|
||||
ft2->genericPacks.clear();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
} // namespace Luau
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include <algorithm>
|
||||
|
||||
LUAU_FASTINT(LuauTypeReductionRecursionLimit)
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -1109,6 +1110,19 @@ TypeId TypeSimplifier::intersect(TypeId left, TypeId right)
|
||||
if (get<NeverType>(right))
|
||||
return right;
|
||||
|
||||
if (auto lf = get<FreeType>(left))
|
||||
{
|
||||
Relation r = relate(lf->upperBound, right);
|
||||
if (r == Relation::Subset || r == Relation::Coincident)
|
||||
return left;
|
||||
}
|
||||
else if (auto rf = get<FreeType>(right))
|
||||
{
|
||||
Relation r = relate(left, rf->upperBound);
|
||||
if (r == Relation::Superset || r == Relation::Coincident)
|
||||
return right;
|
||||
}
|
||||
|
||||
if (isTypeVariable(left))
|
||||
{
|
||||
blockedTypes.insert(left);
|
||||
@ -1160,6 +1174,11 @@ TypeId TypeSimplifier::union_(TypeId left, TypeId right)
|
||||
left = simplify(left);
|
||||
right = simplify(right);
|
||||
|
||||
if (get<NeverType>(left))
|
||||
return right;
|
||||
if (get<NeverType>(right))
|
||||
return left;
|
||||
|
||||
if (auto leftUnion = get<UnionType>(left))
|
||||
{
|
||||
bool changed = false;
|
||||
@ -1263,6 +1282,8 @@ TypeId TypeSimplifier::simplify(TypeId ty, DenseHashSet<TypeId>& seen)
|
||||
|
||||
SimplifyResult simplifyIntersection(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId left, TypeId right)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::DebugLuauDeferredConstraintResolution);
|
||||
|
||||
TypeSimplifier s{builtinTypes, arena};
|
||||
|
||||
// fprintf(stderr, "Intersect %s and %s ...\n", toString(left).c_str(), toString(right).c_str());
|
||||
@ -1276,11 +1297,13 @@ SimplifyResult simplifyIntersection(NotNull<BuiltinTypes> builtinTypes, NotNull<
|
||||
|
||||
SimplifyResult simplifyUnion(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId left, TypeId right)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::DebugLuauDeferredConstraintResolution);
|
||||
|
||||
TypeSimplifier s{builtinTypes, arena};
|
||||
|
||||
TypeId res = s.union_(left, right);
|
||||
|
||||
// fprintf(stderr, "Union %s and %s -> %s\n", toString(a).c_str(), toString(b).c_str(), toString(res).c_str());
|
||||
// fprintf(stderr, "Union %s and %s -> %s\n", toString(left).c_str(), toString(right).c_str(), toString(res).c_str());
|
||||
|
||||
return SimplifyResult{res, std::move(s.blockedTypes)};
|
||||
}
|
||||
|
@ -75,6 +75,20 @@ struct FindCyclicTypes final : TypeVisitor
|
||||
return visitedPacks.insert(tp).second;
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const FreeType& ft) override
|
||||
{
|
||||
if (!visited.insert(ty).second)
|
||||
return false;
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
traverse(ft.lowerBound);
|
||||
traverse(ft.upperBound);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const TableType& ttv) override
|
||||
{
|
||||
if (!visited.insert(ty).second)
|
||||
@ -428,6 +442,36 @@ struct TypeStringifier
|
||||
{
|
||||
state.result.invalid = true;
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
const TypeId lowerBound = follow(ftv.lowerBound);
|
||||
const TypeId upperBound = follow(ftv.upperBound);
|
||||
if (get<NeverType>(lowerBound) && get<UnknownType>(upperBound))
|
||||
{
|
||||
state.emit("'");
|
||||
state.emit(state.getName(ty));
|
||||
}
|
||||
else
|
||||
{
|
||||
state.emit("(");
|
||||
if (!get<NeverType>(lowerBound))
|
||||
{
|
||||
stringify(lowerBound);
|
||||
state.emit(" <: ");
|
||||
}
|
||||
state.emit("'");
|
||||
state.emit(state.getName(ty));
|
||||
|
||||
if (!get<UnknownType>(upperBound))
|
||||
{
|
||||
state.emit(" <: ");
|
||||
stringify(upperBound);
|
||||
}
|
||||
state.emit(")");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (FInt::DebugLuauVerboseTypeNames >= 1)
|
||||
state.emit("free-");
|
||||
|
||||
|
@ -476,6 +476,14 @@ FreeType::FreeType(Scope* scope, TypeLevel level)
|
||||
{
|
||||
}
|
||||
|
||||
FreeType::FreeType(Scope* scope, TypeId lowerBound, TypeId upperBound)
|
||||
: index(Unifiable::freshIndex())
|
||||
, scope(scope)
|
||||
, lowerBound(lowerBound)
|
||||
, upperBound(upperBound)
|
||||
{
|
||||
}
|
||||
|
||||
GenericType::GenericType()
|
||||
: index(Unifiable::freshIndex())
|
||||
, name("g" + std::to_string(index))
|
||||
@ -1351,7 +1359,7 @@ static bool dcrMagicFunctionFormat(MagicFunctionCallContext context)
|
||||
// unify the prefix one argument at a time
|
||||
for (size_t i = 0; i < expected.size() && i + paramOffset < params.size(); ++i)
|
||||
{
|
||||
context.solver->unify(params[i + paramOffset], expected[i], context.solver->rootScope);
|
||||
context.solver->unify(context.solver->rootScope, context.callSite->location, params[i + paramOffset], expected[i]);
|
||||
}
|
||||
|
||||
// if we know the argument count or if we have too many arguments for sure, we can issue an error
|
||||
@ -1481,7 +1489,7 @@ static bool dcrMagicFunctionGmatch(MagicFunctionCallContext context)
|
||||
if (returnTypes.empty())
|
||||
return false;
|
||||
|
||||
context.solver->unify(params[0], context.solver->builtinTypes->stringType, context.solver->rootScope);
|
||||
context.solver->unify(context.solver->rootScope, context.callSite->location, params[0], context.solver->builtinTypes->stringType);
|
||||
|
||||
const TypePackId emptyPack = arena->addTypePack({});
|
||||
const TypePackId returnList = arena->addTypePack(returnTypes);
|
||||
@ -1550,13 +1558,13 @@ static bool dcrMagicFunctionMatch(MagicFunctionCallContext context)
|
||||
if (returnTypes.empty())
|
||||
return false;
|
||||
|
||||
context.solver->unify(params[0], context.solver->builtinTypes->stringType, context.solver->rootScope);
|
||||
context.solver->unify(context.solver->rootScope, context.callSite->location, params[0], context.solver->builtinTypes->stringType);
|
||||
|
||||
const TypeId optionalNumber = arena->addType(UnionType{{context.solver->builtinTypes->nilType, context.solver->builtinTypes->numberType}});
|
||||
|
||||
size_t initIndex = context.callSite->self ? 1 : 2;
|
||||
if (params.size() == 3 && context.callSite->args.size > initIndex)
|
||||
context.solver->unify(params[2], optionalNumber, context.solver->rootScope);
|
||||
context.solver->unify(context.solver->rootScope, context.callSite->location, params[2], optionalNumber);
|
||||
|
||||
const TypePackId returnList = arena->addTypePack(returnTypes);
|
||||
asMutable(context.result)->ty.emplace<BoundTypePack>(returnList);
|
||||
@ -1653,17 +1661,17 @@ static bool dcrMagicFunctionFind(MagicFunctionCallContext context)
|
||||
return false;
|
||||
}
|
||||
|
||||
context.solver->unify(params[0], builtinTypes->stringType, context.solver->rootScope);
|
||||
context.solver->unify(context.solver->rootScope, context.callSite->location, params[0], builtinTypes->stringType);
|
||||
|
||||
const TypeId optionalNumber = arena->addType(UnionType{{builtinTypes->nilType, builtinTypes->numberType}});
|
||||
const TypeId optionalBoolean = arena->addType(UnionType{{builtinTypes->nilType, builtinTypes->booleanType}});
|
||||
|
||||
size_t initIndex = context.callSite->self ? 1 : 2;
|
||||
if (params.size() >= 3 && context.callSite->args.size > initIndex)
|
||||
context.solver->unify(params[2], optionalNumber, context.solver->rootScope);
|
||||
context.solver->unify(context.solver->rootScope, context.callSite->location, params[2], optionalNumber);
|
||||
|
||||
if (params.size() == 4 && context.callSite->args.size > plainIndex)
|
||||
context.solver->unify(params[3], optionalBoolean, context.solver->rootScope);
|
||||
context.solver->unify(context.solver->rootScope, context.callSite->location, params[3], optionalBoolean);
|
||||
|
||||
returnTypes.insert(returnTypes.begin(), {optionalNumber, optionalNumber});
|
||||
|
||||
@ -1672,6 +1680,11 @@ static bool dcrMagicFunctionFind(MagicFunctionCallContext context)
|
||||
return true;
|
||||
}
|
||||
|
||||
TypeId freshType(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, Scope* scope)
|
||||
{
|
||||
return arena->addType(FreeType{scope, builtinTypes->neverType, builtinTypes->unknownType});
|
||||
}
|
||||
|
||||
std::vector<TypeId> filterMap(TypeId type, TypeIdPredicate predicate)
|
||||
{
|
||||
type = follow(type);
|
||||
|
@ -228,7 +228,8 @@ struct TypeChecker2
|
||||
{
|
||||
NotNull<BuiltinTypes> builtinTypes;
|
||||
DcrLogger* logger;
|
||||
NotNull<InternalErrorReporter> ice;
|
||||
const NotNull<TypeCheckLimits> limits;
|
||||
const NotNull<InternalErrorReporter> ice;
|
||||
const SourceModule* sourceModule;
|
||||
Module* module;
|
||||
TypeArena testArena;
|
||||
@ -240,10 +241,11 @@ struct TypeChecker2
|
||||
|
||||
Normalizer normalizer;
|
||||
|
||||
TypeChecker2(NotNull<BuiltinTypes> builtinTypes, NotNull<UnifierSharedState> unifierState, DcrLogger* logger, const SourceModule* sourceModule,
|
||||
TypeChecker2(NotNull<BuiltinTypes> builtinTypes, NotNull<UnifierSharedState> unifierState, NotNull<TypeCheckLimits> limits, DcrLogger* logger, const SourceModule* sourceModule,
|
||||
Module* module)
|
||||
: builtinTypes(builtinTypes)
|
||||
, logger(logger)
|
||||
, limits(limits)
|
||||
, ice(unifierState->iceHandler)
|
||||
, sourceModule(sourceModule)
|
||||
, module(module)
|
||||
@ -807,9 +809,9 @@ struct TypeChecker2
|
||||
else if (std::optional<TypeId> iterMmTy =
|
||||
findMetatableEntry(builtinTypes, module->errors, iteratorTy, "__iter", forInStatement->values.data[0]->location))
|
||||
{
|
||||
Instantiation instantiation{TxnLog::empty(), &arena, TypeLevel{}, scope};
|
||||
Instantiation instantiation{TxnLog::empty(), &arena, builtinTypes, TypeLevel{}, scope};
|
||||
|
||||
if (std::optional<TypeId> instantiatedIterMmTy = instantiation.substitute(*iterMmTy))
|
||||
if (std::optional<TypeId> instantiatedIterMmTy = instantiate(builtinTypes, NotNull{&arena}, limits, scope, *iterMmTy))
|
||||
{
|
||||
if (const FunctionType* iterMmFtv = get<FunctionType>(*instantiatedIterMmTy))
|
||||
{
|
||||
@ -2679,9 +2681,9 @@ struct TypeChecker2
|
||||
};
|
||||
|
||||
void check(
|
||||
NotNull<BuiltinTypes> builtinTypes, NotNull<UnifierSharedState> unifierState, DcrLogger* logger, const SourceModule& sourceModule, Module* module)
|
||||
NotNull<BuiltinTypes> builtinTypes, NotNull<UnifierSharedState> unifierState, NotNull<TypeCheckLimits> limits, DcrLogger* logger, const SourceModule& sourceModule, Module* module)
|
||||
{
|
||||
TypeChecker2 typeChecker{builtinTypes, unifierState, logger, &sourceModule, module};
|
||||
TypeChecker2 typeChecker{builtinTypes, unifierState, limits, logger, &sourceModule, module};
|
||||
|
||||
typeChecker.visit(sourceModule.root);
|
||||
|
||||
|
@ -3,14 +3,15 @@
|
||||
#include "Luau/TypeFamily.h"
|
||||
|
||||
#include "Luau/DenseHash.h"
|
||||
#include "Luau/VisitType.h"
|
||||
#include "Luau/TxnLog.h"
|
||||
#include "Luau/Substitution.h"
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/TypeUtils.h"
|
||||
#include "Luau/Unifier.h"
|
||||
#include "Luau/Instantiation.h"
|
||||
#include "Luau/Normalize.h"
|
||||
#include "Luau/Substitution.h"
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/TxnLog.h"
|
||||
#include "Luau/TypeCheckLimits.h"
|
||||
#include "Luau/TypeUtils.h"
|
||||
#include "Luau/Unifier.h"
|
||||
#include "Luau/VisitType.h"
|
||||
|
||||
LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyGraphReductionMaximumSteps, 1'000'000);
|
||||
|
||||
@ -397,8 +398,8 @@ TypeFamilyReductionResult<TypeId> addFamilyFn(std::vector<TypeId> typeParams, st
|
||||
if (!mmFtv)
|
||||
return {std::nullopt, true, {}, {}};
|
||||
|
||||
Instantiation instantiation{log.get(), arena.get(), TypeLevel{}, scope.get()};
|
||||
if (std::optional<TypeId> instantiatedAddMm = instantiation.substitute(log->follow(*addMm)))
|
||||
TypeCheckLimits limits; // TODO: We need to thread TypeCheckLimits in from Frontend to here.
|
||||
if (std::optional<TypeId> instantiatedAddMm = instantiate(builtins, arena, NotNull{&limits}, scope, log->follow(*addMm)))
|
||||
{
|
||||
if (const FunctionType* instantiatedMmFtv = get<FunctionType>(*instantiatedAddMm))
|
||||
{
|
||||
|
@ -4860,7 +4860,7 @@ TypeId TypeChecker::instantiate(const ScopePtr& scope, TypeId ty, Location locat
|
||||
if (ftv && ftv->hasNoFreeOrGenericTypes)
|
||||
return ty;
|
||||
|
||||
Instantiation instantiation{log, ¤tModule->internalTypes, scope->level, /*scope*/ nullptr};
|
||||
Instantiation instantiation{log, ¤tModule->internalTypes, builtinTypes, scope->level, /*scope*/ nullptr};
|
||||
|
||||
if (instantiationChildLimit)
|
||||
instantiation.childLimit = *instantiationChildLimit;
|
||||
|
@ -8,6 +8,8 @@
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
@ -190,6 +192,12 @@ TypePack extendTypePack(
|
||||
}
|
||||
else
|
||||
{
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
FreeType ft{ftp->scope, builtinTypes->neverType, builtinTypes->unknownType};
|
||||
t = arena.addType(ft);
|
||||
}
|
||||
else
|
||||
t = arena.freshType(ftp->scope);
|
||||
}
|
||||
|
||||
|
@ -24,6 +24,8 @@ LUAU_FASTFLAGVARIABLE(LuauTransitiveSubtyping, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauOccursIsntAlwaysFailure, false)
|
||||
LUAU_FASTFLAG(LuauNormalizeBlockedTypes)
|
||||
LUAU_FASTFLAG(LuauAlwaysCommitInferencesOfFunctionCalls)
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTableUnifyRecursionLimit, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -1741,6 +1743,9 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
|
||||
}
|
||||
|
||||
auto mkFreshType = [this](Scope* scope, TypeLevel level) {
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
return freshType(NotNull{types}, builtinTypes, scope);
|
||||
else
|
||||
return types->freshType(scope, level);
|
||||
};
|
||||
|
||||
@ -1977,7 +1982,7 @@ void Unifier::tryUnifyFunctions(TypeId subTy, TypeId superTy, bool isFunctionCal
|
||||
// generic methods in tables to be marked read-only.
|
||||
if (FFlag::LuauInstantiateInSubtyping && shouldInstantiate)
|
||||
{
|
||||
Instantiation instantiation{&log, types, scope->level, scope};
|
||||
Instantiation instantiation{&log, types, builtinTypes, scope->level, scope};
|
||||
|
||||
std::optional<TypeId> instantiated = instantiation.substitute(subTy);
|
||||
if (instantiated.has_value())
|
||||
@ -2126,7 +2131,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection,
|
||||
{
|
||||
if (variance == Covariant && subTable->state == TableState::Generic && superTable->state != TableState::Generic)
|
||||
{
|
||||
Instantiation instantiation{&log, types, subTable->level, scope};
|
||||
Instantiation instantiation{&log, types, builtinTypes, subTable->level, scope};
|
||||
|
||||
std::optional<TypeId> instantiated = instantiation.substitute(subTy);
|
||||
if (instantiated.has_value())
|
||||
@ -2250,6 +2255,18 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection,
|
||||
TableType* newSubTable = log.getMutable<TableType>(subTyNew);
|
||||
|
||||
if (superTable != newSuperTable || subTable != newSubTable)
|
||||
{
|
||||
if (FFlag::LuauTableUnifyRecursionLimit)
|
||||
{
|
||||
if (errors.empty())
|
||||
{
|
||||
RecursionLimiter _ra(&sharedState.counters.recursionCount, sharedState.counters.recursionLimit);
|
||||
tryUnifyTables(subTy, superTy, isIntersection);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (errors.empty())
|
||||
return tryUnifyTables(subTy, superTy, isIntersection);
|
||||
@ -2257,6 +2274,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection,
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& [name, prop] : subTable->props)
|
||||
{
|
||||
@ -2328,6 +2346,18 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection,
|
||||
TableType* newSubTable = log.getMutable<TableType>(subTyNew);
|
||||
|
||||
if (superTable != newSuperTable || subTable != newSubTable)
|
||||
{
|
||||
if (FFlag::LuauTableUnifyRecursionLimit)
|
||||
{
|
||||
if (errors.empty())
|
||||
{
|
||||
RecursionLimiter _ra(&sharedState.counters.recursionCount, sharedState.counters.recursionLimit);
|
||||
tryUnifyTables(subTy, superTy, isIntersection);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (errors.empty())
|
||||
return tryUnifyTables(subTy, superTy, isIntersection);
|
||||
@ -2335,6 +2365,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection,
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Unify indexers
|
||||
if (superTable->indexer && subTable->indexer)
|
||||
|
366
Analysis/src/Unifier2.cpp
Normal file
366
Analysis/src/Unifier2.cpp
Normal file
@ -0,0 +1,366 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
|
||||
#include "Luau/Unifier2.h"
|
||||
|
||||
#include "Luau/Scope.h"
|
||||
#include "Luau/Simplify.h"
|
||||
#include "Luau/Substitution.h"
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/TxnLog.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/TypeArena.h"
|
||||
#include "Luau/TypeUtils.h"
|
||||
#include "Luau/VisitType.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <unordered_set>
|
||||
|
||||
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
Unifier2::Unifier2(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, NotNull<InternalErrorReporter> ice)
|
||||
: arena(arena)
|
||||
, builtinTypes(builtinTypes)
|
||||
, ice(ice)
|
||||
, recursionLimit(FInt::LuauTypeInferRecursionLimit)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool Unifier2::unify(TypeId subTy, TypeId superTy)
|
||||
{
|
||||
subTy = follow(subTy);
|
||||
superTy = follow(superTy);
|
||||
|
||||
if (subTy == superTy)
|
||||
return true;
|
||||
|
||||
FreeType* subFree = getMutable<FreeType>(subTy);
|
||||
FreeType* superFree = getMutable<FreeType>(superTy);
|
||||
|
||||
if (subFree)
|
||||
subFree->upperBound = mkIntersection(subFree->upperBound, superTy);
|
||||
|
||||
if (superFree)
|
||||
superFree->lowerBound = mkUnion(superFree->lowerBound, subTy);
|
||||
|
||||
if (subFree || superFree)
|
||||
return true;
|
||||
|
||||
const FunctionType* subFn = get<FunctionType>(subTy);
|
||||
const FunctionType* superFn = get<FunctionType>(superTy);
|
||||
|
||||
if (subFn && superFn)
|
||||
{
|
||||
bool argResult = unify(superFn->argTypes, subFn->argTypes);
|
||||
bool retResult = unify(subFn->retTypes, superFn->retTypes);
|
||||
return argResult && retResult;
|
||||
}
|
||||
|
||||
// The unification failed, but we're not doing type checking.
|
||||
return true;
|
||||
}
|
||||
|
||||
// FIXME? This should probably return an ErrorVec or an optional<TypeError>
|
||||
// rather than a boolean to signal an occurs check failure.
|
||||
bool Unifier2::unify(TypePackId subTp, TypePackId superTp)
|
||||
{
|
||||
subTp = follow(subTp);
|
||||
superTp = follow(superTp);
|
||||
|
||||
const FreeTypePack* subFree = get<FreeTypePack>(subTp);
|
||||
const FreeTypePack* superFree = get<FreeTypePack>(superTp);
|
||||
|
||||
if (subFree)
|
||||
{
|
||||
DenseHashSet<TypePackId> seen{nullptr};
|
||||
if (OccursCheckResult::Fail == occursCheck(seen, subTp, superTp))
|
||||
{
|
||||
asMutable(subTp)->ty.emplace<BoundTypePack>(builtinTypes->errorRecoveryTypePack());
|
||||
return false;
|
||||
}
|
||||
|
||||
asMutable(subTp)->ty.emplace<BoundTypePack>(superTp);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (superFree)
|
||||
{
|
||||
DenseHashSet<TypePackId> seen{nullptr};
|
||||
if (OccursCheckResult::Fail == occursCheck(seen, superTp, subTp))
|
||||
{
|
||||
asMutable(superTp)->ty.emplace<BoundTypePack>(builtinTypes->errorRecoveryTypePack());
|
||||
return false;
|
||||
}
|
||||
|
||||
asMutable(superTp)->ty.emplace<BoundTypePack>(subTp);
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t maxLength = std::max(
|
||||
flatten(subTp).first.size(),
|
||||
flatten(superTp).first.size()
|
||||
);
|
||||
|
||||
auto [subTypes, subTail] = extendTypePack(*arena, builtinTypes, subTp, maxLength);
|
||||
auto [superTypes, superTail] = extendTypePack(*arena, builtinTypes, superTp, maxLength);
|
||||
|
||||
if (subTypes.size() < maxLength || superTypes.size() < maxLength)
|
||||
return true;
|
||||
|
||||
for (size_t i = 0; i < maxLength; ++i)
|
||||
unify(subTypes[i], superTypes[i]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
struct FreeTypeSearcher : TypeVisitor
|
||||
{
|
||||
NotNull<Scope> scope;
|
||||
|
||||
explicit FreeTypeSearcher(NotNull<Scope> scope)
|
||||
: TypeVisitor(/*skipBoundTypes*/ true)
|
||||
, scope(scope)
|
||||
{}
|
||||
|
||||
enum { Positive, Negative } polarity = Positive;
|
||||
|
||||
void flip()
|
||||
{
|
||||
switch (polarity)
|
||||
{
|
||||
case Positive: polarity = Negative; break;
|
||||
case Negative: polarity = Positive; break;
|
||||
}
|
||||
}
|
||||
|
||||
std::unordered_set<TypeId> negativeTypes;
|
||||
std::unordered_set<TypeId> positiveTypes;
|
||||
|
||||
bool visit(TypeId ty) override
|
||||
{
|
||||
LUAU_ASSERT(ty);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const FreeType& ft) override
|
||||
{
|
||||
if (!subsumes(scope, ft.scope))
|
||||
return true;
|
||||
|
||||
switch (polarity)
|
||||
{
|
||||
case Positive: positiveTypes.insert(ty); break;
|
||||
case Negative: negativeTypes.insert(ty); break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const FunctionType& ft) override
|
||||
{
|
||||
flip();
|
||||
traverse(ft.argTypes);
|
||||
flip();
|
||||
|
||||
traverse(ft.retTypes);
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
struct MutatingGeneralizer : TypeOnceVisitor
|
||||
{
|
||||
NotNull<BuiltinTypes> builtinTypes;
|
||||
|
||||
NotNull<Scope> scope;
|
||||
std::unordered_set<TypeId> positiveTypes;
|
||||
std::unordered_set<TypeId> negativeTypes;
|
||||
std::vector<TypeId> generics;
|
||||
|
||||
MutatingGeneralizer(NotNull<BuiltinTypes> builtinTypes, NotNull<Scope> scope, std::unordered_set<TypeId> positiveTypes, std::unordered_set<TypeId> negativeTypes)
|
||||
: TypeOnceVisitor(/* skipBoundTypes */ true)
|
||||
, builtinTypes(builtinTypes)
|
||||
, scope(scope)
|
||||
, positiveTypes(std::move(positiveTypes))
|
||||
, negativeTypes(std::move(negativeTypes))
|
||||
{}
|
||||
|
||||
static void replace(DenseHashSet<TypeId>& seen, TypeId haystack, TypeId needle, TypeId replacement)
|
||||
{
|
||||
haystack = follow(haystack);
|
||||
|
||||
if (seen.find(haystack))
|
||||
return;
|
||||
seen.insert(haystack);
|
||||
|
||||
std::vector<TypeId>* parts = nullptr;
|
||||
if (UnionType* ut = getMutable<UnionType>(haystack))
|
||||
parts = &ut->options;
|
||||
else if (IntersectionType* it = getMutable<IntersectionType>(needle))
|
||||
parts = &it->parts;
|
||||
else
|
||||
return;
|
||||
|
||||
LUAU_ASSERT(parts);
|
||||
|
||||
for (TypeId& option : *parts)
|
||||
{
|
||||
// FIXME: I bet this function has reentrancy problems
|
||||
option = follow(option);
|
||||
if (option == needle)
|
||||
{
|
||||
LUAU_ASSERT(!seen.find(option));
|
||||
option = replacement;
|
||||
}
|
||||
|
||||
// TODO seen set
|
||||
else if (get<UnionType>(option))
|
||||
replace(seen, option, needle, haystack);
|
||||
else if (get<IntersectionType>(option))
|
||||
replace(seen, option, needle, haystack);
|
||||
}
|
||||
}
|
||||
|
||||
bool visit (TypeId ty, const FreeType&) override
|
||||
{
|
||||
const FreeType* ft = get<FreeType>(ty);
|
||||
LUAU_ASSERT(ft);
|
||||
|
||||
traverse(ft->lowerBound);
|
||||
traverse(ft->upperBound);
|
||||
|
||||
// ft is potentially invalid now.
|
||||
ty = follow(ty);
|
||||
ft = get<FreeType>(ty);
|
||||
if (!ft)
|
||||
return false;
|
||||
|
||||
const bool isPositive = positiveTypes.count(ty);
|
||||
const bool isNegative = negativeTypes.count(ty);
|
||||
|
||||
if (!isPositive && !isNegative)
|
||||
return false;
|
||||
|
||||
const bool hasLowerBound = !get<NeverType>(follow(ft->lowerBound));
|
||||
const bool hasUpperBound = !get<UnknownType>(follow(ft->upperBound));
|
||||
|
||||
DenseHashSet<TypeId> seen{nullptr};
|
||||
seen.insert(ty);
|
||||
|
||||
if (!hasLowerBound && !hasUpperBound)
|
||||
{
|
||||
emplaceType<GenericType>(asMutable(ty), scope);
|
||||
generics.push_back(ty);
|
||||
}
|
||||
|
||||
// It is possible that this free type has other free types in its upper
|
||||
// or lower bounds. If this is the case, we must replace those
|
||||
// references with never (for the lower bound) or unknown (for the upper
|
||||
// bound).
|
||||
//
|
||||
// If we do not do this, we get tautological bounds like a <: a <: unknown.
|
||||
else if (isPositive && !hasUpperBound)
|
||||
{
|
||||
if (FreeType* lowerFree = getMutable<FreeType>(ft->lowerBound); lowerFree && lowerFree->upperBound == ty)
|
||||
lowerFree->upperBound = builtinTypes->unknownType;
|
||||
else
|
||||
replace(seen, ft->lowerBound, ty, builtinTypes->unknownType);
|
||||
emplaceType<BoundType>(asMutable(ty), ft->lowerBound);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (FreeType* upperFree = getMutable<FreeType>(ft->upperBound); upperFree && upperFree->lowerBound == ty)
|
||||
upperFree->lowerBound = builtinTypes->neverType;
|
||||
else
|
||||
replace(seen, ft->upperBound, ty, builtinTypes->neverType);
|
||||
emplaceType<BoundType>(asMutable(ty), ft->upperBound);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
std::optional<TypeId> Unifier2::generalize(NotNull<Scope> scope, TypeId ty)
|
||||
{
|
||||
ty = follow(ty);
|
||||
|
||||
if (ty->owningArena != arena)
|
||||
return ty;
|
||||
|
||||
if (ty->persistent)
|
||||
return ty;
|
||||
|
||||
if (const FunctionType* ft = get<FunctionType>(ty); ft && (!ft->generics.empty() || !ft->genericPacks.empty()))
|
||||
return ty;
|
||||
|
||||
FreeTypeSearcher fts{scope};
|
||||
fts.traverse(ty);
|
||||
|
||||
MutatingGeneralizer gen{builtinTypes, scope, std::move(fts.positiveTypes), std::move(fts.negativeTypes)};
|
||||
|
||||
gen.traverse(ty);
|
||||
|
||||
std::optional<TypeId> res = ty;
|
||||
|
||||
FunctionType* ftv = getMutable<FunctionType>(follow(*res));
|
||||
if (ftv)
|
||||
ftv->generics = std::move(gen.generics);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
TypeId Unifier2::mkUnion(TypeId left, TypeId right)
|
||||
{
|
||||
left = follow(left);
|
||||
right = follow(right);
|
||||
|
||||
return simplifyUnion(builtinTypes, arena, left, right).result;
|
||||
}
|
||||
|
||||
TypeId Unifier2::mkIntersection(TypeId left, TypeId right)
|
||||
{
|
||||
left = follow(left);
|
||||
right = follow(right);
|
||||
|
||||
return simplifyIntersection(builtinTypes, arena, left, right).result;
|
||||
}
|
||||
|
||||
OccursCheckResult Unifier2::occursCheck(DenseHashSet<TypePackId>& seen, TypePackId needle, TypePackId haystack)
|
||||
{
|
||||
needle = follow(needle);
|
||||
haystack = follow(haystack);
|
||||
|
||||
if (seen.find(haystack))
|
||||
return OccursCheckResult::Pass;
|
||||
|
||||
seen.insert(haystack);
|
||||
|
||||
if (getMutable<ErrorTypePack>(needle))
|
||||
return OccursCheckResult::Pass;
|
||||
|
||||
if (!getMutable<FreeTypePack>(needle))
|
||||
ice->ice("Expected needle pack to be free");
|
||||
|
||||
RecursionLimiter _ra(&recursionCount, recursionLimit);
|
||||
|
||||
while (!getMutable<Unifiable::Error>(haystack))
|
||||
{
|
||||
if (needle == haystack)
|
||||
return OccursCheckResult::Fail;
|
||||
|
||||
if (auto a = get<TypePack>(haystack); a && a->tail)
|
||||
{
|
||||
haystack = follow(*a->tail);
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return OccursCheckResult::Pass;
|
||||
}
|
||||
|
||||
}
|
@ -168,7 +168,12 @@ enum class IrCmd : uint8_t
|
||||
// Compute Luau 'not' operation on destructured TValue
|
||||
// A: tag
|
||||
// B: int (value)
|
||||
NOT_ANY, // TODO: boolean specialization will be useful
|
||||
NOT_ANY,
|
||||
|
||||
// Perform a TValue comparison, supported conditions are LessEqual, Less and Equal
|
||||
// A, B: Rn
|
||||
// C: condition
|
||||
CMP_ANY,
|
||||
|
||||
// Unconditional jump
|
||||
// A: block/vmexit
|
||||
@ -224,13 +229,6 @@ enum class IrCmd : uint8_t
|
||||
// E: block (if false)
|
||||
JUMP_CMP_NUM,
|
||||
|
||||
// Perform a conditional jump based on the result of TValue comparison
|
||||
// A, B: Rn
|
||||
// C: condition
|
||||
// D: block (if true)
|
||||
// E: block (if false)
|
||||
JUMP_CMP_ANY,
|
||||
|
||||
// Perform a conditional jump based on cached table node slot matching the actual table node slot for a key
|
||||
// A: pointer (LuaNode)
|
||||
// B: Kn
|
||||
@ -377,27 +375,33 @@ enum class IrCmd : uint8_t
|
||||
// instead.
|
||||
CHECK_TAG,
|
||||
|
||||
// Guard against a falsy tag+value
|
||||
// A: tag
|
||||
// B: value
|
||||
// C: block/vmexit/undef
|
||||
CHECK_TRUTHY,
|
||||
|
||||
// Guard against readonly table
|
||||
// A: pointer (Table)
|
||||
// B: block/undef
|
||||
// B: block/vmexit/undef
|
||||
// When undef is specified instead of a block, execution is aborted on check failure
|
||||
CHECK_READONLY,
|
||||
|
||||
// Guard against table having a metatable
|
||||
// A: pointer (Table)
|
||||
// B: block/undef
|
||||
// B: block/vmexit/undef
|
||||
// When undef is specified instead of a block, execution is aborted on check failure
|
||||
CHECK_NO_METATABLE,
|
||||
|
||||
// Guard against executing in unsafe environment, exits to VM on check failure
|
||||
// A: vmexit/undef
|
||||
// A: vmexit/vmexit/undef
|
||||
// When undef is specified, execution is aborted on check failure
|
||||
CHECK_SAFE_ENV,
|
||||
|
||||
// Guard against index overflowing the table array size
|
||||
// A: pointer (Table)
|
||||
// B: int (index)
|
||||
// C: block/undef
|
||||
// C: block/vmexit/undef
|
||||
// When undef is specified instead of a block, execution is aborted on check failure
|
||||
CHECK_ARRAY_SIZE,
|
||||
|
||||
@ -410,7 +414,7 @@ enum class IrCmd : uint8_t
|
||||
|
||||
// Guard against table node with a linked next node to ensure that our lookup hits the main position of the key
|
||||
// A: pointer (LuaNode)
|
||||
// B: block/undef
|
||||
// B: block/vmexit/undef
|
||||
// When undef is specified instead of a block, execution is aborted on check failure
|
||||
CHECK_NODE_NO_NEXT,
|
||||
|
||||
|
@ -99,7 +99,6 @@ inline bool isBlockTerminator(IrCmd cmd)
|
||||
case IrCmd::JUMP_GE_UINT:
|
||||
case IrCmd::JUMP_EQ_POINTER:
|
||||
case IrCmd::JUMP_CMP_NUM:
|
||||
case IrCmd::JUMP_CMP_ANY:
|
||||
case IrCmd::JUMP_SLOT_MATCH:
|
||||
case IrCmd::RETURN:
|
||||
case IrCmd::FORGLOOP:
|
||||
@ -122,6 +121,7 @@ inline bool isNonTerminatingJump(IrCmd cmd)
|
||||
case IrCmd::TRY_CALL_FASTGETTM:
|
||||
case IrCmd::CHECK_FASTCALL_RES:
|
||||
case IrCmd::CHECK_TAG:
|
||||
case IrCmd::CHECK_TRUTHY:
|
||||
case IrCmd::CHECK_READONLY:
|
||||
case IrCmd::CHECK_NO_METATABLE:
|
||||
case IrCmd::CHECK_SAFE_ENV:
|
||||
@ -167,6 +167,7 @@ inline bool hasResult(IrCmd cmd)
|
||||
case IrCmd::SQRT_NUM:
|
||||
case IrCmd::ABS_NUM:
|
||||
case IrCmd::NOT_ANY:
|
||||
case IrCmd::CMP_ANY:
|
||||
case IrCmd::TABLE_LEN:
|
||||
case IrCmd::STRING_LEN:
|
||||
case IrCmd::NEW_TABLE:
|
||||
|
@ -794,34 +794,31 @@ const Instruction* executeFORGPREP(lua_State* L, const Instruction* pc, StkId ba
|
||||
return pc;
|
||||
}
|
||||
|
||||
const Instruction* executeGETVARARGS(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
void executeGETVARARGSMultRet(lua_State* L, const Instruction* pc, StkId base, int rai)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
int b = LUAU_INSN_B(insn) - 1;
|
||||
int n = cast_int(base - L->ci->func) - cl->l.p->numparams - 1;
|
||||
|
||||
if (b == LUA_MULTRET)
|
||||
{
|
||||
VM_PROTECT(luaD_checkstack(L, n));
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn)); // previous call may change the stack
|
||||
StkId ra = VM_REG(rai); // previous call may change the stack
|
||||
|
||||
for (int j = 0; j < n; j++)
|
||||
setobj2s(L, ra + j, base - n + j);
|
||||
|
||||
L->top = ra + n;
|
||||
return pc;
|
||||
}
|
||||
else
|
||||
|
||||
void executeGETVARARGSConst(lua_State* L, StkId base, int rai, int b)
|
||||
{
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
int n = cast_int(base - L->ci->func) - cl->l.p->numparams - 1;
|
||||
|
||||
StkId ra = VM_REG(rai);
|
||||
|
||||
for (int j = 0; j < b && j < n; j++)
|
||||
setobj2s(L, ra + j, base - n + j);
|
||||
for (int j = n; j < b; j++)
|
||||
setnilvalue(ra + j);
|
||||
return pc;
|
||||
}
|
||||
}
|
||||
|
||||
const Instruction* executeDUPCLOSURE(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
|
@ -27,7 +27,8 @@ const Instruction* executeNEWCLOSURE(lua_State* L, const Instruction* pc, StkId
|
||||
const Instruction* executeNAMECALL(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* executeSETLIST(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* executeFORGPREP(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* executeGETVARARGS(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
void executeGETVARARGSMultRet(lua_State* L, const Instruction* pc, StkId base, int rai);
|
||||
void executeGETVARARGSConst(lua_State* L, StkId base, int rai, int b);
|
||||
const Instruction* executeDUPCLOSURE(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* executePREPVARARGS(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
|
||||
|
@ -66,29 +66,6 @@ void jumpOnNumberCmp(AssemblyBuilderX64& build, RegisterX64 tmp, OperandX64 lhs,
|
||||
}
|
||||
}
|
||||
|
||||
void jumpOnAnyCmpFallback(IrRegAllocX64& regs, AssemblyBuilderX64& build, int ra, int rb, IrCondition cond, Label& label)
|
||||
{
|
||||
IrCallWrapperX64 callWrap(regs, build);
|
||||
callWrap.addArgument(SizeX64::qword, rState);
|
||||
callWrap.addArgument(SizeX64::qword, luauRegAddress(ra));
|
||||
callWrap.addArgument(SizeX64::qword, luauRegAddress(rb));
|
||||
|
||||
if (cond == IrCondition::NotLessEqual || cond == IrCondition::LessEqual)
|
||||
callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaV_lessequal)]);
|
||||
else if (cond == IrCondition::NotLess || cond == IrCondition::Less)
|
||||
callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaV_lessthan)]);
|
||||
else if (cond == IrCondition::NotEqual || cond == IrCondition::Equal)
|
||||
callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaV_equalval)]);
|
||||
else
|
||||
LUAU_ASSERT(!"Unsupported condition");
|
||||
|
||||
emitUpdateBase(build);
|
||||
build.test(eax, eax);
|
||||
build.jcc(cond == IrCondition::NotLessEqual || cond == IrCondition::NotLess || cond == IrCondition::NotEqual ? ConditionX64::Zero
|
||||
: ConditionX64::NotZero,
|
||||
label);
|
||||
}
|
||||
|
||||
void getTableNodeAtCachedSlot(AssemblyBuilderX64& build, RegisterX64 tmp, RegisterX64 node, RegisterX64 table, int pcpos)
|
||||
{
|
||||
LUAU_ASSERT(tmp != node);
|
||||
|
@ -160,7 +160,6 @@ inline void jumpIfTruthy(AssemblyBuilderX64& build, int ri, Label& target, Label
|
||||
}
|
||||
|
||||
void jumpOnNumberCmp(AssemblyBuilderX64& build, RegisterX64 tmp, OperandX64 lhs, OperandX64 rhs, IrCondition cond, Label& label);
|
||||
void jumpOnAnyCmpFallback(IrRegAllocX64& regs, AssemblyBuilderX64& build, int ra, int rb, IrCondition cond, Label& label);
|
||||
|
||||
void getTableNodeAtCachedSlot(AssemblyBuilderX64& build, RegisterX64 tmp, RegisterX64 node, RegisterX64 table, int pcpos);
|
||||
void convertNumberToIndexOrJump(AssemblyBuilderX64& build, RegisterX64 tmp, RegisterX64 numd, RegisterX64 numi, Label& label);
|
||||
|
@ -274,14 +274,14 @@ static RegisterSet computeBlockLiveInRegSet(IrFunction& function, const IrBlock&
|
||||
case IrCmd::STORE_TVALUE:
|
||||
maybeDef(inst.a); // Argument can also be a pointer value
|
||||
break;
|
||||
case IrCmd::CMP_ANY:
|
||||
use(inst.a);
|
||||
use(inst.b);
|
||||
break;
|
||||
case IrCmd::JUMP_IF_TRUTHY:
|
||||
case IrCmd::JUMP_IF_FALSY:
|
||||
use(inst.a);
|
||||
break;
|
||||
case IrCmd::JUMP_CMP_ANY:
|
||||
use(inst.a);
|
||||
use(inst.b);
|
||||
break;
|
||||
// A <- B, C
|
||||
case IrCmd::DO_ARITH:
|
||||
case IrCmd::GET_TABLE:
|
||||
|
@ -147,6 +147,8 @@ const char* getCmdName(IrCmd cmd)
|
||||
return "ABS_NUM";
|
||||
case IrCmd::NOT_ANY:
|
||||
return "NOT_ANY";
|
||||
case IrCmd::CMP_ANY:
|
||||
return "CMP_ANY";
|
||||
case IrCmd::JUMP:
|
||||
return "JUMP";
|
||||
case IrCmd::JUMP_IF_TRUTHY:
|
||||
@ -165,8 +167,6 @@ const char* getCmdName(IrCmd cmd)
|
||||
return "JUMP_EQ_POINTER";
|
||||
case IrCmd::JUMP_CMP_NUM:
|
||||
return "JUMP_CMP_NUM";
|
||||
case IrCmd::JUMP_CMP_ANY:
|
||||
return "JUMP_CMP_ANY";
|
||||
case IrCmd::JUMP_SLOT_MATCH:
|
||||
return "JUMP_SLOT_MATCH";
|
||||
case IrCmd::TABLE_LEN:
|
||||
@ -219,6 +219,8 @@ const char* getCmdName(IrCmd cmd)
|
||||
return "PREPARE_FORN";
|
||||
case IrCmd::CHECK_TAG:
|
||||
return "CHECK_TAG";
|
||||
case IrCmd::CHECK_TRUTHY:
|
||||
return "CHECK_TRUTHY";
|
||||
case IrCmd::CHECK_READONLY:
|
||||
return "CHECK_READONLY";
|
||||
case IrCmd::CHECK_NO_METATABLE:
|
||||
|
@ -538,6 +538,33 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
}
|
||||
break;
|
||||
}
|
||||
case IrCmd::CMP_ANY:
|
||||
{
|
||||
IrCondition cond = conditionOp(inst.c);
|
||||
|
||||
regs.spill(build, index);
|
||||
build.mov(x0, rState);
|
||||
build.add(x1, rBase, uint16_t(vmRegOp(inst.a) * sizeof(TValue)));
|
||||
build.add(x2, rBase, uint16_t(vmRegOp(inst.b) * sizeof(TValue)));
|
||||
|
||||
if (cond == IrCondition::LessEqual)
|
||||
build.ldr(x3, mem(rNativeContext, offsetof(NativeContext, luaV_lessequal)));
|
||||
else if (cond == IrCondition::Less)
|
||||
build.ldr(x3, mem(rNativeContext, offsetof(NativeContext, luaV_lessthan)));
|
||||
else if (cond == IrCondition::Equal)
|
||||
build.ldr(x3, mem(rNativeContext, offsetof(NativeContext, luaV_equalval)));
|
||||
else
|
||||
LUAU_ASSERT(!"Unsupported condition");
|
||||
|
||||
build.blr(x3);
|
||||
|
||||
emitUpdateBase(build);
|
||||
|
||||
// since w0 came from a call, we need to move it so that we don't violate zextReg safety contract
|
||||
inst.regA64 = regs.allocReg(KindA64::w, index);
|
||||
build.mov(inst.regA64, w0);
|
||||
break;
|
||||
}
|
||||
case IrCmd::JUMP:
|
||||
if (inst.a.kind == IrOpKind::VmExit)
|
||||
{
|
||||
@ -671,35 +698,6 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
jumpOrFallthrough(blockOp(inst.e), next);
|
||||
break;
|
||||
}
|
||||
case IrCmd::JUMP_CMP_ANY:
|
||||
{
|
||||
IrCondition cond = conditionOp(inst.c);
|
||||
|
||||
regs.spill(build, index);
|
||||
build.mov(x0, rState);
|
||||
build.add(x1, rBase, uint16_t(vmRegOp(inst.a) * sizeof(TValue)));
|
||||
build.add(x2, rBase, uint16_t(vmRegOp(inst.b) * sizeof(TValue)));
|
||||
|
||||
if (cond == IrCondition::NotLessEqual || cond == IrCondition::LessEqual)
|
||||
build.ldr(x3, mem(rNativeContext, offsetof(NativeContext, luaV_lessequal)));
|
||||
else if (cond == IrCondition::NotLess || cond == IrCondition::Less)
|
||||
build.ldr(x3, mem(rNativeContext, offsetof(NativeContext, luaV_lessthan)));
|
||||
else if (cond == IrCondition::NotEqual || cond == IrCondition::Equal)
|
||||
build.ldr(x3, mem(rNativeContext, offsetof(NativeContext, luaV_equalval)));
|
||||
else
|
||||
LUAU_ASSERT(!"Unsupported condition");
|
||||
|
||||
build.blr(x3);
|
||||
|
||||
emitUpdateBase(build);
|
||||
|
||||
if (cond == IrCondition::NotLessEqual || cond == IrCondition::NotLess || cond == IrCondition::NotEqual)
|
||||
build.cbz(x0, labelOp(inst.d));
|
||||
else
|
||||
build.cbnz(x0, labelOp(inst.d));
|
||||
jumpOrFallthrough(blockOp(inst.e), next);
|
||||
break;
|
||||
}
|
||||
// IrCmd::JUMP_SLOT_MATCH implemented below
|
||||
case IrCmd::TABLE_LEN:
|
||||
{
|
||||
@ -1072,6 +1070,36 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
finalizeTargetLabel(inst.c, fresh);
|
||||
break;
|
||||
}
|
||||
case IrCmd::CHECK_TRUTHY:
|
||||
{
|
||||
// Constant tags which don't require boolean value check should've been removed in constant folding
|
||||
LUAU_ASSERT(inst.a.kind != IrOpKind::Constant || tagOp(inst.a) == LUA_TBOOLEAN);
|
||||
|
||||
Label fresh; // used when guard aborts execution or jumps to a VM exit
|
||||
Label& target = getTargetLabel(inst.c, fresh);
|
||||
|
||||
Label skip;
|
||||
|
||||
if (inst.a.kind != IrOpKind::Constant)
|
||||
{
|
||||
// fail to fallback on 'nil' (falsy)
|
||||
LUAU_ASSERT(LUA_TNIL == 0);
|
||||
build.cbz(regOp(inst.a), target);
|
||||
|
||||
// skip value test if it's not a boolean (truthy)
|
||||
build.cmp(regOp(inst.a), LUA_TBOOLEAN);
|
||||
build.b(ConditionA64::NotEqual, skip);
|
||||
}
|
||||
|
||||
// fail to fallback on 'false' boolean value (falsy)
|
||||
build.cbz(regOp(inst.b), target);
|
||||
|
||||
if (inst.a.kind != IrOpKind::Constant)
|
||||
build.setLabel(skip);
|
||||
|
||||
finalizeTargetLabel(inst.c, fresh);
|
||||
break;
|
||||
}
|
||||
case IrCmd::CHECK_READONLY:
|
||||
{
|
||||
Label fresh; // used when guard aborts execution or jumps to a VM exit
|
||||
@ -1530,7 +1558,26 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
LUAU_ASSERT(inst.c.kind == IrOpKind::Constant);
|
||||
|
||||
regs.spill(build, index);
|
||||
emitFallback(build, offsetof(NativeContext, executeGETVARARGS), uintOp(inst.a));
|
||||
build.mov(x0, rState);
|
||||
|
||||
if (intOp(inst.c) == LUA_MULTRET)
|
||||
{
|
||||
emitAddOffset(build, x1, rCode, uintOp(inst.a) * sizeof(Instruction));
|
||||
build.mov(x2, rBase);
|
||||
build.mov(x3, vmRegOp(inst.b));
|
||||
build.ldr(x4, mem(rNativeContext, offsetof(NativeContext, executeGETVARARGSMultRet)));
|
||||
build.blr(x4);
|
||||
|
||||
emitUpdateBase(build);
|
||||
}
|
||||
else
|
||||
{
|
||||
build.mov(x1, rBase);
|
||||
build.mov(x2, vmRegOp(inst.b));
|
||||
build.mov(x3, intOp(inst.c));
|
||||
build.ldr(x4, mem(rNativeContext, offsetof(NativeContext, executeGETVARARGSConst)));
|
||||
build.blr(x4);
|
||||
}
|
||||
break;
|
||||
case IrCmd::NEWCLOSURE:
|
||||
{
|
||||
|
@ -541,6 +541,29 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
build.setLabel(exit);
|
||||
break;
|
||||
}
|
||||
case IrCmd::CMP_ANY:
|
||||
{
|
||||
IrCondition cond = conditionOp(inst.c);
|
||||
|
||||
IrCallWrapperX64 callWrap(regs, build);
|
||||
callWrap.addArgument(SizeX64::qword, rState);
|
||||
callWrap.addArgument(SizeX64::qword, luauRegAddress(vmRegOp(inst.a)));
|
||||
callWrap.addArgument(SizeX64::qword, luauRegAddress(vmRegOp(inst.b)));
|
||||
|
||||
if (cond == IrCondition::LessEqual)
|
||||
callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaV_lessequal)]);
|
||||
else if (cond == IrCondition::Less)
|
||||
callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaV_lessthan)]);
|
||||
else if (cond == IrCondition::Equal)
|
||||
callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaV_equalval)]);
|
||||
else
|
||||
LUAU_ASSERT(!"Unsupported condition");
|
||||
|
||||
emitUpdateBase(build);
|
||||
|
||||
inst.regX64 = regs.takeReg(eax, index);
|
||||
break;
|
||||
}
|
||||
case IrCmd::JUMP:
|
||||
if (inst.a.kind == IrOpKind::VmExit)
|
||||
{
|
||||
@ -589,10 +612,28 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
break;
|
||||
}
|
||||
case IrCmd::JUMP_EQ_INT:
|
||||
if (intOp(inst.b) == 0)
|
||||
{
|
||||
build.test(regOp(inst.a), regOp(inst.a));
|
||||
|
||||
if (isFallthroughBlock(blockOp(inst.c), next))
|
||||
{
|
||||
build.jcc(ConditionX64::NotZero, labelOp(inst.d));
|
||||
jumpOrFallthrough(blockOp(inst.c), next);
|
||||
}
|
||||
else
|
||||
{
|
||||
build.jcc(ConditionX64::Zero, labelOp(inst.c));
|
||||
jumpOrFallthrough(blockOp(inst.d), next);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
build.cmp(regOp(inst.a), intOp(inst.b));
|
||||
|
||||
build.jcc(ConditionX64::Equal, labelOp(inst.c));
|
||||
jumpOrFallthrough(blockOp(inst.d), next);
|
||||
}
|
||||
break;
|
||||
case IrCmd::JUMP_LT_INT:
|
||||
build.cmp(regOp(inst.a), intOp(inst.b));
|
||||
@ -623,10 +664,6 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
jumpOrFallthrough(blockOp(inst.e), next);
|
||||
break;
|
||||
}
|
||||
case IrCmd::JUMP_CMP_ANY:
|
||||
jumpOnAnyCmpFallback(regs, build, vmRegOp(inst.a), vmRegOp(inst.b), conditionOp(inst.c), labelOp(inst.d));
|
||||
jumpOrFallthrough(blockOp(inst.e), next);
|
||||
break;
|
||||
case IrCmd::TABLE_LEN:
|
||||
{
|
||||
IrCallWrapperX64 callWrap(regs, build, index);
|
||||
@ -944,6 +981,32 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
jumpOrAbortOnUndef(ConditionX64::NotEqual, ConditionX64::Equal, inst.c, continueInVm);
|
||||
break;
|
||||
}
|
||||
case IrCmd::CHECK_TRUTHY:
|
||||
{
|
||||
// Constant tags which don't require boolean value check should've been removed in constant folding
|
||||
LUAU_ASSERT(inst.a.kind != IrOpKind::Constant || tagOp(inst.a) == LUA_TBOOLEAN);
|
||||
|
||||
Label skip;
|
||||
|
||||
if (inst.a.kind != IrOpKind::Constant)
|
||||
{
|
||||
// Fail to fallback on 'nil' (falsy)
|
||||
build.cmp(memRegTagOp(inst.a), LUA_TNIL);
|
||||
jumpOrAbortOnUndef(ConditionX64::Equal, ConditionX64::NotEqual, inst.c);
|
||||
|
||||
// Skip value test if it's not a boolean (truthy)
|
||||
build.cmp(memRegTagOp(inst.a), LUA_TBOOLEAN);
|
||||
build.jcc(ConditionX64::NotEqual, skip);
|
||||
}
|
||||
|
||||
// fail to fallback on 'false' boolean value (falsy)
|
||||
build.cmp(memRegUintOp(inst.b), 0);
|
||||
jumpOrAbortOnUndef(ConditionX64::Equal, ConditionX64::NotEqual, inst.c);
|
||||
|
||||
if (inst.a.kind != IrOpKind::Constant)
|
||||
build.setLabel(skip);
|
||||
break;
|
||||
}
|
||||
case IrCmd::CHECK_READONLY:
|
||||
build.cmp(byte[regOp(inst.a) + offsetof(Table, readonly)], 0);
|
||||
jumpOrAbortOnUndef(ConditionX64::NotEqual, ConditionX64::Equal, inst.b);
|
||||
@ -1231,7 +1294,30 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
LUAU_ASSERT(inst.b.kind == IrOpKind::VmReg);
|
||||
LUAU_ASSERT(inst.c.kind == IrOpKind::Constant);
|
||||
|
||||
emitFallback(regs, build, offsetof(NativeContext, executeGETVARARGS), uintOp(inst.a));
|
||||
if (intOp(inst.c) == LUA_MULTRET)
|
||||
{
|
||||
IrCallWrapperX64 callWrap(regs, build);
|
||||
callWrap.addArgument(SizeX64::qword, rState);
|
||||
|
||||
RegisterX64 reg = callWrap.suggestNextArgumentRegister(SizeX64::qword);
|
||||
build.mov(reg, sCode);
|
||||
callWrap.addArgument(SizeX64::qword, addr[reg + uintOp(inst.a) * sizeof(Instruction)]);
|
||||
|
||||
callWrap.addArgument(SizeX64::qword, rBase);
|
||||
callWrap.addArgument(SizeX64::dword, vmRegOp(inst.b));
|
||||
callWrap.call(qword[rNativeContext + offsetof(NativeContext, executeGETVARARGSMultRet)]);
|
||||
|
||||
emitUpdateBase(build);
|
||||
}
|
||||
else
|
||||
{
|
||||
IrCallWrapperX64 callWrap(regs, build);
|
||||
callWrap.addArgument(SizeX64::qword, rState);
|
||||
callWrap.addArgument(SizeX64::qword, rBase);
|
||||
callWrap.addArgument(SizeX64::dword, vmRegOp(inst.b));
|
||||
callWrap.addArgument(SizeX64::dword, intOp(inst.c));
|
||||
callWrap.call(qword[rNativeContext + offsetof(NativeContext, executeGETVARARGSConst)]);
|
||||
}
|
||||
break;
|
||||
case IrCmd::NEWCLOSURE:
|
||||
{
|
||||
@ -1585,6 +1671,8 @@ OperandX64 IrLoweringX64::memRegUintOp(IrOp op)
|
||||
return regOp(op);
|
||||
case IrOpKind::Constant:
|
||||
return OperandX64(unsigned(intOp(op)));
|
||||
case IrOpKind::VmReg:
|
||||
return luauRegValueInt(vmRegOp(op));
|
||||
default:
|
||||
LUAU_ASSERT(!"Unsupported operand kind");
|
||||
}
|
||||
|
@ -137,16 +137,17 @@ static BuiltinImplResult translateBuiltinNumberTo2Number(
|
||||
return {BuiltinImplType::Full, 2};
|
||||
}
|
||||
|
||||
static BuiltinImplResult translateBuiltinAssert(IrBuilder& build, int nparams, int ra, int arg, IrOp args, int nresults, IrOp fallback)
|
||||
static BuiltinImplResult translateBuiltinAssert(IrBuilder& build, int nparams, int ra, int arg, IrOp args, int nresults, int pcpos)
|
||||
{
|
||||
if (nparams < 1 || nresults != 0)
|
||||
return {BuiltinImplType::None, -1};
|
||||
|
||||
IrOp cont = build.block(IrBlockKind::Internal);
|
||||
IrOp tag = build.inst(IrCmd::LOAD_TAG, build.vmReg(arg));
|
||||
|
||||
// TODO: maybe adding a guard like CHECK_TRUTHY can be useful
|
||||
build.inst(IrCmd::JUMP_IF_FALSY, build.vmReg(arg), fallback, cont);
|
||||
build.beginBlock(cont);
|
||||
// We don't know if it's really a boolean at this point, but we will only check this value if it is
|
||||
IrOp value = build.inst(IrCmd::LOAD_INT, build.vmReg(arg));
|
||||
|
||||
build.inst(IrCmd::CHECK_TRUTHY, tag, value, build.vmExit(pcpos));
|
||||
|
||||
return {BuiltinImplType::UsesFallback, 0};
|
||||
}
|
||||
@ -463,8 +464,6 @@ static BuiltinImplResult translateBuiltinBit32Shift(
|
||||
if (nparams < 2 || nresults > 1)
|
||||
return {BuiltinImplType::None, -1};
|
||||
|
||||
IrOp block = build.block(IrBlockKind::Internal);
|
||||
|
||||
builtinCheckDouble(build, build.vmReg(arg), pcpos);
|
||||
builtinCheckDouble(build, args, pcpos);
|
||||
|
||||
@ -472,10 +471,22 @@ static BuiltinImplResult translateBuiltinBit32Shift(
|
||||
IrOp vb = builtinLoadDouble(build, args);
|
||||
|
||||
IrOp vaui = build.inst(IrCmd::NUM_TO_UINT, va);
|
||||
IrOp vbi = build.inst(IrCmd::NUM_TO_INT, vb);
|
||||
|
||||
IrOp vbi;
|
||||
|
||||
if (std::optional<double> vbd = build.function.asDoubleOp(vb); vbd && *vbd >= INT_MIN && *vbd <= INT_MAX)
|
||||
vbi = build.constInt(int(*vbd));
|
||||
else
|
||||
vbi = build.inst(IrCmd::NUM_TO_INT, vb);
|
||||
|
||||
bool knownGoodShift = unsigned(build.function.asIntOp(vbi).value_or(-1)) < 32u;
|
||||
|
||||
if (!knownGoodShift)
|
||||
{
|
||||
IrOp block = build.block(IrBlockKind::Internal);
|
||||
build.inst(IrCmd::JUMP_GE_UINT, vbi, build.constInt(32), fallback, block);
|
||||
build.beginBlock(block);
|
||||
}
|
||||
|
||||
IrCmd cmd = IrCmd::NOP;
|
||||
if (bfid == LBF_BIT32_LSHIFT)
|
||||
@ -763,7 +774,7 @@ BuiltinImplResult translateBuiltin(IrBuilder& build, int bfid, int ra, int arg,
|
||||
switch (bfid)
|
||||
{
|
||||
case LBF_ASSERT:
|
||||
return translateBuiltinAssert(build, nparams, ra, arg, args, nresults, fallback);
|
||||
return translateBuiltinAssert(build, nparams, ra, arg, args, nresults, pcpos);
|
||||
case LBF_MATH_DEG:
|
||||
return translateBuiltinMathDeg(build, nparams, ra, arg, args, nresults, pcpos);
|
||||
case LBF_MATH_RAD:
|
||||
|
@ -167,7 +167,9 @@ void translateInstJumpIfEq(IrBuilder& build, const Instruction* pc, int pcpos, b
|
||||
|
||||
build.beginBlock(fallback);
|
||||
build.inst(IrCmd::SET_SAVEDPC, build.constUint(pcpos + 1));
|
||||
build.inst(IrCmd::JUMP_CMP_ANY, build.vmReg(ra), build.vmReg(rb), build.cond(not_ ? IrCondition::NotEqual : IrCondition::Equal), target, next);
|
||||
|
||||
IrOp result = build.inst(IrCmd::CMP_ANY, build.vmReg(ra), build.vmReg(rb), build.cond(IrCondition::Equal));
|
||||
build.inst(IrCmd::JUMP_EQ_INT, result, build.constInt(0), not_ ? target : next, not_ ? next : target);
|
||||
|
||||
build.beginBlock(next);
|
||||
}
|
||||
@ -195,7 +197,27 @@ void translateInstJumpIfCond(IrBuilder& build, const Instruction* pc, int pcpos,
|
||||
|
||||
build.beginBlock(fallback);
|
||||
build.inst(IrCmd::SET_SAVEDPC, build.constUint(pcpos + 1));
|
||||
build.inst(IrCmd::JUMP_CMP_ANY, build.vmReg(ra), build.vmReg(rb), build.cond(cond), target, next);
|
||||
|
||||
bool reverse = false;
|
||||
|
||||
if (cond == IrCondition::NotLessEqual)
|
||||
{
|
||||
reverse = true;
|
||||
cond = IrCondition::LessEqual;
|
||||
}
|
||||
else if (cond == IrCondition::NotLess)
|
||||
{
|
||||
reverse = true;
|
||||
cond = IrCondition::Less;
|
||||
}
|
||||
else if (cond == IrCondition::NotEqual)
|
||||
{
|
||||
reverse = true;
|
||||
cond = IrCondition::Equal;
|
||||
}
|
||||
|
||||
IrOp result = build.inst(IrCmd::CMP_ANY, build.vmReg(ra), build.vmReg(rb), build.cond(cond));
|
||||
build.inst(IrCmd::JUMP_EQ_INT, result, build.constInt(0), reverse ? target : next, reverse ? next : target);
|
||||
|
||||
build.beginBlock(next);
|
||||
}
|
||||
|
@ -66,6 +66,7 @@ IrValueKind getCmdValueKind(IrCmd cmd)
|
||||
case IrCmd::ABS_NUM:
|
||||
return IrValueKind::Double;
|
||||
case IrCmd::NOT_ANY:
|
||||
case IrCmd::CMP_ANY:
|
||||
return IrValueKind::Int;
|
||||
case IrCmd::JUMP:
|
||||
case IrCmd::JUMP_IF_TRUTHY:
|
||||
@ -76,7 +77,6 @@ IrValueKind getCmdValueKind(IrCmd cmd)
|
||||
case IrCmd::JUMP_GE_UINT:
|
||||
case IrCmd::JUMP_EQ_POINTER:
|
||||
case IrCmd::JUMP_CMP_NUM:
|
||||
case IrCmd::JUMP_CMP_ANY:
|
||||
case IrCmd::JUMP_SLOT_MATCH:
|
||||
return IrValueKind::None;
|
||||
case IrCmd::TABLE_LEN:
|
||||
@ -114,6 +114,7 @@ IrValueKind getCmdValueKind(IrCmd cmd)
|
||||
case IrCmd::SET_UPVALUE:
|
||||
case IrCmd::PREPARE_FORN:
|
||||
case IrCmd::CHECK_TAG:
|
||||
case IrCmd::CHECK_TRUTHY:
|
||||
case IrCmd::CHECK_READONLY:
|
||||
case IrCmd::CHECK_NO_METATABLE:
|
||||
case IrCmd::CHECK_SAFE_ENV:
|
||||
@ -624,6 +625,29 @@ void foldConstants(IrBuilder& build, IrFunction& function, IrBlock& block, uint3
|
||||
replace(function, block, index, {IrCmd::JUMP, inst.c}); // Shows a conflict in assumptions on this path
|
||||
}
|
||||
break;
|
||||
case IrCmd::CHECK_TRUTHY:
|
||||
if (inst.a.kind == IrOpKind::Constant)
|
||||
{
|
||||
if (function.tagOp(inst.a) == LUA_TNIL)
|
||||
{
|
||||
replace(function, block, index, {IrCmd::JUMP, inst.c}); // Shows a conflict in assumptions on this path
|
||||
}
|
||||
else if (function.tagOp(inst.a) == LUA_TBOOLEAN)
|
||||
{
|
||||
if (inst.b.kind == IrOpKind::Constant)
|
||||
{
|
||||
if (function.intOp(inst.b) == 0)
|
||||
replace(function, block, index, {IrCmd::JUMP, inst.c}); // Shows a conflict in assumptions on this path
|
||||
else
|
||||
kill(function, inst);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
kill(function, inst);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case IrCmd::BITAND_UINT:
|
||||
if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant)
|
||||
{
|
||||
|
@ -90,9 +90,9 @@ void IrValueLocationTracking::beforeInstLowering(IrInst& inst)
|
||||
case IrCmd::LOAD_DOUBLE:
|
||||
case IrCmd::LOAD_INT:
|
||||
case IrCmd::LOAD_TVALUE:
|
||||
case IrCmd::CMP_ANY:
|
||||
case IrCmd::JUMP_IF_TRUTHY:
|
||||
case IrCmd::JUMP_IF_FALSY:
|
||||
case IrCmd::JUMP_CMP_ANY:
|
||||
case IrCmd::SET_TABLE:
|
||||
case IrCmd::SET_UPVALUE:
|
||||
case IrCmd::INTERRUPT:
|
||||
@ -114,6 +114,7 @@ void IrValueLocationTracking::beforeInstLowering(IrInst& inst)
|
||||
|
||||
// These instrucitons read VmReg only after optimizeMemoryOperandsX64
|
||||
case IrCmd::CHECK_TAG:
|
||||
case IrCmd::CHECK_TRUTHY:
|
||||
case IrCmd::ADD_NUM:
|
||||
case IrCmd::SUB_NUM:
|
||||
case IrCmd::MUL_NUM:
|
||||
|
@ -101,7 +101,8 @@ void initFunctions(NativeState& data)
|
||||
data.context.executeNEWCLOSURE = executeNEWCLOSURE;
|
||||
data.context.executeNAMECALL = executeNAMECALL;
|
||||
data.context.executeFORGPREP = executeFORGPREP;
|
||||
data.context.executeGETVARARGS = executeGETVARARGS;
|
||||
data.context.executeGETVARARGSMultRet = executeGETVARARGSMultRet;
|
||||
data.context.executeGETVARARGSConst = executeGETVARARGSConst;
|
||||
data.context.executeDUPCLOSURE = executeDUPCLOSURE;
|
||||
data.context.executePREPVARARGS = executePREPVARARGS;
|
||||
data.context.executeSETLIST = executeSETLIST;
|
||||
|
@ -98,7 +98,8 @@ struct NativeContext
|
||||
const Instruction* (*executeNAMECALL)(lua_State* L, const Instruction* pc, StkId base, TValue* k) = nullptr;
|
||||
const Instruction* (*executeSETLIST)(lua_State* L, const Instruction* pc, StkId base, TValue* k) = nullptr;
|
||||
const Instruction* (*executeFORGPREP)(lua_State* L, const Instruction* pc, StkId base, TValue* k) = nullptr;
|
||||
const Instruction* (*executeGETVARARGS)(lua_State* L, const Instruction* pc, StkId base, TValue* k) = nullptr;
|
||||
void (*executeGETVARARGSMultRet)(lua_State* L, const Instruction* pc, StkId base, int rai) = nullptr;
|
||||
void (*executeGETVARARGSConst)(lua_State* L, StkId base, int rai, int b) = nullptr;
|
||||
const Instruction* (*executeDUPCLOSURE)(lua_State* L, const Instruction* pc, StkId base, TValue* k) = nullptr;
|
||||
const Instruction* (*executePREPVARARGS)(lua_State* L, const Instruction* pc, StkId base, TValue* k) = nullptr;
|
||||
|
||||
|
@ -513,6 +513,15 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
|
||||
{
|
||||
state.invalidateValue(inst.a);
|
||||
state.forwardVmRegStoreToLoad(inst, IrCmd::LOAD_POINTER);
|
||||
|
||||
if (IrInst* instOp = function.asInstOp(inst.b); instOp && instOp->cmd == IrCmd::NEW_TABLE)
|
||||
{
|
||||
if (RegisterInfo* info = state.tryGetRegisterInfo(inst.a))
|
||||
{
|
||||
info->knownNotReadonly = true;
|
||||
info->knownNoMetatable = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case IrCmd::STORE_DOUBLE:
|
||||
@ -681,6 +690,9 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
|
||||
}
|
||||
break;
|
||||
}
|
||||
case IrCmd::CHECK_TRUTHY:
|
||||
// It is possible to check if current tag in state is truthy or not, but this case almost never comes up
|
||||
break;
|
||||
case IrCmd::CHECK_READONLY:
|
||||
if (RegisterInfo* info = state.tryGetRegisterInfo(inst.a))
|
||||
{
|
||||
@ -782,6 +794,9 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
|
||||
case IrCmd::NOT_ANY:
|
||||
state.substituteOrRecord(inst, index);
|
||||
break;
|
||||
case IrCmd::CMP_ANY:
|
||||
state.invalidateUserCall();
|
||||
break;
|
||||
case IrCmd::JUMP:
|
||||
case IrCmd::JUMP_EQ_POINTER:
|
||||
case IrCmd::JUMP_SLOT_MATCH:
|
||||
@ -840,9 +855,6 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
|
||||
case IrCmd::FINDUPVAL:
|
||||
break;
|
||||
|
||||
case IrCmd::JUMP_CMP_ANY:
|
||||
state.invalidateUserCall(); // TODO: if arguments are strings, there will be no user calls
|
||||
break;
|
||||
case IrCmd::DO_ARITH:
|
||||
state.invalidate(inst.a);
|
||||
state.invalidateUserCall();
|
||||
|
@ -35,6 +35,25 @@ static void optimizeMemoryOperandsX64(IrFunction& function, IrBlock& block)
|
||||
}
|
||||
break;
|
||||
}
|
||||
case IrCmd::CHECK_TRUTHY:
|
||||
{
|
||||
if (inst.a.kind == IrOpKind::Inst)
|
||||
{
|
||||
IrInst& tag = function.instOp(inst.a);
|
||||
|
||||
if (tag.useCount == 1 && tag.cmd == IrCmd::LOAD_TAG && (tag.a.kind == IrOpKind::VmReg || tag.a.kind == IrOpKind::VmConst))
|
||||
replace(function, inst.a, tag.a);
|
||||
}
|
||||
|
||||
if (inst.b.kind == IrOpKind::Inst)
|
||||
{
|
||||
IrInst& value = function.instOp(inst.b);
|
||||
|
||||
if (value.useCount == 1 && value.cmd == IrCmd::LOAD_INT)
|
||||
replace(function, inst.b, value.a);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case IrCmd::ADD_NUM:
|
||||
case IrCmd::SUB_NUM:
|
||||
case IrCmd::MUL_NUM:
|
||||
|
@ -12,7 +12,6 @@ inline bool isFlagExperimental(const char* flag)
|
||||
// or critical bugs that are found after the code has been submitted.
|
||||
static const char* const kList[] = {
|
||||
"LuauInstantiateInSubtyping", // requires some fixes to lua-apps code
|
||||
"LuauTypecheckTypeguards", // requires some fixes to lua-apps code (CLI-67030)
|
||||
"LuauTinyControlFlowAnalysis", // waiting for updates to packages depended by internal builtin plugins
|
||||
// makes sure we always have at least one entry
|
||||
nullptr,
|
||||
|
@ -10,7 +10,8 @@ namespace Luau
|
||||
namespace Compile
|
||||
{
|
||||
|
||||
const double kRadDeg = 3.14159265358979323846 / 180.0;
|
||||
const double kPi = 3.14159265358979323846;
|
||||
const double kRadDeg = kPi / 180.0;
|
||||
|
||||
static Constant cvar()
|
||||
{
|
||||
@ -460,5 +461,16 @@ Constant foldBuiltin(int bfid, const Constant* args, size_t count)
|
||||
return cvar();
|
||||
}
|
||||
|
||||
Constant foldBuiltinMath(AstName index)
|
||||
{
|
||||
if (index == "pi")
|
||||
return cnum(kPi);
|
||||
|
||||
if (index == "huge")
|
||||
return cnum(HUGE_VAL);
|
||||
|
||||
return cvar();
|
||||
}
|
||||
|
||||
} // namespace Compile
|
||||
} // namespace Luau
|
||||
|
@ -9,6 +9,7 @@ namespace Compile
|
||||
{
|
||||
|
||||
Constant foldBuiltin(int bfid, const Constant* args, size_t count);
|
||||
Constant foldBuiltinMath(AstName index);
|
||||
|
||||
} // namespace Compile
|
||||
} // namespace Luau
|
||||
|
@ -31,6 +31,8 @@ LUAU_FASTFLAGVARIABLE(LuauCompileNativeComment, false)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauCompileFixBuiltinArity, false)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauCompileFoldMathK, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
@ -661,7 +663,7 @@ struct Compiler
|
||||
inlineFrames.push_back({func, oldLocals, target, targetCount});
|
||||
|
||||
// fold constant values updated above into expressions in the function body
|
||||
foldConstants(constants, variables, locstants, builtinsFold, func->body);
|
||||
foldConstants(constants, variables, locstants, builtinsFold, builtinsFoldMathK, func->body);
|
||||
|
||||
bool usedFallthrough = false;
|
||||
|
||||
@ -702,7 +704,7 @@ struct Compiler
|
||||
if (Constant* var = locstants.find(func->args.data[i]))
|
||||
var->type = Constant::Type_Unknown;
|
||||
|
||||
foldConstants(constants, variables, locstants, builtinsFold, func->body);
|
||||
foldConstants(constants, variables, locstants, builtinsFold, builtinsFoldMathK, func->body);
|
||||
}
|
||||
|
||||
void compileExprCall(AstExprCall* expr, uint8_t target, uint8_t targetCount, bool targetTop = false, bool multRet = false)
|
||||
@ -2807,7 +2809,7 @@ struct Compiler
|
||||
locstants[var].type = Constant::Type_Number;
|
||||
locstants[var].valueNumber = from + iv * step;
|
||||
|
||||
foldConstants(constants, variables, locstants, builtinsFold, stat);
|
||||
foldConstants(constants, variables, locstants, builtinsFold, builtinsFoldMathK, stat);
|
||||
|
||||
size_t iterJumps = loopJumps.size();
|
||||
|
||||
@ -2835,7 +2837,7 @@ struct Compiler
|
||||
// clean up fold state in case we need to recompile - normally we compile the loop body once, but due to inlining we may need to do it again
|
||||
locstants[var].type = Constant::Type_Unknown;
|
||||
|
||||
foldConstants(constants, variables, locstants, builtinsFold, stat);
|
||||
foldConstants(constants, variables, locstants, builtinsFold, builtinsFoldMathK, stat);
|
||||
}
|
||||
|
||||
void compileStatFor(AstStatFor* stat)
|
||||
@ -3604,6 +3606,7 @@ struct Compiler
|
||||
{
|
||||
Compiler* self;
|
||||
std::vector<AstExprFunction*>& functions;
|
||||
bool hasTypes = false;
|
||||
|
||||
FunctionVisitor(Compiler* self, std::vector<AstExprFunction*>& functions)
|
||||
: self(self)
|
||||
@ -3617,6 +3620,10 @@ struct Compiler
|
||||
{
|
||||
node->body->visit(this);
|
||||
|
||||
if (FFlag::LuauCompileFunctionType)
|
||||
for (AstLocal* arg : node->args)
|
||||
hasTypes |= arg->annotation != nullptr;
|
||||
|
||||
// this makes sure all functions that are used when compiling this one have been already added to the vector
|
||||
functions.push_back(node);
|
||||
|
||||
@ -3824,6 +3831,7 @@ struct Compiler
|
||||
DenseHashMap<AstExprFunction*, std::string> typeMap;
|
||||
|
||||
const DenseHashMap<AstExprCall*, int>* builtinsFold = nullptr;
|
||||
bool builtinsFoldMathK = false;
|
||||
|
||||
unsigned int regTop = 0;
|
||||
unsigned int stackSize = 0;
|
||||
@ -3874,15 +3882,21 @@ void compileOrThrow(BytecodeBuilder& bytecode, const ParseResult& parseResult, c
|
||||
|
||||
// builtin folding is enabled on optimization level 2 since we can't deoptimize folding at runtime
|
||||
if (options.optimizationLevel >= 2)
|
||||
{
|
||||
compiler.builtinsFold = &compiler.builtins;
|
||||
|
||||
if (FFlag::LuauCompileFoldMathK)
|
||||
if (AstName math = names.get("math"); math.value && getGlobalState(compiler.globals, math) == Global::Default)
|
||||
compiler.builtinsFoldMathK = true;
|
||||
}
|
||||
|
||||
if (options.optimizationLevel >= 1)
|
||||
{
|
||||
// this pass tracks which calls are builtins and can be compiled more efficiently
|
||||
analyzeBuiltins(compiler.builtins, compiler.globals, compiler.variables, options, root);
|
||||
|
||||
// this pass analyzes constantness of expressions
|
||||
foldConstants(compiler.constants, compiler.variables, compiler.locstants, compiler.builtinsFold, root);
|
||||
foldConstants(compiler.constants, compiler.variables, compiler.locstants, compiler.builtinsFold, compiler.builtinsFoldMathK, root);
|
||||
|
||||
// this pass analyzes table assignments to estimate table shapes for initially empty tables
|
||||
predictTableShapes(compiler.tableShapes, root);
|
||||
@ -3895,17 +3909,16 @@ void compileOrThrow(BytecodeBuilder& bytecode, const ParseResult& parseResult, c
|
||||
root->visit(&fenvVisitor);
|
||||
}
|
||||
|
||||
if (FFlag::LuauCompileFunctionType)
|
||||
{
|
||||
buildTypeMap(compiler.typeMap, root, options.vectorType);
|
||||
}
|
||||
|
||||
// gathers all functions with the invariant that all function references are to functions earlier in the list
|
||||
// for example, function foo() return function() end end will result in two vector entries, [0] = anonymous and [1] = foo
|
||||
std::vector<AstExprFunction*> functions;
|
||||
Compiler::FunctionVisitor functionVisitor(&compiler, functions);
|
||||
root->visit(&functionVisitor);
|
||||
|
||||
// computes type information for all functions based on type annotations
|
||||
if (FFlag::LuauCompileFunctionType && functionVisitor.hasTypes)
|
||||
buildTypeMap(compiler.typeMap, root, options.vectorType);
|
||||
|
||||
for (AstExprFunction* expr : functions)
|
||||
compiler.compileFunction(expr, 0);
|
||||
|
||||
|
@ -197,17 +197,19 @@ struct ConstantVisitor : AstVisitor
|
||||
DenseHashMap<AstLocal*, Constant>& locals;
|
||||
|
||||
const DenseHashMap<AstExprCall*, int>* builtins;
|
||||
bool foldMathK = false;
|
||||
|
||||
bool wasEmpty = false;
|
||||
|
||||
std::vector<Constant> builtinArgs;
|
||||
|
||||
ConstantVisitor(DenseHashMap<AstExpr*, Constant>& constants, DenseHashMap<AstLocal*, Variable>& variables,
|
||||
DenseHashMap<AstLocal*, Constant>& locals, const DenseHashMap<AstExprCall*, int>* builtins)
|
||||
DenseHashMap<AstLocal*, Constant>& locals, const DenseHashMap<AstExprCall*, int>* builtins, bool foldMathK)
|
||||
: constants(constants)
|
||||
, variables(variables)
|
||||
, locals(locals)
|
||||
, builtins(builtins)
|
||||
, foldMathK(foldMathK)
|
||||
{
|
||||
// since we do a single pass over the tree, if the initial state was empty we don't need to clear out old entries
|
||||
wasEmpty = constants.empty() && locals.empty();
|
||||
@ -296,6 +298,14 @@ struct ConstantVisitor : AstVisitor
|
||||
else if (AstExprIndexName* expr = node->as<AstExprIndexName>())
|
||||
{
|
||||
analyze(expr->expr);
|
||||
|
||||
if (foldMathK)
|
||||
{
|
||||
if (AstExprGlobal* eg = expr->expr->as<AstExprGlobal>(); eg && eg->name == "math")
|
||||
{
|
||||
result = foldBuiltinMath(expr->index);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (AstExprIndexExpr* expr = node->as<AstExprIndexExpr>())
|
||||
{
|
||||
@ -437,9 +447,9 @@ struct ConstantVisitor : AstVisitor
|
||||
};
|
||||
|
||||
void foldConstants(DenseHashMap<AstExpr*, Constant>& constants, DenseHashMap<AstLocal*, Variable>& variables,
|
||||
DenseHashMap<AstLocal*, Constant>& locals, const DenseHashMap<AstExprCall*, int>* builtins, AstNode* root)
|
||||
DenseHashMap<AstLocal*, Constant>& locals, const DenseHashMap<AstExprCall*, int>* builtins, bool foldMathK, AstNode* root)
|
||||
{
|
||||
ConstantVisitor visitor{constants, variables, locals, builtins};
|
||||
ConstantVisitor visitor{constants, variables, locals, builtins, foldMathK};
|
||||
root->visit(&visitor);
|
||||
}
|
||||
|
||||
|
@ -43,7 +43,7 @@ struct Constant
|
||||
};
|
||||
|
||||
void foldConstants(DenseHashMap<AstExpr*, Constant>& constants, DenseHashMap<AstLocal*, Variable>& variables,
|
||||
DenseHashMap<AstLocal*, Constant>& locals, const DenseHashMap<AstExprCall*, int>* builtins, AstNode* root);
|
||||
DenseHashMap<AstLocal*, Constant>& locals, const DenseHashMap<AstExprCall*, int>* builtins, bool foldMathK, AstNode* root);
|
||||
|
||||
} // namespace Compile
|
||||
} // namespace Luau
|
||||
|
@ -194,6 +194,7 @@ target_sources(Luau.Analysis PRIVATE
|
||||
Analysis/include/Luau/TypeUtils.h
|
||||
Analysis/include/Luau/Unifiable.h
|
||||
Analysis/include/Luau/Unifier.h
|
||||
Analysis/include/Luau/Unifier2.h
|
||||
Analysis/include/Luau/UnifierSharedState.h
|
||||
Analysis/include/Luau/Variant.h
|
||||
Analysis/include/Luau/VisitType.h
|
||||
@ -246,6 +247,7 @@ target_sources(Luau.Analysis PRIVATE
|
||||
Analysis/src/TypeUtils.cpp
|
||||
Analysis/src/Unifiable.cpp
|
||||
Analysis/src/Unifier.cpp
|
||||
Analysis/src/Unifier2.cpp
|
||||
)
|
||||
|
||||
# Luau.VM Sources
|
||||
@ -424,6 +426,7 @@ if(TARGET Luau.UnitTest)
|
||||
tests/TypeInfer.unknownnever.test.cpp
|
||||
tests/TypePack.test.cpp
|
||||
tests/TypeVar.test.cpp
|
||||
tests/Unifier2.test.cpp
|
||||
tests/Variant.test.cpp
|
||||
tests/VisitType.test.cpp
|
||||
tests/InsertionOrderedMap.test.cpp
|
||||
|
@ -99,6 +99,7 @@ LUALIB_API char* luaL_extendbuffer(luaL_Buffer* B, size_t additionalsize, int bo
|
||||
LUALIB_API void luaL_reservebuffer(luaL_Buffer* B, size_t size, int boxloc);
|
||||
LUALIB_API void luaL_addlstring(luaL_Buffer* B, const char* s, size_t l, int boxloc);
|
||||
LUALIB_API void luaL_addvalue(luaL_Buffer* B);
|
||||
LUALIB_API void luaL_addvalueany(luaL_Buffer* B, int idx);
|
||||
LUALIB_API void luaL_pushresult(luaL_Buffer* B);
|
||||
LUALIB_API void luaL_pushresultsize(luaL_Buffer* B, size_t size);
|
||||
|
||||
|
@ -11,6 +11,8 @@
|
||||
|
||||
#include <string.h>
|
||||
|
||||
LUAU_FASTFLAG(LuauFasterInterp)
|
||||
|
||||
// convert a stack index to positive
|
||||
#define abs_index(L, i) ((i) > 0 || (i) <= LUA_REGISTRYINDEX ? (i) : lua_gettop(L) + (i) + 1)
|
||||
|
||||
@ -440,6 +442,52 @@ void luaL_addvalue(luaL_Buffer* B)
|
||||
}
|
||||
}
|
||||
|
||||
void luaL_addvalueany(luaL_Buffer* B, int idx)
|
||||
{
|
||||
lua_State* L = B->L;
|
||||
|
||||
switch (lua_type(L, idx))
|
||||
{
|
||||
case LUA_TNONE:
|
||||
{
|
||||
LUAU_ASSERT(!"expected value");
|
||||
break;
|
||||
}
|
||||
case LUA_TNIL:
|
||||
luaL_addstring(B, "nil");
|
||||
break;
|
||||
case LUA_TBOOLEAN:
|
||||
if (lua_toboolean(L, idx))
|
||||
luaL_addstring(B, "true");
|
||||
else
|
||||
luaL_addstring(B, "false");
|
||||
break;
|
||||
case LUA_TNUMBER:
|
||||
{
|
||||
double n = lua_tonumber(L, idx);
|
||||
char s[LUAI_MAXNUM2STR];
|
||||
char* e = luai_num2str(s, n);
|
||||
luaL_addlstring(B, s, e - s, -1);
|
||||
break;
|
||||
}
|
||||
case LUA_TSTRING:
|
||||
{
|
||||
size_t len;
|
||||
const char* s = lua_tolstring(L, idx, &len);
|
||||
luaL_addlstring(B, s, len, -1);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
size_t len;
|
||||
const char* s = luaL_tolstring(L, idx, &len);
|
||||
|
||||
luaL_addlstring(B, s, len, -2);
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void luaL_pushresult(luaL_Buffer* B)
|
||||
{
|
||||
lua_State* L = B->L;
|
||||
@ -475,14 +523,30 @@ void luaL_pushresultsize(luaL_Buffer* B, size_t size)
|
||||
const char* luaL_tolstring(lua_State* L, int idx, size_t* len)
|
||||
{
|
||||
if (luaL_callmeta(L, idx, "__tostring")) // is there a metafield?
|
||||
{
|
||||
if (FFlag::LuauFasterInterp)
|
||||
{
|
||||
const char* s = lua_tolstring(L, -1, len);
|
||||
if (!s)
|
||||
luaL_error(L, "'__tostring' must return a string");
|
||||
return s;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!lua_isstring(L, -1))
|
||||
luaL_error(L, "'__tostring' must return a string");
|
||||
return lua_tolstring(L, -1, len);
|
||||
}
|
||||
}
|
||||
|
||||
switch (lua_type(L, idx))
|
||||
{
|
||||
case LUA_TNIL:
|
||||
lua_pushliteral(L, "nil");
|
||||
break;
|
||||
case LUA_TBOOLEAN:
|
||||
lua_pushstring(L, (lua_toboolean(L, idx) ? "true" : "false"));
|
||||
break;
|
||||
case LUA_TNUMBER:
|
||||
{
|
||||
double n = lua_tonumber(L, idx);
|
||||
@ -491,15 +555,6 @@ const char* luaL_tolstring(lua_State* L, int idx, size_t* len)
|
||||
lua_pushlstring(L, s, e - s);
|
||||
break;
|
||||
}
|
||||
case LUA_TSTRING:
|
||||
lua_pushvalue(L, idx);
|
||||
break;
|
||||
case LUA_TBOOLEAN:
|
||||
lua_pushstring(L, (lua_toboolean(L, idx) ? "true" : "false"));
|
||||
break;
|
||||
case LUA_TNIL:
|
||||
lua_pushliteral(L, "nil");
|
||||
break;
|
||||
case LUA_TVECTOR:
|
||||
{
|
||||
const float* v = lua_tovector(L, idx);
|
||||
@ -518,6 +573,9 @@ const char* luaL_tolstring(lua_State* L, int idx, size_t* len)
|
||||
lua_pushlstring(L, s, e - s);
|
||||
break;
|
||||
}
|
||||
case LUA_TSTRING:
|
||||
lua_pushvalue(L, idx);
|
||||
break;
|
||||
default:
|
||||
{
|
||||
const void* ptr = lua_topointer(L, idx);
|
||||
|
@ -394,6 +394,15 @@ int luaG_getline(Proto* p, int pc)
|
||||
return p->abslineinfo[pc >> p->linegaplog2] + p->lineinfo[pc];
|
||||
}
|
||||
|
||||
int luaG_isnative(lua_State* L, int level)
|
||||
{
|
||||
if (unsigned(level) >= unsigned(L->ci - L->base_ci))
|
||||
return 0;
|
||||
|
||||
CallInfo* ci = L->ci - level;
|
||||
return (ci->flags & LUA_CALLINFO_NATIVE) != 0 ? 1 : 0;
|
||||
}
|
||||
|
||||
void lua_singlestep(lua_State* L, int enabled)
|
||||
{
|
||||
L->singlestep = bool(enabled);
|
||||
|
@ -29,3 +29,5 @@ LUAI_FUNC void luaG_breakpoint(lua_State* L, Proto* p, int line, bool enable);
|
||||
LUAI_FUNC bool luaG_onbreak(lua_State* L);
|
||||
|
||||
LUAI_FUNC int luaG_getline(Proto* p, int pc);
|
||||
|
||||
LUAI_FUNC int luaG_isnative(lua_State* L, int level);
|
||||
|
@ -648,16 +648,28 @@ static void enumtable(EnumContext* ctx, Table* h)
|
||||
|
||||
if (h->node != &luaH_dummynode)
|
||||
{
|
||||
bool weakkey = false;
|
||||
bool weakvalue = false;
|
||||
|
||||
if (const TValue* mode = gfasttm(ctx->L->global, h->metatable, TM_MODE))
|
||||
{
|
||||
if (ttisstring(mode))
|
||||
{
|
||||
weakkey = strchr(svalue(mode), 'k') != NULL;
|
||||
weakvalue = strchr(svalue(mode), 'v') != NULL;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < sizenode(h); ++i)
|
||||
{
|
||||
const LuaNode& n = h->node[i];
|
||||
|
||||
if (!ttisnil(&n.val) && (iscollectable(&n.key) || iscollectable(&n.val)))
|
||||
{
|
||||
if (iscollectable(&n.key))
|
||||
if (!weakkey && iscollectable(&n.key))
|
||||
enumedge(ctx, obj2gco(h), gcvalue(&n.key), "[key]");
|
||||
|
||||
if (iscollectable(&n.val))
|
||||
if (!weakvalue && iscollectable(&n.val))
|
||||
{
|
||||
if (ttisstring(&n.key))
|
||||
{
|
||||
@ -671,7 +683,9 @@ static void enumtable(EnumContext* ctx, Table* h)
|
||||
}
|
||||
else
|
||||
{
|
||||
enumedge(ctx, obj2gco(h), gcvalue(&n.val), NULL);
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "[%s]", getstr(ctx->L->global->ttname[n.key.tt]));
|
||||
enumedge(ctx, obj2gco(h), gcvalue(&n.val), buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -745,7 +759,14 @@ static void enumthread(EnumContext* ctx, lua_State* th)
|
||||
{
|
||||
Proto* p = tcl->l.p;
|
||||
|
||||
enumnode(ctx, obj2gco(th), getstr(p->source));
|
||||
char buf[LUA_IDSIZE];
|
||||
|
||||
if (p->source)
|
||||
snprintf(buf, sizeof(buf), "%s:%d %s", p->debugname ? getstr(p->debugname) : "", p->linedefined, getstr(p->source));
|
||||
else
|
||||
snprintf(buf, sizeof(buf), "%s:%d", p->debugname ? getstr(p->debugname) : "", p->linedefined);
|
||||
|
||||
enumnode(ctx, obj2gco(th), buf);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -8,6 +8,8 @@
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauFasterInterp, false)
|
||||
|
||||
// macro to `unsign' a character
|
||||
#define uchar(c) ((unsigned char)(c))
|
||||
|
||||
@ -966,6 +968,14 @@ static int str_format(lua_State* L)
|
||||
luaL_addchar(&b, *strfrmt++);
|
||||
else if (*++strfrmt == L_ESC)
|
||||
luaL_addchar(&b, *strfrmt++); // %%
|
||||
else if (FFlag::LuauFasterInterp && *strfrmt == '*')
|
||||
{
|
||||
strfrmt++;
|
||||
if (++arg > top)
|
||||
luaL_error(L, "missing argument #%d", arg);
|
||||
|
||||
luaL_addvalueany(&b, arg);
|
||||
}
|
||||
else
|
||||
{ // format item
|
||||
char form[MAX_FORMAT]; // to store the format (`%...')
|
||||
@ -1034,7 +1044,7 @@ static int str_format(lua_State* L)
|
||||
}
|
||||
case '*':
|
||||
{
|
||||
if (formatItemSize != 1)
|
||||
if (FFlag::LuauFasterInterp || formatItemSize != 1)
|
||||
luaL_error(L, "'%%*' does not take a form");
|
||||
|
||||
size_t length;
|
||||
|
44
bench/micro_tests/test_StringInterp.lua
Normal file
44
bench/micro_tests/test_StringInterp.lua
Normal file
@ -0,0 +1,44 @@
|
||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
||||
|
||||
bench.runCode(function()
|
||||
for j=1,1e6 do
|
||||
local _ = "j=" .. tostring(j)
|
||||
end
|
||||
end, "interp: tostring")
|
||||
|
||||
bench.runCode(function()
|
||||
for j=1,1e6 do
|
||||
local _ = "j=" .. j
|
||||
end
|
||||
end, "interp: concat")
|
||||
|
||||
bench.runCode(function()
|
||||
for j=1,1e6 do
|
||||
local _ = string.format("j=%f", j)
|
||||
end
|
||||
end, "interp: %f format")
|
||||
|
||||
bench.runCode(function()
|
||||
for j=1,1e6 do
|
||||
local _ = string.format("j=%d", j)
|
||||
end
|
||||
end, "interp: %d format")
|
||||
|
||||
bench.runCode(function()
|
||||
for j=1,1e6 do
|
||||
local _ = string.format("j=%*", j)
|
||||
end
|
||||
end, "interp: %* format")
|
||||
|
||||
bench.runCode(function()
|
||||
for j=1,1e6 do
|
||||
local _ = `j={j}`
|
||||
end
|
||||
end, "interp: interp number")
|
||||
|
||||
bench.runCode(function()
|
||||
local ok = "hello!"
|
||||
for j=1,1e6 do
|
||||
local _ = `j={ok}`
|
||||
end
|
||||
end, "interp: interp string")
|
@ -3595,7 +3595,7 @@ TEST_CASE_FIXTURE(ACFixture, "string_completion_outside_quotes")
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled_empty")
|
||||
{
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled", true};
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled1", true};
|
||||
|
||||
check(R"(
|
||||
local function foo(a: () -> ())
|
||||
@ -3618,7 +3618,7 @@ foo(@1)
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled_args")
|
||||
{
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled", true};
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled1", true};
|
||||
|
||||
check(R"(
|
||||
local function foo(a: (number, string) -> ())
|
||||
@ -3641,7 +3641,7 @@ foo(@1)
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled_args_single_return")
|
||||
{
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled", true};
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled1", true};
|
||||
|
||||
check(R"(
|
||||
local function foo(a: (number, string) -> (string))
|
||||
@ -3664,7 +3664,7 @@ foo(@1)
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled_args_multi_return")
|
||||
{
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled", true};
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled1", true};
|
||||
|
||||
check(R"(
|
||||
local function foo(a: (number, string) -> (string, number))
|
||||
@ -3687,7 +3687,7 @@ foo(@1)
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled__noargs_multi_return")
|
||||
{
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled", true};
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled1", true};
|
||||
|
||||
check(R"(
|
||||
local function foo(a: () -> (string, number))
|
||||
@ -3710,7 +3710,7 @@ foo(@1)
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled__varargs_multi_return")
|
||||
{
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled", true};
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled1", true};
|
||||
|
||||
check(R"(
|
||||
local function foo(a: (...number) -> (string, number))
|
||||
@ -3733,7 +3733,7 @@ foo(@1)
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled_multi_varargs_multi_return")
|
||||
{
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled", true};
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled1", true};
|
||||
|
||||
check(R"(
|
||||
local function foo(a: (string, ...number) -> (string, number))
|
||||
@ -3756,7 +3756,7 @@ foo(@1)
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled_multi_varargs_varargs_return")
|
||||
{
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled", true};
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled1", true};
|
||||
|
||||
check(R"(
|
||||
local function foo(a: (string, ...number) -> ...number)
|
||||
@ -3779,7 +3779,7 @@ foo(@1)
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled_multi_varargs_multi_varargs_return")
|
||||
{
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled", true};
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled1", true};
|
||||
|
||||
check(R"(
|
||||
local function foo(a: (string, ...number) -> (boolean, ...number))
|
||||
@ -3802,7 +3802,7 @@ foo(@1)
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled_named_args")
|
||||
{
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled", true};
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled1", true};
|
||||
|
||||
check(R"(
|
||||
local function foo(a: (foo: number, bar: string) -> (string, number))
|
||||
@ -3825,7 +3825,7 @@ foo(@1)
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled_partially_args")
|
||||
{
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled", true};
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled1", true};
|
||||
|
||||
check(R"(
|
||||
local function foo(a: (number, bar: string) -> (string, number))
|
||||
@ -3848,7 +3848,7 @@ foo(@1)
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled_partially_args_last")
|
||||
{
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled", true};
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled1", true};
|
||||
|
||||
check(R"(
|
||||
local function foo(a: (foo: number, string) -> (string, number))
|
||||
@ -3871,7 +3871,7 @@ foo(@1)
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled_typeof_args")
|
||||
{
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled", true};
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled1", true};
|
||||
|
||||
check(R"(
|
||||
local t = { a = 1, b = 2 }
|
||||
@ -3896,7 +3896,7 @@ foo(@1)
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled_table_literal_args")
|
||||
{
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled", true};
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled1", true};
|
||||
|
||||
check(R"(
|
||||
local function foo(a: (tbl: { x: number, y: number }) -> number) return a({x=2, y = 3}) end
|
||||
@ -3916,7 +3916,7 @@ foo(@1)
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled_typeof_returns")
|
||||
{
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled", true};
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled1", true};
|
||||
|
||||
check(R"(
|
||||
local t = { a = 1, b = 2 }
|
||||
@ -3941,7 +3941,7 @@ foo(@1)
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled_table_literal_args")
|
||||
{
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled", true};
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled1", true};
|
||||
|
||||
check(R"(
|
||||
local function foo(a: () -> { x: number, y: number }) return {x=2, y = 3} end
|
||||
@ -3961,7 +3961,7 @@ foo(@1)
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled_typeof_vararg")
|
||||
{
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled", true};
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled1", true};
|
||||
|
||||
check(R"(
|
||||
local t = { a = 1, b = 2 }
|
||||
@ -3986,7 +3986,7 @@ foo(@1)
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled_generic_type_pack_vararg")
|
||||
{
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled", true};
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled1", true};
|
||||
|
||||
check(R"(
|
||||
local function foo<A>(a: (...A) -> number, ...: A)
|
||||
@ -4009,7 +4009,7 @@ foo(@1)
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled_generic_on_argument_type_pack_vararg")
|
||||
{
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled", true};
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled1", true};
|
||||
|
||||
check(R"(
|
||||
local function foo(a: <T...>(...: T...) -> number)
|
||||
|
@ -7264,4 +7264,47 @@ end
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE("BuiltinFoldMathK")
|
||||
{
|
||||
ScopedFastFlag sff("LuauCompileFoldMathK", true);
|
||||
|
||||
// we can fold math.pi at optimization level 2
|
||||
CHECK_EQ("\n" + compileFunction(R"(
|
||||
function test()
|
||||
return math.pi * 2
|
||||
end
|
||||
)", 0, 2),
|
||||
R"(
|
||||
LOADK R0 K0 [6.2831853071795862]
|
||||
RETURN R0 1
|
||||
)");
|
||||
|
||||
// we don't do this at optimization level 1 because it may interfere with environment substitution
|
||||
CHECK_EQ("\n" + compileFunction(R"(
|
||||
function test()
|
||||
return math.pi * 2
|
||||
end
|
||||
)", 0, 1),
|
||||
R"(
|
||||
GETIMPORT R1 3 [math.pi]
|
||||
MULK R0 R1 K0 [2]
|
||||
RETURN R0 1
|
||||
)");
|
||||
|
||||
// we also don't do it if math global is assigned to
|
||||
CHECK_EQ("\n" + compileFunction(R"(
|
||||
function test()
|
||||
return math.pi * 2
|
||||
end
|
||||
|
||||
math = { pi = 4 }
|
||||
)", 0, 2),
|
||||
R"(
|
||||
GETGLOBAL R2 K1 ['math']
|
||||
GETTABLEKS R1 R2 K2 ['pi']
|
||||
MULK R0 R1 K0 [2]
|
||||
RETURN R0 1
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -1723,6 +1723,52 @@ TEST_CASE("Native")
|
||||
runConformance("native.lua");
|
||||
}
|
||||
|
||||
TEST_CASE("NativeTypeAnnotations")
|
||||
{
|
||||
ScopedFastFlag bytecodeVersion4("BytecodeVersion4", true);
|
||||
ScopedFastFlag luauCompileFunctionType("LuauCompileFunctionType", true);
|
||||
|
||||
// This tests requires code to run natively, otherwise all 'is_native' checks will fail
|
||||
if (!codegen || !luau_codegen_supported())
|
||||
return;
|
||||
|
||||
lua_CompileOptions copts = defaultOptions();
|
||||
copts.vectorCtor = "vector";
|
||||
copts.vectorType = "vector";
|
||||
|
||||
runConformance(
|
||||
"native_types.lua",
|
||||
[](lua_State* L) {
|
||||
// add is_native() function
|
||||
lua_pushcclosurek(
|
||||
L,
|
||||
[](lua_State* L) -> int {
|
||||
extern int luaG_isnative(lua_State * L, int level);
|
||||
|
||||
lua_pushboolean(L, luaG_isnative(L, 1));
|
||||
return 1;
|
||||
},
|
||||
"is_native", 0, nullptr);
|
||||
lua_setglobal(L, "is_native");
|
||||
|
||||
// for vector tests
|
||||
lua_pushcfunction(L, lua_vector, "vector");
|
||||
lua_setglobal(L, "vector");
|
||||
|
||||
#if LUA_VECTOR_SIZE == 4
|
||||
lua_pushvector(L, 0.0f, 0.0f, 0.0f, 0.0f);
|
||||
#else
|
||||
lua_pushvector(L, 0.0f, 0.0f, 0.0f);
|
||||
#endif
|
||||
luaL_newmetatable(L, "vector");
|
||||
|
||||
lua_setreadonly(L, -1, true);
|
||||
lua_setmetatable(L, -2);
|
||||
lua_pop(L, 1);
|
||||
},
|
||||
nullptr, nullptr, &copts);
|
||||
}
|
||||
|
||||
TEST_CASE("HugeFunction")
|
||||
{
|
||||
std::string source;
|
||||
|
@ -20,7 +20,7 @@ void ConstraintGraphBuilderFixture::generateConstraints(const std::string& code)
|
||||
{
|
||||
AstStatBlock* root = parse(code);
|
||||
dfg = std::make_unique<DataFlowGraph>(DataFlowGraphBuilder::build(root, NotNull{&ice}));
|
||||
cgb = std::make_unique<ConstraintGraphBuilder>(mainModule, &arena, NotNull(&moduleResolver), builtinTypes, NotNull(&ice),
|
||||
cgb = std::make_unique<ConstraintGraphBuilder>(mainModule, NotNull{&normalizer}, NotNull(&moduleResolver), builtinTypes, NotNull(&ice),
|
||||
frontend.globals.globalScope, /*prepareModuleScope*/ nullptr, &logger, NotNull{dfg.get()}, std::vector<RequireCycle>());
|
||||
cgb->visit(root);
|
||||
rootScope = cgb->rootScope;
|
||||
|
@ -5,8 +5,10 @@
|
||||
#include "Luau/Frontend.h"
|
||||
|
||||
#include "Fixture.h"
|
||||
#include "ClassFixture.h"
|
||||
|
||||
#include "Luau/Symbol.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "ScopedFlags.h"
|
||||
#include "doctest.h"
|
||||
#include <iostream>
|
||||
@ -128,6 +130,592 @@ TEST_CASE_FIXTURE(DifferFixture, "a_nested_table_wrong_match")
|
||||
"{ on: string } } } }, while the right type at almostFoo.inner.table.has.wrong.variant has type string");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(DifferFixture, "left_cyclic_table_right_table_missing_property")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function id<a>(x: a): a
|
||||
return x
|
||||
end
|
||||
|
||||
-- Remove name from cyclic table
|
||||
local foo = id({})
|
||||
foo.foo = foo
|
||||
local almostFoo = { x = 2 }
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
compareTypesNe("foo", "almostFoo",
|
||||
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol>.foo has type t1 where t1 = { foo: t1 }, while the right type at almostFoo is missing the property foo)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(DifferFixture, "left_cyclic_table_right_table_property_wrong")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function id<a>(x: a): a
|
||||
return x
|
||||
end
|
||||
|
||||
-- Remove name from cyclic table
|
||||
local foo = id({})
|
||||
foo.foo = foo
|
||||
local almostFoo = { foo = 2 }
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
compareTypesNe("foo", "almostFoo",
|
||||
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol>.foo has type t1 where t1 = { foo: t1 }, while the right type at almostFoo.foo has type number)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(DifferFixture, "right_cyclic_table_left_table_missing_property")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function id<a>(x: a): a
|
||||
return x
|
||||
end
|
||||
|
||||
-- Remove name from cyclic table
|
||||
local foo = id({})
|
||||
foo.foo = foo
|
||||
local almostFoo = { x = 2 }
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
compareTypesNe("almostFoo", "foo",
|
||||
R"(DiffError: these two types are not equal because the left type at almostFoo.x has type number, while the right type at <unlabeled-symbol> is missing the property x)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(DifferFixture, "right_cyclic_table_left_table_property_wrong")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function id<a>(x: a): a
|
||||
return x
|
||||
end
|
||||
|
||||
-- Remove name from cyclic table
|
||||
local foo = id({})
|
||||
foo.foo = foo
|
||||
local almostFoo = { foo = 2 }
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
compareTypesNe("almostFoo", "foo",
|
||||
R"(DiffError: these two types are not equal because the left type at almostFoo.foo has type number, while the right type at <unlabeled-symbol>.foo has type t1 where t1 = { foo: t1 })");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(DifferFixture, "equal_table_two_cyclic_tables_are_not_different")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function id<a>(x: a): a
|
||||
return x
|
||||
end
|
||||
|
||||
-- Remove name from cyclic table
|
||||
local foo = id({})
|
||||
foo.foo = foo
|
||||
local almostFoo = id({})
|
||||
almostFoo.foo = almostFoo
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
compareTypesEq("foo", "almostFoo");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(DifferFixture, "equal_table_two_shifted_circles_are_not_different")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function id<a>(x: a): a
|
||||
return x
|
||||
end
|
||||
|
||||
-- Remove name from cyclic table
|
||||
local foo = id({})
|
||||
foo.foo = id({})
|
||||
foo.foo.foo = id({})
|
||||
foo.foo.foo.foo = id({})
|
||||
foo.foo.foo.foo.foo = foo
|
||||
|
||||
local builder = id({})
|
||||
builder.foo = id({})
|
||||
builder.foo.foo = id({})
|
||||
builder.foo.foo.foo = id({})
|
||||
builder.foo.foo.foo.foo = builder
|
||||
-- Shift
|
||||
local almostFoo = builder.foo.foo
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
compareTypesEq("foo", "almostFoo");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(DifferFixture, "table_left_circle_right_measuring_tape")
|
||||
{
|
||||
// Left is a circle, right is a measuring tape
|
||||
CheckResult result = check(R"(
|
||||
local function id<a>(x: a): a
|
||||
return x
|
||||
end
|
||||
|
||||
-- Remove name from cyclic table
|
||||
local foo = id({})
|
||||
foo.foo = id({})
|
||||
foo.foo.foo = id({})
|
||||
foo.foo.foo.foo = id({})
|
||||
foo.foo.foo.bar = id({}) -- anchor to pin shape
|
||||
foo.foo.foo.foo.foo = foo
|
||||
local almostFoo = id({})
|
||||
almostFoo.foo = id({})
|
||||
almostFoo.foo.foo = id({})
|
||||
almostFoo.foo.foo.foo = id({})
|
||||
almostFoo.foo.foo.bar = id({}) -- anchor to pin shape
|
||||
almostFoo.foo.foo.foo.foo = almostFoo.foo
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
compareTypesNe("foo", "almostFoo",
|
||||
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol>.foo.foo.foo.foo.foo is missing the property bar, while the right type at <unlabeled-symbol>.foo.foo.foo.foo.foo.bar has type { })");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(DifferFixture, "equal_table_measuring_tapes")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function id<a>(x: a): a
|
||||
return x
|
||||
end
|
||||
|
||||
-- Remove name from cyclic table
|
||||
local foo = id({})
|
||||
foo.foo = id({})
|
||||
foo.foo.foo = id({})
|
||||
foo.foo.foo.foo = id({})
|
||||
foo.foo.foo.foo.foo = foo.foo
|
||||
local almostFoo = id({})
|
||||
almostFoo.foo = id({})
|
||||
almostFoo.foo.foo = id({})
|
||||
almostFoo.foo.foo.foo = id({})
|
||||
almostFoo.foo.foo.foo.foo = almostFoo.foo
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
compareTypesEq("foo", "almostFoo");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(DifferFixture, "equal_table_A_B_C")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function id<a>(x: a): a
|
||||
return x
|
||||
end
|
||||
|
||||
-- Remove name from cyclic table
|
||||
local foo = id({})
|
||||
foo.foo = id({})
|
||||
foo.foo.foo = id({})
|
||||
foo.foo.foo.foo = id({})
|
||||
foo.foo.foo.foo.foo = foo.foo
|
||||
local almostFoo = id({})
|
||||
almostFoo.foo = id({})
|
||||
almostFoo.foo.foo = id({})
|
||||
almostFoo.foo.foo.foo = id({})
|
||||
almostFoo.foo.foo.foo.foo = almostFoo.foo
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
compareTypesEq("foo", "almostFoo");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(DifferFixture, "equal_table_kind_A")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
-- Remove name from cyclic table
|
||||
local function id<a>(x: a): a
|
||||
return x
|
||||
end
|
||||
|
||||
local foo = id({})
|
||||
foo.left = id({})
|
||||
foo.right = id({})
|
||||
foo.left.left = id({})
|
||||
foo.left.right = id({})
|
||||
foo.right.left = id({})
|
||||
foo.right.right = id({})
|
||||
foo.right.left.left = id({})
|
||||
foo.right.left.right = id({})
|
||||
|
||||
foo.right.left.left.child = foo.right
|
||||
|
||||
local almostFoo = id({})
|
||||
almostFoo.left = id({})
|
||||
almostFoo.right = id({})
|
||||
almostFoo.left.left = id({})
|
||||
almostFoo.left.right = id({})
|
||||
almostFoo.right.left = id({})
|
||||
almostFoo.right.right = id({})
|
||||
almostFoo.right.left.left = id({})
|
||||
almostFoo.right.left.right = id({})
|
||||
|
||||
almostFoo.right.left.left.child = almostFoo.right
|
||||
|
||||
-- Bindings for requireType
|
||||
local fooLeft = foo.left
|
||||
local fooRight = foo.left.right
|
||||
local fooLeftLeft = foo.left.left
|
||||
local fooLeftRight = foo.left.right
|
||||
local fooRightLeft = foo.right.left
|
||||
local fooRightRight = foo.right.right
|
||||
local fooRightLeftLeft = foo.right.left.left
|
||||
local fooRightLeftRight = foo.right.left.right
|
||||
|
||||
local almostFooLeft = almostFoo.left
|
||||
local almostFooRight = almostFoo.left.right
|
||||
local almostFooLeftLeft = almostFoo.left.left
|
||||
local almostFooLeftRight = almostFoo.left.right
|
||||
local almostFooRightLeft = almostFoo.right.left
|
||||
local almostFooRightRight = almostFoo.right.right
|
||||
local almostFooRightLeftLeft = almostFoo.right.left.left
|
||||
local almostFooRightLeftRight = almostFoo.right.left.right
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
compareTypesEq("foo", "almostFoo");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(DifferFixture, "equal_table_kind_B")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
-- Remove name from cyclic table
|
||||
local function id<a>(x: a): a
|
||||
return x
|
||||
end
|
||||
|
||||
local foo = id({})
|
||||
foo.left = id({})
|
||||
foo.right = id({})
|
||||
foo.left.left = id({})
|
||||
foo.left.right = id({})
|
||||
foo.right.left = id({})
|
||||
foo.right.right = id({})
|
||||
foo.right.left.left = id({})
|
||||
foo.right.left.right = id({})
|
||||
|
||||
foo.right.left.left.child = foo.left
|
||||
|
||||
local almostFoo = id({})
|
||||
almostFoo.left = id({})
|
||||
almostFoo.right = id({})
|
||||
almostFoo.left.left = id({})
|
||||
almostFoo.left.right = id({})
|
||||
almostFoo.right.left = id({})
|
||||
almostFoo.right.right = id({})
|
||||
almostFoo.right.left.left = id({})
|
||||
almostFoo.right.left.right = id({})
|
||||
|
||||
almostFoo.right.left.left.child = almostFoo.left
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
compareTypesEq("foo", "almostFoo");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(DifferFixture, "equal_table_kind_C")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
-- Remove name from cyclic table
|
||||
local function id<a>(x: a): a
|
||||
return x
|
||||
end
|
||||
|
||||
local foo = id({})
|
||||
foo.left = id({})
|
||||
foo.right = id({})
|
||||
foo.left.left = id({})
|
||||
foo.left.right = id({})
|
||||
foo.right.left = id({})
|
||||
foo.right.right = id({})
|
||||
foo.right.left.left = id({})
|
||||
foo.right.left.right = id({})
|
||||
|
||||
foo.right.left.left.child = foo
|
||||
|
||||
local almostFoo = id({})
|
||||
almostFoo.left = id({})
|
||||
almostFoo.right = id({})
|
||||
almostFoo.left.left = id({})
|
||||
almostFoo.left.right = id({})
|
||||
almostFoo.right.left = id({})
|
||||
almostFoo.right.right = id({})
|
||||
almostFoo.right.left.left = id({})
|
||||
almostFoo.right.left.right = id({})
|
||||
|
||||
almostFoo.right.left.left.child = almostFoo
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
compareTypesEq("foo", "almostFoo");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(DifferFixture, "equal_table_kind_D")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
-- Remove name from cyclic table
|
||||
local function id<a>(x: a): a
|
||||
return x
|
||||
end
|
||||
|
||||
local foo = id({})
|
||||
foo.left = id({})
|
||||
foo.right = id({})
|
||||
foo.left.left = id({})
|
||||
foo.left.right = id({})
|
||||
foo.right.left = id({})
|
||||
foo.right.right = id({})
|
||||
foo.right.left.left = id({})
|
||||
foo.right.left.right = id({})
|
||||
|
||||
foo.right.left.left.child = foo.right.left.left
|
||||
|
||||
local almostFoo = id({})
|
||||
almostFoo.left = id({})
|
||||
almostFoo.right = id({})
|
||||
almostFoo.left.left = id({})
|
||||
almostFoo.left.right = id({})
|
||||
almostFoo.right.left = id({})
|
||||
almostFoo.right.right = id({})
|
||||
almostFoo.right.left.left = id({})
|
||||
almostFoo.right.left.right = id({})
|
||||
|
||||
almostFoo.right.left.left.child = almostFoo.right.left.left
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
compareTypesEq("foo", "almostFoo");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(DifferFixture, "equal_table_cyclic_diamonds_unraveled")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
-- Remove name from cyclic table
|
||||
local function id<a>(x: a): a
|
||||
return x
|
||||
end
|
||||
|
||||
-- Pattern 1
|
||||
local foo = id({})
|
||||
foo.child = id({})
|
||||
foo.child.left = id({})
|
||||
foo.child.right = id({})
|
||||
|
||||
foo.child.left.child = foo
|
||||
foo.child.right.child = foo
|
||||
|
||||
-- Pattern 2
|
||||
local almostFoo = id({})
|
||||
almostFoo.child = id({})
|
||||
almostFoo.child.left = id({})
|
||||
almostFoo.child.right = id({})
|
||||
|
||||
almostFoo.child.left.child = id({}) -- Use a new table
|
||||
almostFoo.child.right.child = almostFoo.child.left.child -- Refer to the same new table
|
||||
|
||||
almostFoo.child.left.child.child = id({})
|
||||
almostFoo.child.left.child.child.left = id({})
|
||||
almostFoo.child.left.child.child.right = id({})
|
||||
|
||||
almostFoo.child.left.child.child.left.child = almostFoo.child.left.child
|
||||
almostFoo.child.left.child.child.right.child = almostFoo.child.left.child
|
||||
|
||||
-- Pattern 3
|
||||
local anotherFoo = id({})
|
||||
anotherFoo.child = id({})
|
||||
anotherFoo.child.left = id({})
|
||||
anotherFoo.child.right = id({})
|
||||
|
||||
anotherFoo.child.left.child = id({}) -- Use a new table
|
||||
anotherFoo.child.right.child = id({}) -- Use another new table
|
||||
|
||||
anotherFoo.child.left.child.child = id({})
|
||||
anotherFoo.child.left.child.child.left = id({})
|
||||
anotherFoo.child.left.child.child.right = id({})
|
||||
anotherFoo.child.right.child.child = id({})
|
||||
anotherFoo.child.right.child.child.left = id({})
|
||||
anotherFoo.child.right.child.child.right = id({})
|
||||
|
||||
anotherFoo.child.left.child.child.left.child = anotherFoo.child.left.child
|
||||
anotherFoo.child.left.child.child.right.child = anotherFoo.child.left.child
|
||||
anotherFoo.child.right.child.child.left.child = anotherFoo.child.right.child
|
||||
anotherFoo.child.right.child.child.right.child = anotherFoo.child.right.child
|
||||
|
||||
-- Pattern 4
|
||||
local cleverFoo = id({})
|
||||
cleverFoo.child = id({})
|
||||
cleverFoo.child.left = id({})
|
||||
cleverFoo.child.right = id({})
|
||||
|
||||
cleverFoo.child.left.child = id({}) -- Use a new table
|
||||
cleverFoo.child.right.child = id({}) -- Use another new table
|
||||
|
||||
cleverFoo.child.left.child.child = id({})
|
||||
cleverFoo.child.left.child.child.left = id({})
|
||||
cleverFoo.child.left.child.child.right = id({})
|
||||
cleverFoo.child.right.child.child = id({})
|
||||
cleverFoo.child.right.child.child.left = id({})
|
||||
cleverFoo.child.right.child.child.right = id({})
|
||||
-- Same as pattern 3, but swapped here
|
||||
cleverFoo.child.left.child.child.left.child = cleverFoo.child.right.child -- Swap
|
||||
cleverFoo.child.left.child.child.right.child = cleverFoo.child.right.child
|
||||
cleverFoo.child.right.child.child.left.child = cleverFoo.child.left.child
|
||||
cleverFoo.child.right.child.child.right.child = cleverFoo.child.left.child
|
||||
|
||||
-- Pattern 5
|
||||
local cheekyFoo = id({})
|
||||
cheekyFoo.child = id({})
|
||||
cheekyFoo.child.left = id({})
|
||||
cheekyFoo.child.right = id({})
|
||||
|
||||
cheekyFoo.child.left.child = foo -- Use existing pattern
|
||||
cheekyFoo.child.right.child = foo -- Use existing pattern
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
std::vector<std::string> symbols{"foo", "almostFoo", "anotherFoo", "cleverFoo", "cheekyFoo"};
|
||||
|
||||
for (auto left : symbols)
|
||||
{
|
||||
for (auto right : symbols)
|
||||
{
|
||||
compareTypesEq(left, right);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(DifferFixture, "equal_function_cyclic")
|
||||
{
|
||||
// Old solver does not correctly infer function typepacks
|
||||
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function foo()
|
||||
return foo
|
||||
end
|
||||
function almostFoo()
|
||||
function bar()
|
||||
return bar
|
||||
end
|
||||
return bar
|
||||
end
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
compareTypesEq("foo", "almostFoo");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(DifferFixture, "equal_function_table_cyclic")
|
||||
{
|
||||
// Old solver does not correctly infer function typepacks
|
||||
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function foo()
|
||||
return {
|
||||
bar = foo
|
||||
}
|
||||
end
|
||||
function almostFoo()
|
||||
function bar()
|
||||
return {
|
||||
bar = bar
|
||||
}
|
||||
end
|
||||
return {
|
||||
bar = bar
|
||||
}
|
||||
end
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
compareTypesEq("foo", "almostFoo");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(DifferFixture, "function_table_self_referential_cyclic")
|
||||
{
|
||||
// Old solver does not correctly infer function typepacks
|
||||
// ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function foo()
|
||||
return {
|
||||
bar = foo
|
||||
}
|
||||
end
|
||||
function almostFoo()
|
||||
function bar()
|
||||
return bar
|
||||
end
|
||||
return {
|
||||
bar = bar
|
||||
}
|
||||
end
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
compareTypesNe("foo", "almostFoo",
|
||||
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol>.Ret[1].bar.Ret[1] has type t1 where t1 = {| bar: () -> t1 |}, while the right type at <unlabeled-symbol>.Ret[1].bar.Ret[1] has type t1 where t1 = () -> t1)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(DifferFixture, "equal_union_cyclic")
|
||||
{
|
||||
TypeArena arena;
|
||||
TypeId number = arena.addType(PrimitiveType{PrimitiveType::Number});
|
||||
TypeId string = arena.addType(PrimitiveType{PrimitiveType::String});
|
||||
|
||||
TypeId foo = arena.addType(UnionType{std::vector<TypeId>{number, string}});
|
||||
UnionType* unionFoo = getMutable<UnionType>(foo);
|
||||
unionFoo->options.push_back(foo);
|
||||
|
||||
TypeId almostFoo = arena.addType(UnionType{std::vector<TypeId>{number, string}});
|
||||
UnionType* unionAlmostFoo = getMutable<UnionType>(almostFoo);
|
||||
unionAlmostFoo->options.push_back(almostFoo);
|
||||
|
||||
compareEq(foo, almostFoo);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(DifferFixture, "equal_intersection_cyclic")
|
||||
{
|
||||
// Old solver does not correctly refine test types
|
||||
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function foo1(x: number)
|
||||
return x
|
||||
end
|
||||
function foo2(x: string)
|
||||
return 0
|
||||
end
|
||||
function bar1(x: number)
|
||||
return x
|
||||
end
|
||||
function bar2(x: string)
|
||||
return 0
|
||||
end
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
TypeId foo1 = requireType("foo1");
|
||||
TypeId foo2 = requireType("foo2");
|
||||
TypeId bar1 = requireType("bar1");
|
||||
TypeId bar2 = requireType("bar2");
|
||||
|
||||
TypeArena arena;
|
||||
|
||||
TypeId foo = arena.addType(IntersectionType{std::vector<TypeId>{foo1, foo2}});
|
||||
IntersectionType* intersectionFoo = getMutable<IntersectionType>(foo);
|
||||
intersectionFoo->parts.push_back(foo);
|
||||
|
||||
TypeId almostFoo = arena.addType(IntersectionType{std::vector<TypeId>{bar1, bar2}});
|
||||
IntersectionType* intersectionAlmostFoo = getMutable<IntersectionType>(almostFoo);
|
||||
intersectionAlmostFoo->parts.push_back(almostFoo);
|
||||
|
||||
compareEq(foo, almostFoo);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(DifferFixture, "singleton")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
@ -700,4 +1288,244 @@ TEST_CASE_FIXTURE(DifferFixture, "generic_three_or_three")
|
||||
R"(DiffError: these two types are not equal because the left generic at <unlabeled-symbol>.Arg[2] cannot be the same type parameter as the right generic at <unlabeled-symbol>.Arg[2])");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(DifferFixtureWithBuiltins, "equal_metatable")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local metaFoo = {
|
||||
metaBar = 5
|
||||
}
|
||||
local metaAlmostFoo = {
|
||||
metaBar = 1
|
||||
}
|
||||
local foo = {
|
||||
bar = 3
|
||||
}
|
||||
setmetatable(foo, metaFoo)
|
||||
local almostFoo = {
|
||||
bar = 4
|
||||
}
|
||||
setmetatable(almostFoo, metaAlmostFoo)
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
compareTypesEq("foo", "almostFoo");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(DifferFixtureWithBuiltins, "metatable_normal")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local metaFoo = {
|
||||
metaBar = 5
|
||||
}
|
||||
local metaAlmostFoo = {
|
||||
metaBar = 1
|
||||
}
|
||||
local foo = {
|
||||
bar = 3
|
||||
}
|
||||
setmetatable(foo, metaFoo)
|
||||
local almostFoo = {
|
||||
bar = "hello"
|
||||
}
|
||||
setmetatable(almostFoo, metaAlmostFoo)
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
compareTypesNe("foo", "almostFoo",
|
||||
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol>.bar has type number, while the right type at <unlabeled-symbol>.bar has type string)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(DifferFixtureWithBuiltins, "metatable_metanormal")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local metaFoo = {
|
||||
metaBar = "world"
|
||||
}
|
||||
local metaAlmostFoo = {
|
||||
metaBar = 1
|
||||
}
|
||||
local foo = {
|
||||
bar = "amazing"
|
||||
}
|
||||
setmetatable(foo, metaFoo)
|
||||
local almostFoo = {
|
||||
bar = "hello"
|
||||
}
|
||||
setmetatable(almostFoo, metaAlmostFoo)
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
compareTypesNe("foo", "almostFoo",
|
||||
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol>.__metatable.metaBar has type string, while the right type at <unlabeled-symbol>.__metatable.metaBar has type number)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(DifferFixtureWithBuiltins, "metatable_metamissing_left")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local metaFoo = {
|
||||
metaBar = "world"
|
||||
}
|
||||
local metaAlmostFoo = {
|
||||
metaBar = 1,
|
||||
thisIsOnlyInRight = 2,
|
||||
}
|
||||
local foo = {
|
||||
bar = "amazing"
|
||||
}
|
||||
setmetatable(foo, metaFoo)
|
||||
local almostFoo = {
|
||||
bar = "hello"
|
||||
}
|
||||
setmetatable(almostFoo, metaAlmostFoo)
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
compareTypesNe("foo", "almostFoo",
|
||||
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol>.__metatable is missing the property thisIsOnlyInRight, while the right type at <unlabeled-symbol>.__metatable.thisIsOnlyInRight has type number)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(DifferFixtureWithBuiltins, "metatable_metamissing_right")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local metaFoo = {
|
||||
metaBar = "world",
|
||||
thisIsOnlyInLeft = 2,
|
||||
}
|
||||
local metaAlmostFoo = {
|
||||
metaBar = 1,
|
||||
}
|
||||
local foo = {
|
||||
bar = "amazing"
|
||||
}
|
||||
setmetatable(foo, metaFoo)
|
||||
local almostFoo = {
|
||||
bar = "hello"
|
||||
}
|
||||
setmetatable(almostFoo, metaAlmostFoo)
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
compareTypesNe("foo", "almostFoo",
|
||||
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol>.__metatable.thisIsOnlyInLeft has type number, while the right type at <unlabeled-symbol>.__metatable is missing the property thisIsOnlyInLeft)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(DifferFixtureGeneric<ClassFixture>, "equal_class")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local foo = BaseClass
|
||||
local almostFoo = BaseClass
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
compareTypesEq("foo", "almostFoo");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(DifferFixtureGeneric<ClassFixture>, "class_normal")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local foo = BaseClass
|
||||
local almostFoo = ChildClass
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
compareTypesNe("foo", "almostFoo",
|
||||
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol> has type BaseClass, while the right type at <unlabeled-symbol> has type ChildClass)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(DifferFixture, "equal_generictp")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local foo: <T...>() -> T...
|
||||
local almostFoo: <U...>() -> U...
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
compareTypesEq("foo", "almostFoo");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(DifferFixture, "generictp_ne_fn")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local foo: <T, U...>(...T) -> U...
|
||||
local almostFoo: <U...>(U...) -> U...
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
compareTypesNe("foo", "almostFoo",
|
||||
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol> has type <T, U...>(...T) -> (U...), while the right type at <unlabeled-symbol> has type <U...>(U...) -> (U...))");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(DifferFixture, "generictp_normal")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
-- trN should be X... -> Y...
|
||||
-- s should be X -> Y...
|
||||
-- x should be X
|
||||
-- bij should be X... -> X...
|
||||
|
||||
-- Intended signature: <X..., Y..., Z>(X... -> Y..., Z -> X..., X... -> Y..., Z, Y... -> Y...) -> ()
|
||||
function foo(tr, s, tr2, x, bij)
|
||||
bij(bij(tr(s(x))))
|
||||
bij(bij(tr2(s(x))))
|
||||
end
|
||||
-- Intended signature: <X..., Y..., Z>(X... -> X..., Z -> X..., X... -> Y..., Z, Y... -> Y...) -> ()
|
||||
function almostFoo(bij, s, tr, x, bij2)
|
||||
bij(bij(s(x)))
|
||||
bij2(bij2(tr(s(x))))
|
||||
end
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
INFO(Luau::toString(requireType("foo")));
|
||||
INFO(Luau::toString(requireType("almostFoo")));
|
||||
|
||||
compareTypesNe("foo", "almostFoo",
|
||||
R"(DiffError: these two types are not equal because the left generic at <unlabeled-symbol>.Arg[1].Ret[Variadic] cannot be the same type parameter as the right generic at <unlabeled-symbol>.Arg[1].Ret[Variadic])");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(DifferFixture, "generictp_normal_2")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
-- trN should be X... -> Y...
|
||||
-- s should be X -> Y...
|
||||
-- x should be X
|
||||
-- bij should be X... -> X...
|
||||
|
||||
function foo(s, tr, tr2, x, bij)
|
||||
bij(bij(tr(s(x))))
|
||||
bij(bij(tr2(s(x))))
|
||||
end
|
||||
function almostFoo(s, bij, tr, x, bij2)
|
||||
bij2(bij2(bij(bij(tr(s(x))))))
|
||||
end
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
INFO(Luau::toString(requireType("foo")));
|
||||
INFO(Luau::toString(requireType("almostFoo")));
|
||||
|
||||
compareTypesNe("foo", "almostFoo",
|
||||
R"(DiffError: these two types are not equal because the left generic at <unlabeled-symbol>.Arg[2].Arg[Variadic] cannot be the same type parameter as the right generic at <unlabeled-symbol>.Arg[2].Arg[Variadic])");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(DifferFixture, "equal_generictp_cyclic")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
function foo(f, g, s, x)
|
||||
f(f(g(g(s(x)))))
|
||||
return foo
|
||||
end
|
||||
function almostFoo(f, g, s, x)
|
||||
g(g(f(f(s(x)))))
|
||||
return almostFoo
|
||||
end
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
INFO(Luau::toString(requireType("foo")));
|
||||
INFO(Luau::toString(requireType("almostFoo")));
|
||||
|
||||
compareTypesEq("foo", "almostFoo");
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -267,13 +267,16 @@ TEST_CASE_FIXTURE(Fixture, "clone_class")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "clone_free_types")
|
||||
{
|
||||
Type freeTy(FreeType{TypeLevel{}});
|
||||
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", false};
|
||||
|
||||
TypeArena arena;
|
||||
TypeId freeTy = freshType(NotNull{&arena}, builtinTypes, nullptr);
|
||||
TypePackVar freeTp(FreeTypePack{TypeLevel{}});
|
||||
|
||||
TypeArena dest;
|
||||
CloneState cloneState;
|
||||
|
||||
TypeId clonedTy = clone(&freeTy, dest, cloneState);
|
||||
TypeId clonedTy = clone(freeTy, dest, cloneState);
|
||||
CHECK(get<FreeType>(clonedTy));
|
||||
|
||||
cloneState = {};
|
||||
|
@ -1125,6 +1125,10 @@ until false
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "parse_nesting_based_end_detection_local_function")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{"DebugLuauDeferredConstraintResolution", false},
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
parse(R"(-- i am line 1
|
||||
@ -1157,6 +1161,10 @@ end
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "parse_nesting_based_end_detection_failsafe_earlier")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{"DebugLuauDeferredConstraintResolution", false},
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
parse(R"(-- i am line 1
|
||||
@ -2418,6 +2426,10 @@ TEST_CASE_FIXTURE(Fixture, "recovery_of_parenthesized_expressions")
|
||||
}
|
||||
};
|
||||
|
||||
ScopedFastFlag sff[] = {
|
||||
{"DebugLuauDeferredConstraintResolution", false},
|
||||
};
|
||||
|
||||
checkRecovery("function foo(a, b. c) return a + b end", "function foo(a, b) return a + b end", 1);
|
||||
checkRecovery("function foo(a, b: { a: number, b: number. c:number }) return a + b end",
|
||||
"function foo(a, b: { a: number, b: number }) return a + b end", 1);
|
||||
@ -2648,6 +2660,10 @@ TEST_CASE_FIXTURE(Fixture, "AstName_comparison")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "generic_type_list_recovery")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{"DebugLuauDeferredConstraintResolution", false},
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
parse(R"(
|
||||
|
@ -35,6 +35,10 @@ TEST_SUITE_BEGIN("RuntimeLimits");
|
||||
|
||||
TEST_CASE_FIXTURE(LimitFixture, "typescript_port_of_Result_type")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{"DebugLuauDeferredConstraintResolution", false},
|
||||
};
|
||||
|
||||
constexpr const char* src = R"LUA(
|
||||
--!strict
|
||||
|
||||
|
@ -42,7 +42,7 @@ struct SimplifyFixture : Fixture
|
||||
const TypeId truthyTy = builtinTypes->truthyType;
|
||||
const TypeId falsyTy = builtinTypes->falsyType;
|
||||
|
||||
const TypeId freeTy = arena->addType(FreeType{&scope});
|
||||
const TypeId freeTy = freshType(arena, builtinTypes, &scope);
|
||||
const TypeId genericTy = arena->addType(GenericType{});
|
||||
const TypeId blockedTy = arena->addType(BlockedType{});
|
||||
const TypeId pendingTy = arena->addType(PendingExpansionType{{}, {}, {}, {}});
|
||||
@ -60,6 +60,8 @@ struct SimplifyFixture : Fixture
|
||||
TypeId anotherChildClassTy = nullptr;
|
||||
TypeId unrelatedClassTy = nullptr;
|
||||
|
||||
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
|
||||
|
||||
SimplifyFixture()
|
||||
{
|
||||
createSomeClasses(&frontend);
|
||||
@ -176,8 +178,8 @@ TEST_CASE_FIXTURE(SimplifyFixture, "boolean_and_truthy_and_falsy")
|
||||
|
||||
TEST_CASE_FIXTURE(SimplifyFixture, "any_and_indeterminate_types")
|
||||
{
|
||||
CHECK("a" == intersectStr(anyTy, freeTy));
|
||||
CHECK("a" == intersectStr(freeTy, anyTy));
|
||||
CHECK("'a" == intersectStr(anyTy, freeTy));
|
||||
CHECK("'a" == intersectStr(freeTy, anyTy));
|
||||
|
||||
CHECK("b" == intersectStr(anyTy, genericTy));
|
||||
CHECK("b" == intersectStr(genericTy, anyTy));
|
||||
@ -191,17 +193,25 @@ TEST_CASE_FIXTURE(SimplifyFixture, "any_and_indeterminate_types")
|
||||
|
||||
TEST_CASE_FIXTURE(SimplifyFixture, "unknown_and_indeterminate_types")
|
||||
{
|
||||
CHECK(isIntersection(intersect(unknownTy, freeTy)));
|
||||
CHECK(isIntersection(intersect(freeTy, unknownTy)));
|
||||
CHECK(freeTy == intersect(unknownTy, freeTy));
|
||||
CHECK(freeTy == intersect(freeTy, unknownTy));
|
||||
|
||||
CHECK(isIntersection(intersect(unknownTy, genericTy)));
|
||||
CHECK(isIntersection(intersect(genericTy, unknownTy)));
|
||||
TypeId t = nullptr;
|
||||
|
||||
CHECK(isIntersection(intersect(unknownTy, blockedTy)));
|
||||
CHECK(isIntersection(intersect(blockedTy, unknownTy)));
|
||||
t = intersect(unknownTy, genericTy);
|
||||
CHECK_MESSAGE(isIntersection(t), "Should be an intersection but got " << t);
|
||||
t = intersect(genericTy, unknownTy);
|
||||
CHECK_MESSAGE(isIntersection(t), "Should be an intersection but got " << t);
|
||||
|
||||
CHECK(isIntersection(intersect(unknownTy, pendingTy)));
|
||||
CHECK(isIntersection(intersect(pendingTy, unknownTy)));
|
||||
t = intersect(unknownTy, blockedTy);
|
||||
CHECK_MESSAGE(isIntersection(t), "Should be an intersection but got " << t);
|
||||
t = intersect(blockedTy, unknownTy);
|
||||
CHECK_MESSAGE(isIntersection(t), "Should be an intersection but got " << t);
|
||||
|
||||
t = intersect(unknownTy, pendingTy);
|
||||
CHECK_MESSAGE(isIntersection(t), "Should be an intersection but got " << t);
|
||||
t = intersect(pendingTy, unknownTy);
|
||||
CHECK_MESSAGE(isIntersection(t), "Should be an intersection but got " << t);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(SimplifyFixture, "unknown_and_concrete")
|
||||
@ -225,8 +235,8 @@ TEST_CASE_FIXTURE(SimplifyFixture, "error_and_other_tops_and_bottom_types")
|
||||
|
||||
TEST_CASE_FIXTURE(SimplifyFixture, "error_and_indeterminate_types")
|
||||
{
|
||||
CHECK("*error-type* & a" == intersectStr(errorTy, freeTy));
|
||||
CHECK("*error-type* & a" == intersectStr(freeTy, errorTy));
|
||||
CHECK("'a & *error-type*" == intersectStr(errorTy, freeTy));
|
||||
CHECK("'a & *error-type*" == intersectStr(freeTy, errorTy));
|
||||
|
||||
CHECK("*error-type* & b" == intersectStr(errorTy, genericTy));
|
||||
CHECK("*error-type* & b" == intersectStr(genericTy, errorTy));
|
||||
@ -430,7 +440,7 @@ TEST_CASE_FIXTURE(SimplifyFixture, "curious_union")
|
||||
TypeId curious =
|
||||
arena->addType(UnionType{{arena->addType(IntersectionType{{freeTy, falseTy}}), arena->addType(IntersectionType{{freeTy, nilTy}})}});
|
||||
|
||||
CHECK("(a & false) | (a & nil) | number" == toString(union_(curious, numberTy)));
|
||||
CHECK("('a & false) | ('a & nil) | number" == toString(union_(curious, numberTy)));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(SimplifyFixture, "negations")
|
||||
@ -516,4 +526,13 @@ TEST_CASE_FIXTURE(SimplifyFixture, "simplify_stops_at_cycles")
|
||||
CHECK(t2 == intersect(anyTy, t2));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(SimplifyFixture, "free_type_bound_by_any_with_any")
|
||||
{
|
||||
CHECK(freeTy == intersect(freeTy, anyTy));
|
||||
CHECK(freeTy == intersect(anyTy, freeTy));
|
||||
|
||||
CHECK(freeTy == intersect(freeTy, anyTy));
|
||||
CHECK(freeTy == intersect(anyTy, freeTy));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -269,6 +269,10 @@ n3 [label="TableType 3"];
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "free")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{"DebugLuauDeferredConstraintResolution", false},
|
||||
};
|
||||
|
||||
Type type{TypeVariant{FreeType{TypeLevel{0, 0}}}};
|
||||
|
||||
ToDotOptions opts;
|
||||
|
@ -22,9 +22,9 @@ struct TxnLogFixture
|
||||
ScopePtr globalScope = std::make_shared<Scope>(builtinTypes.anyTypePack);
|
||||
ScopePtr childScope = std::make_shared<Scope>(globalScope);
|
||||
|
||||
TypeId a = arena.freshType(globalScope.get());
|
||||
TypeId b = arena.freshType(globalScope.get());
|
||||
TypeId c = arena.freshType(childScope.get());
|
||||
TypeId a = freshType(NotNull{&arena}, NotNull{&builtinTypes}, globalScope.get());
|
||||
TypeId b = freshType(NotNull{&arena}, NotNull{&builtinTypes}, globalScope.get());
|
||||
TypeId c = freshType(NotNull{&arena}, NotNull{&builtinTypes}, childScope.get());
|
||||
|
||||
TypeId g = arena.addType(GenericType{"G"});
|
||||
};
|
||||
@ -108,8 +108,8 @@ TEST_CASE_FIXTURE(TxnLogFixture, "colliding_coincident_logs_do_not_create_degene
|
||||
|
||||
log.commit();
|
||||
|
||||
CHECK("a" == toString(a));
|
||||
CHECK("a" == toString(b));
|
||||
CHECK("'a" == toString(a));
|
||||
CHECK("'a" == toString(b));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(TxnLogFixture, "replacing_persistent_types_is_allowed_but_makes_the_log_radioactive")
|
||||
|
@ -336,6 +336,10 @@ TEST_CASE_FIXTURE(Fixture, "stringify_type_alias_of_recursive_template_table_typ
|
||||
// Check that recursive intersection type doesn't generate an OOM
|
||||
TEST_CASE_FIXTURE(Fixture, "cli_38393_recursive_intersection_oom")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{"DebugLuauDeferredConstraintResolution", false},
|
||||
}; // FIXME
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function _(l0:(t0)&((t0)&(((t0)&((t0)->()))->(typeof(_),typeof(# _)))),l39,...):any
|
||||
end
|
||||
|
@ -1912,9 +1912,8 @@ end
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "dont_assert_when_the_tarjan_limit_is_exceeded_during_generalization")
|
||||
{
|
||||
ScopedFastInt sfi{"LuauTarjanChildLimit", 2};
|
||||
ScopedFastFlag sff[] = {
|
||||
{"DebugLuauDeferredConstraintResolution", true},
|
||||
};
|
||||
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||
return;
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function f(t)
|
||||
@ -2156,4 +2155,17 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "num_is_solved_after_num_or_str")
|
||||
CHECK_EQ("() -> number", toString(requireType("num_or_str")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "apply_of_lambda_with_inferred_and_explicit_types")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function apply(f, x) return f(x) end
|
||||
local x = apply(function(x: string): number return 5 end, "hello!")
|
||||
|
||||
local function apply_explicit<A, B...>(f: (A) -> B..., x: A): B... return f(x) end
|
||||
local x = apply_explicit(function(x: string): number return 5 end, "hello!")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -935,7 +935,7 @@ TEST_CASE_FIXTURE(Fixture, "instantiate_cyclic_generic_function")
|
||||
std::optional<Property> methodProp = get(argTable->props, "method");
|
||||
REQUIRE(bool(methodProp));
|
||||
|
||||
const FunctionType* methodFunction = get<FunctionType>(methodProp->type());
|
||||
const FunctionType* methodFunction = get<FunctionType>(follow(methodProp->type()));
|
||||
REQUIRE(methodFunction != nullptr);
|
||||
|
||||
std::optional<TypeId> methodArg = first(methodFunction->argTypes);
|
||||
|
@ -77,7 +77,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "iteration_regression_issue_69967")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "iteration_regression_issue_69967_alt")
|
||||
{
|
||||
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
|
||||
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||
return;
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type Iterable = typeof(setmetatable(
|
||||
{},
|
||||
@ -911,7 +913,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "dcr_xpath_candidates")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "dcr_iteration_on_never_gives_never")
|
||||
{
|
||||
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
|
||||
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||
return;
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local iter: never
|
||||
local ans
|
||||
|
@ -1,5 +1,6 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/TypeInfer.h"
|
||||
#include "Luau/RecursionCounter.h"
|
||||
|
||||
#include "Fixture.h"
|
||||
|
||||
@ -999,4 +1000,44 @@ end
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
// We would prefer this unification to be able to complete, but at least it should not crash
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "table_unification_infinite_recursion")
|
||||
{
|
||||
ScopedFastFlag luauTableUnifyRecursionLimit{"LuauTableUnifyRecursionLimit", true};
|
||||
|
||||
fileResolver.source["game/A"] = R"(
|
||||
local tbl = {}
|
||||
|
||||
function tbl:f1(state)
|
||||
self.someNonExistentvalue2 = state
|
||||
end
|
||||
|
||||
function tbl:f2()
|
||||
self.someNonExistentvalue:Dc()
|
||||
end
|
||||
|
||||
function tbl:f3()
|
||||
self:f2()
|
||||
self:f1(false)
|
||||
end
|
||||
return tbl
|
||||
)";
|
||||
|
||||
fileResolver.source["game/B"] = R"(
|
||||
local tbl = require(game.A)
|
||||
tbl:f3()
|
||||
)";
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
// TODO: DCR should transform RecursionLimitException into a CodeTooComplex error (currently it rethows it as InternalCompilerError)
|
||||
CHECK_THROWS_AS(frontend.check("game/B"), Luau::InternalCompilerError);
|
||||
}
|
||||
else
|
||||
{
|
||||
CheckResult result = frontend.check("game/B");
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -289,17 +289,26 @@ TEST_CASE_FIXTURE(Fixture, "type_assertion_expr_carry_its_constraints")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_in_if_condition_position")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
function f(s: any)
|
||||
CheckResult result1 = check(R"(
|
||||
function f(s: any, t: unknown)
|
||||
if type(s) == "number" then
|
||||
local n = s
|
||||
end
|
||||
if type(t) == "number" then
|
||||
local n = t
|
||||
end
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
LUAU_REQUIRE_NO_ERRORS(result1);
|
||||
|
||||
// DCR changes refinements to preserve error suppression.
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("*error-type* | number", toString(requireTypeAtPosition({3, 26})));
|
||||
else
|
||||
CHECK_EQ("number", toString(requireTypeAtPosition({3, 26})));
|
||||
CHECK_EQ("number", toString(requireTypeAtPosition({6, 26})));
|
||||
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_in_assert_position")
|
||||
@ -322,16 +331,28 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "call_an_incompatible_function_after_using_ty
|
||||
return x
|
||||
end
|
||||
|
||||
local function g(x: any)
|
||||
local function g(x: unknown)
|
||||
if type(x) == "string" then
|
||||
f(x)
|
||||
end
|
||||
end
|
||||
|
||||
local function h(x: any)
|
||||
if type(x) == "string" then
|
||||
f(x)
|
||||
end
|
||||
end
|
||||
)");
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
else
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
|
||||
CHECK_EQ("Type 'string' could not be converted into 'number'", toString(result.errors[0]));
|
||||
|
||||
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("Type 'string' could not be converted into 'number'", toString(result.errors[1]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "impossible_type_narrow_is_not_an_error")
|
||||
@ -785,16 +806,23 @@ TEST_CASE_FIXTURE(Fixture, "not_a_and_not_b2")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "either_number_or_string")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: any)
|
||||
local function f(x: any, y: unknown)
|
||||
if type(x) == "number" or type(x) == "string" then
|
||||
local foo = x
|
||||
end
|
||||
if type(y) == "number" or type(y) == "string" then
|
||||
local foo = y
|
||||
end
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("*error-type* | number | string", toString(requireTypeAtPosition({3, 28})));
|
||||
else
|
||||
CHECK_EQ("number | string", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("number | string", toString(requireTypeAtPosition({6, 28})));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "not_t_or_some_prop_of_t")
|
||||
@ -906,17 +934,32 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_comparison_ifelse_expression")
|
||||
function f(v:any)
|
||||
return if typeof(v) == "number" then v else returnOne(v)
|
||||
end
|
||||
|
||||
function g(v:unknown)
|
||||
return if typeof(v) == "number" then v else returnOne(v)
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("number", toString(requireTypeAtPosition({6, 49})));
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("~number", toString(requireTypeAtPosition({6, 66})));
|
||||
{
|
||||
CHECK_EQ("*error-type* | number", toString(requireTypeAtPosition({6, 49})));
|
||||
CHECK_EQ("*error-type* | ~number", toString(requireTypeAtPosition({6, 66})));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("number", toString(requireTypeAtPosition({6, 49})));
|
||||
CHECK_EQ("any", toString(requireTypeAtPosition({6, 66})));
|
||||
}
|
||||
|
||||
CHECK_EQ("number", toString(requireTypeAtPosition({10, 49})));
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("unknown & ~number", toString(requireTypeAtPosition({10, 66})));
|
||||
else
|
||||
CHECK_EQ("unknown", toString(requireTypeAtPosition({10, 66})));
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "is_truthy_constraint_while_expression")
|
||||
{
|
||||
@ -1862,4 +1905,20 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "refine_unknown_to_table")
|
||||
CHECK_EQ("unknown", toString(requireType("val")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "conditional_refinement_should_stay_error_suppressing")
|
||||
{
|
||||
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
|
||||
// this test is DCR-only as an instance of DCR fixing a bug in the old solver
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function test(element: any?)
|
||||
if element then
|
||||
local owner = element._owner
|
||||
end
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -78,10 +78,38 @@ TEST_CASE_FIXTURE(Fixture, "infer_locals_via_assignment_from_its_call_site")
|
||||
f("foo")
|
||||
)");
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK("number | string" == toString(requireType("a")));
|
||||
CHECK("(number | string) -> ()" == toString(requireType("f")));
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
CHECK_EQ("number", toString(requireType("a")));
|
||||
}
|
||||
}
|
||||
TEST_CASE_FIXTURE(Fixture, "interesting_local_type_inference_case")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{"DebugLuauDeferredConstraintResolution", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local a
|
||||
function f(x) a = x end
|
||||
f({x = 5})
|
||||
f({x = 5})
|
||||
)");
|
||||
|
||||
CHECK("{ x: number }" == toString(requireType("a")));
|
||||
CHECK("({ x: number }) -> ()" == toString(requireType("f")));
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "infer_in_nocheck_mode")
|
||||
{
|
||||
|
108
tests/Unifier2.test.cpp
Normal file
108
tests/Unifier2.test.cpp
Normal file
@ -0,0 +1,108 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
|
||||
#include "Luau/Scope.h"
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/TypeArena.h"
|
||||
#include "Luau/Unifier2.h"
|
||||
#include "Luau/Error.h"
|
||||
|
||||
#include "ScopedFlags.h"
|
||||
|
||||
#include "doctest.h"
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
struct Unifier2Fixture
|
||||
{
|
||||
TypeArena arena;
|
||||
BuiltinTypes builtinTypes;
|
||||
Scope scope{builtinTypes.anyTypePack};
|
||||
InternalErrorReporter iceReporter;
|
||||
Unifier2 u2{NotNull{&arena}, NotNull{&builtinTypes}, NotNull{&iceReporter}};
|
||||
ToStringOptions opts;
|
||||
|
||||
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
|
||||
|
||||
std::pair<TypeId, FreeType*> freshType()
|
||||
{
|
||||
FreeType ft{&scope, builtinTypes.neverType, builtinTypes.unknownType};
|
||||
|
||||
TypeId ty = arena.addType(ft);
|
||||
FreeType* ftv = getMutable<FreeType>(ty);
|
||||
REQUIRE(ftv != nullptr);
|
||||
|
||||
return {ty, ftv};
|
||||
}
|
||||
|
||||
std::string toString(TypeId ty)
|
||||
{
|
||||
return ::Luau::toString(ty, opts);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_SUITE_BEGIN("Unifier2");
|
||||
|
||||
TEST_CASE_FIXTURE(Unifier2Fixture, "T <: number")
|
||||
{
|
||||
auto [left, freeLeft] = freshType();
|
||||
|
||||
CHECK(u2.unify(left, builtinTypes.numberType));
|
||||
|
||||
CHECK("never" == toString(freeLeft->lowerBound));
|
||||
CHECK("number" == toString(freeLeft->upperBound));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Unifier2Fixture, "number <: T")
|
||||
{
|
||||
auto [right, freeRight] = freshType();
|
||||
|
||||
CHECK(u2.unify(builtinTypes.numberType, right));
|
||||
|
||||
CHECK("number" == toString(freeRight->lowerBound));
|
||||
CHECK("unknown" == toString(freeRight->upperBound));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Unifier2Fixture, "T <: U")
|
||||
{
|
||||
auto [left, freeLeft] = freshType();
|
||||
auto [right, freeRight] = freshType();
|
||||
|
||||
CHECK(u2.unify(left, right));
|
||||
|
||||
CHECK("t1 where t1 = ('a <: (t1 <: 'b))" == toString(left));
|
||||
CHECK("t1 where t1 = (('a <: t1) <: 'b)" == toString(right));
|
||||
|
||||
CHECK("never" == toString(freeLeft->lowerBound));
|
||||
CHECK("t1 where t1 = (('a <: t1) <: 'b)" == toString(freeLeft->upperBound));
|
||||
|
||||
CHECK("t1 where t1 = ('a <: (t1 <: 'b))" == toString(freeRight->lowerBound));
|
||||
CHECK("unknown" == toString(freeRight->upperBound));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Unifier2Fixture, "(string) -> () <: (X) -> Y...")
|
||||
{
|
||||
TypeId stringToUnit = arena.addType(FunctionType{
|
||||
arena.addTypePack({builtinTypes.stringType}),
|
||||
arena.addTypePack({})
|
||||
});
|
||||
|
||||
auto [x, xFree] = freshType();
|
||||
TypePackId y = arena.freshTypePack(&scope);
|
||||
|
||||
TypeId xToY = arena.addType(FunctionType{
|
||||
arena.addTypePack({x}),
|
||||
y
|
||||
});
|
||||
|
||||
u2.unify(stringToUnit, xToY);
|
||||
|
||||
CHECK("string" == toString(xFree->upperBound));
|
||||
|
||||
const TypePack* yPack = get<TypePack>(follow(y));
|
||||
REQUIRE(yPack != nullptr);
|
||||
|
||||
CHECK(0 == yPack->head.size());
|
||||
CHECK(!yPack->tail);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
72
tests/conformance/native_types.lua
Normal file
72
tests/conformance/native_types.lua
Normal file
@ -0,0 +1,72 @@
|
||||
-- This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
print("testing native code generation with type annotations")
|
||||
|
||||
function call(fn, ...)
|
||||
local ok, res = pcall(fn, ...)
|
||||
assert(ok)
|
||||
return res
|
||||
end
|
||||
|
||||
function ecall(fn, ...)
|
||||
local ok, err = pcall(fn, ...)
|
||||
assert(not ok)
|
||||
return err:sub(err:find(": ") + 2, #err)
|
||||
end
|
||||
|
||||
local function add(a: number, b: number, native: boolean)
|
||||
assert(native == is_native())
|
||||
return a + b
|
||||
end
|
||||
|
||||
call(add, 1, 3, true)
|
||||
ecall(add, nil, 2, false)
|
||||
|
||||
local function isnil(x: nil)
|
||||
assert(is_native())
|
||||
return not x
|
||||
end
|
||||
|
||||
call(isnil, nil)
|
||||
ecall(isnil, 2)
|
||||
|
||||
local function isany(x: any, y: number)
|
||||
assert(is_native())
|
||||
return not not x
|
||||
end
|
||||
|
||||
call(isany, nil, 1)
|
||||
call(isany, 2, 1)
|
||||
call(isany, {}, 1)
|
||||
|
||||
local function optstring(s: string?)
|
||||
assert(is_native())
|
||||
return if s then s..'2' else '3'
|
||||
end
|
||||
|
||||
assert(call(optstring, nil) == '3')
|
||||
assert(call(optstring, 'two: ') == 'two: 2')
|
||||
ecall(optstring, 2)
|
||||
|
||||
local function checktable(a: {x:number}) assert(is_native()) end
|
||||
local function checkfunction(a: () -> ()) assert(is_native()) end
|
||||
local function checkthread(a: thread) assert(is_native()) end
|
||||
local function checkuserdata(a: userdata) assert(is_native()) end
|
||||
local function checkvector(a: vector) assert(is_native()) end
|
||||
|
||||
call(checktable, {})
|
||||
ecall(checktable, 2)
|
||||
|
||||
call(checkfunction, function() end)
|
||||
ecall(checkfunction, 2)
|
||||
|
||||
call(checkthread, coroutine.create(function() end))
|
||||
ecall(checkthread, 2)
|
||||
|
||||
call(checkuserdata, newproxy())
|
||||
ecall(checkuserdata, 2)
|
||||
|
||||
call(checkvector, vector(1, 2, 3))
|
||||
ecall(checkvector, 2)
|
||||
|
||||
|
||||
return('OK')
|
@ -1,33 +1,73 @@
|
||||
AnnotationTests.infer_type_of_value_a_via_typeof_with_assignment
|
||||
AnnotationTests.two_type_params
|
||||
AstQuery.last_argument_function_call_type
|
||||
AutocompleteTest.anonymous_autofilled_generic_on_argument_type_pack_vararg
|
||||
AutocompleteTest.anonymous_autofilled_generic_type_pack_vararg
|
||||
AutocompleteTest.autocomplete_if_else_regression
|
||||
AutocompleteTest.autocomplete_interpolated_string_as_singleton
|
||||
AutocompleteTest.autocomplete_oop_implicit_self
|
||||
AutocompleteTest.autocomplete_response_perf1
|
||||
AutocompleteTest.autocomplete_string_singleton_escape
|
||||
AutocompleteTest.autocomplete_string_singletons
|
||||
AutocompleteTest.cyclic_table
|
||||
AutocompleteTest.suggest_external_module_type
|
||||
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_function_no_parenthesis
|
||||
AutocompleteTest.type_correct_function_return_types
|
||||
AutocompleteTest.type_correct_keywords
|
||||
AutocompleteTest.type_correct_suggestion_in_argument
|
||||
AutocompleteTest.unsealed_table_2
|
||||
BuiltinTests.aliased_string_format
|
||||
BuiltinTests.assert_removes_falsy_types
|
||||
BuiltinTests.assert_removes_falsy_types2
|
||||
BuiltinTests.assert_removes_falsy_types_even_from_type_pack_tail_but_only_for_the_first_type
|
||||
BuiltinTests.assert_returns_false_and_string_iff_it_knows_the_first_argument_cannot_be_truthy
|
||||
BuiltinTests.bad_select_should_not_crash
|
||||
BuiltinTests.next_iterator_should_infer_types_and_type_check
|
||||
BuiltinTests.select_slightly_out_of_range
|
||||
BuiltinTests.select_way_out_of_range
|
||||
BuiltinTests.set_metatable_needs_arguments
|
||||
BuiltinTests.setmetatable_should_not_mutate_persisted_types
|
||||
BuiltinTests.sort_with_bad_predicate
|
||||
BuiltinTests.string_format_arg_types_inference
|
||||
BuiltinTests.string_format_as_method
|
||||
BuiltinTests.string_format_correctly_ordered_types
|
||||
BuiltinTests.string_format_report_all_type_errors_at_correct_positions
|
||||
BuiltinTests.string_format_tostring_specifier_type_constraint
|
||||
BuiltinTests.string_format_use_correct_argument2
|
||||
BuiltinTests.table_dot_remove_optionally_returns_generic
|
||||
BuiltinTests.table_freeze_is_generic
|
||||
BuiltinTests.table_insert_correctly_infers_type_of_array_2_args_overload
|
||||
BuiltinTests.table_insert_correctly_infers_type_of_array_3_args_overload
|
||||
BuiltinTests.table_pack
|
||||
BuiltinTests.table_pack_reduce
|
||||
BuiltinTests.table_pack_variadic
|
||||
DefinitionTests.class_definition_indexer
|
||||
DefinitionTests.class_definition_overload_metamethods
|
||||
DefinitionTests.class_definition_string_props
|
||||
Differ.equal_generictp_cyclic
|
||||
Differ.equal_table_A_B_C
|
||||
Differ.equal_table_cyclic_diamonds_unraveled
|
||||
Differ.equal_table_kind_A
|
||||
Differ.equal_table_kind_B
|
||||
Differ.equal_table_kind_C
|
||||
Differ.equal_table_kind_D
|
||||
Differ.equal_table_measuring_tapes
|
||||
Differ.equal_table_two_shifted_circles_are_not_different
|
||||
Differ.function_table_self_referential_cyclic
|
||||
Differ.generictp_normal
|
||||
Differ.generictp_normal_2
|
||||
Differ.table_left_circle_right_measuring_tape
|
||||
GenericsTests.better_mismatch_error_messages
|
||||
GenericsTests.bidirectional_checking_and_generalization_play_nice
|
||||
GenericsTests.bound_tables_do_not_clone_original_fields
|
||||
GenericsTests.check_mutual_generic_functions
|
||||
GenericsTests.correctly_instantiate_polymorphic_member_functions
|
||||
GenericsTests.do_not_infer_generic_functions
|
||||
GenericsTests.dont_unify_bound_types
|
||||
GenericsTests.dont_substitute_bound_types
|
||||
GenericsTests.generic_argument_count_too_few
|
||||
GenericsTests.generic_argument_count_too_many
|
||||
GenericsTests.generic_functions_should_be_memory_safe
|
||||
@ -37,71 +77,125 @@ GenericsTests.infer_generic_function_function_argument_2
|
||||
GenericsTests.infer_generic_function_function_argument_3
|
||||
GenericsTests.infer_generic_function_function_argument_overloaded
|
||||
GenericsTests.infer_generic_lib_function_function_argument
|
||||
GenericsTests.infer_generic_property
|
||||
GenericsTests.instantiate_generic_function_in_assignments
|
||||
GenericsTests.instantiate_generic_function_in_assignments2
|
||||
GenericsTests.instantiated_function_argument_names
|
||||
GenericsTests.mutable_state_polymorphism
|
||||
GenericsTests.no_stack_overflow_from_quantifying
|
||||
GenericsTests.properties_can_be_polytypes
|
||||
GenericsTests.quantify_functions_even_if_they_have_an_explicit_generic
|
||||
GenericsTests.self_recursive_instantiated_param
|
||||
IntersectionTypes.intersection_of_tables_with_top_properties
|
||||
IntersectionTypes.less_greedy_unification_with_intersection_types
|
||||
IntersectionTypes.table_intersection_write_sealed_indirect
|
||||
IntersectionTypes.table_write_sealed_indirect
|
||||
Normalize.negations_of_tables
|
||||
Normalize.specific_functions_cannot_be_negated
|
||||
ParserTests.parse_nesting_based_end_detection
|
||||
ParserTests.parse_nesting_based_end_detection_single_line
|
||||
ProvisionalTests.assign_table_with_refined_property_with_a_similar_type_is_illegal
|
||||
ProvisionalTests.do_not_ice_when_trying_to_pick_first_of_generic_type_pack
|
||||
ProvisionalTests.error_on_eq_metamethod_returning_a_type_other_than_boolean
|
||||
ProvisionalTests.free_options_can_be_unified_together
|
||||
ProvisionalTests.free_options_cannot_be_unified_together
|
||||
ProvisionalTests.greedy_inference_with_shared_self_triggers_function_with_no_returns
|
||||
ProvisionalTests.luau-polyfill.Array.filter
|
||||
ProvisionalTests.setmetatable_constrains_free_type_into_free_table
|
||||
ProvisionalTests.specialization_binds_with_prototypes_too_early
|
||||
ProvisionalTests.table_insert_with_a_singleton_argument
|
||||
ProvisionalTests.table_unification_infinite_recursion
|
||||
ProvisionalTests.typeguard_inference_incomplete
|
||||
RefinementTest.call_an_incompatible_function_after_using_typeguard
|
||||
RefinementTest.dataflow_analysis_can_tell_refinements_when_its_appropriate_to_refine_into_nil_or_never
|
||||
RefinementTest.discriminate_from_truthiness_of_x
|
||||
RefinementTest.fail_to_refine_a_property_of_subscript_expression
|
||||
RefinementTest.falsiness_of_TruthyPredicate_narrows_into_nil
|
||||
RefinementTest.isa_type_refinement_must_be_known_ahead_of_time
|
||||
RefinementTest.narrow_property_of_a_bounded_variable
|
||||
RefinementTest.nonoptional_type_can_narrow_to_nil_if_sense_is_true
|
||||
RefinementTest.not_t_or_some_prop_of_t
|
||||
RefinementTest.refine_a_param_that_got_resolved_during_constraint_solving_stage_2
|
||||
RefinementTest.refine_a_property_of_some_global
|
||||
RefinementTest.truthy_constraint_on_properties
|
||||
RefinementTest.type_narrow_to_vector
|
||||
RefinementTest.typeguard_cast_free_table_to_vector
|
||||
RefinementTest.typeguard_in_assert_position
|
||||
RefinementTest.x_as_any_if_x_is_instance_elseif_x_is_table
|
||||
RuntimeLimits.typescript_port_of_Result_type
|
||||
TableTests.a_free_shape_can_turn_into_a_scalar_if_it_is_compatible
|
||||
TableTests.a_free_shape_cannot_turn_into_a_scalar_if_it_is_not_compatible
|
||||
TableTests.call_method
|
||||
TableTests.call_method_with_explicit_self_argument
|
||||
TableTests.cannot_augment_sealed_table
|
||||
TableTests.cannot_change_type_of_unsealed_table_prop
|
||||
TableTests.casting_sealed_tables_with_props_into_table_with_indexer
|
||||
TableTests.casting_tables_with_props_into_table_with_indexer4
|
||||
TableTests.casting_unsealed_tables_with_props_into_table_with_indexer
|
||||
TableTests.checked_prop_too_early
|
||||
TableTests.cyclic_shifted_tables
|
||||
TableTests.defining_a_method_for_a_local_sealed_table_must_fail
|
||||
TableTests.defining_a_self_method_for_a_local_sealed_table_must_fail
|
||||
TableTests.disallow_indexing_into_an_unsealed_table_with_no_indexer_in_strict_mode
|
||||
TableTests.dont_crash_when_setmetatable_does_not_produce_a_metatabletypevar
|
||||
TableTests.dont_extend_unsealed_tables_in_rvalue_position
|
||||
TableTests.dont_hang_when_trying_to_look_up_in_cyclic_metatable_index
|
||||
TableTests.dont_leak_free_table_props
|
||||
TableTests.dont_suggest_exact_match_keys
|
||||
TableTests.error_detailed_metatable_prop
|
||||
TableTests.explicitly_typed_table
|
||||
TableTests.explicitly_typed_table_with_indexer
|
||||
TableTests.fuzz_table_unify_instantiated_table
|
||||
TableTests.fuzz_table_unify_instantiated_table_with_prop_realloc
|
||||
TableTests.generalize_table_argument
|
||||
TableTests.generic_table_instantiation_potential_regression
|
||||
TableTests.give_up_after_one_metatable_index_look_up
|
||||
TableTests.indexer_on_sealed_table_must_unify_with_free_table
|
||||
TableTests.hide_table_error_properties
|
||||
TableTests.indexers_get_quantified_too
|
||||
TableTests.indexing_from_a_table_should_prefer_properties_when_possible
|
||||
TableTests.inequality_operators_imply_exactly_matching_types
|
||||
TableTests.infer_array_2
|
||||
TableTests.infer_indexer_from_value_property_in_literal
|
||||
TableTests.infer_type_when_indexing_from_a_table_indexer
|
||||
TableTests.inferred_properties_of_a_table_should_start_with_the_same_TypeLevel_of_that_table
|
||||
TableTests.inferred_return_type_of_free_table
|
||||
TableTests.instantiate_table_cloning_3
|
||||
TableTests.leaking_bad_metatable_errors
|
||||
TableTests.less_exponential_blowup_please
|
||||
TableTests.missing_metatable_for_sealed_tables_do_not_get_inferred
|
||||
TableTests.mixed_tables_with_implicit_numbered_keys
|
||||
TableTests.nil_assign_doesnt_hit_indexer
|
||||
TableTests.ok_to_set_nil_even_on_non_lvalue_base_expr
|
||||
TableTests.ok_to_provide_a_subtype_during_construction
|
||||
TableTests.okay_to_add_property_to_unsealed_tables_by_assignment
|
||||
TableTests.okay_to_add_property_to_unsealed_tables_by_function_call
|
||||
TableTests.only_ascribe_synthetic_names_at_module_scope
|
||||
TableTests.oop_indexer_works
|
||||
TableTests.oop_polymorphic
|
||||
TableTests.quantify_even_that_table_was_never_exported_at_all
|
||||
TableTests.quantify_metatables_of_metatables_of_table
|
||||
TableTests.quantifying_a_bound_var_works
|
||||
TableTests.reasonable_error_when_adding_a_nonexistent_property_to_an_array_like_table
|
||||
TableTests.recursive_metatable_type_call
|
||||
TableTests.right_table_missing_key2
|
||||
TableTests.shared_selfs
|
||||
TableTests.shared_selfs_from_free_param
|
||||
TableTests.shared_selfs_through_metatables
|
||||
TableTests.table_call_metamethod_basic
|
||||
TableTests.table_simple_call
|
||||
TableTests.table_subtyping_with_extra_props_dont_report_multiple_errors
|
||||
TableTests.table_subtyping_with_missing_props_dont_report_multiple_errors
|
||||
TableTests.table_subtyping_with_missing_props_dont_report_multiple_errors2
|
||||
TableTests.table_unification_4
|
||||
TableTests.type_mismatch_on_massive_table_is_cut_short
|
||||
TableTests.used_colon_instead_of_dot
|
||||
TableTests.used_dot_instead_of_colon
|
||||
TableTests.used_dot_instead_of_colon_but_correctly
|
||||
TableTests.when_augmenting_an_unsealed_table_with_an_indexer_apply_the_correct_scope_to_the_indexer_type
|
||||
TableTests.wrong_assign_does_hit_indexer
|
||||
ToDot.function
|
||||
ToString.exhaustive_toString_of_cyclic_table
|
||||
ToString.free_types
|
||||
ToString.named_metatable_toStringNamedFunction
|
||||
ToString.pick_distinct_names_for_mixed_explicit_and_implicit_generics
|
||||
ToString.toStringDetailed2
|
||||
ToString.toStringErrorPack
|
||||
ToString.toStringGenericPack
|
||||
ToString.toStringNamedFunction_generic_pack
|
||||
TryUnifyTests.members_of_failed_typepack_unification_are_unified_with_errorType
|
||||
TryUnifyTests.result_of_failed_typepack_unification_is_constrained
|
||||
@ -118,34 +212,52 @@ TypeAliases.type_alias_local_mutation
|
||||
TypeAliases.type_alias_local_rename
|
||||
TypeAliases.type_alias_locations
|
||||
TypeAliases.type_alias_of_an_imported_recursive_generic_type
|
||||
TypeFamilyTests.family_as_fn_arg
|
||||
TypeFamilyTests.table_internal_families
|
||||
TypeFamilyTests.unsolvable_family
|
||||
TypeInfer.bidirectional_checking_of_higher_order_function
|
||||
TypeInfer.check_type_infer_recursion_count
|
||||
TypeInfer.cli_39932_use_unifier_in_ensure_methods
|
||||
TypeInfer.cli_50041_committing_txnlog_in_apollo_client_error
|
||||
TypeInfer.dont_report_type_errors_within_an_AstExprError
|
||||
TypeInfer.dont_report_type_errors_within_an_AstStatError
|
||||
TypeInfer.follow_on_new_types_in_substitution
|
||||
TypeInfer.fuzz_free_table_type_change_during_index_check
|
||||
TypeInfer.infer_assignment_value_types_mutable_lval
|
||||
TypeInfer.infer_locals_via_assignment_from_its_call_site
|
||||
TypeInfer.no_stack_overflow_from_isoptional
|
||||
TypeInfer.recursive_function_that_invokes_itself_with_a_refinement_of_its_parameter
|
||||
TypeInfer.recursive_function_that_invokes_itself_with_a_refinement_of_its_parameter_2
|
||||
TypeInfer.tc_after_error_recovery_no_replacement_name_in_error
|
||||
TypeInfer.type_infer_cache_limit_normalizer
|
||||
TypeInfer.type_infer_recursion_limit_no_ice
|
||||
TypeInfer.type_infer_recursion_limit_normalizer
|
||||
TypeInferAnyError.can_subscript_any
|
||||
TypeInferAnyError.for_in_loop_iterator_is_any2
|
||||
TypeInferAnyError.for_in_loop_iterator_returns_any
|
||||
TypeInferAnyError.intersection_of_any_can_have_props
|
||||
TypeInferAnyError.replace_every_free_type_when_unifying_a_complex_function_with_any
|
||||
TypeInferAnyError.union_of_types_regression_test
|
||||
TypeInferClasses.can_read_prop_of_base_class_using_string
|
||||
TypeInferClasses.class_type_mismatch_with_name_conflict
|
||||
TypeInferClasses.index_instance_property
|
||||
TypeInferFunctions.cannot_hoist_interior_defns_into_signature
|
||||
TypeInferFunctions.dont_assert_when_the_tarjan_limit_is_exceeded_during_generalization
|
||||
TypeInferFunctions.dont_infer_parameter_types_for_functions_from_their_call_site
|
||||
TypeInferFunctions.function_cast_error_uses_correct_language
|
||||
TypeInferFunctions.function_decl_non_self_sealed_overwrite_2
|
||||
TypeInferFunctions.function_decl_non_self_unsealed_overwrite
|
||||
TypeInferFunctions.function_does_not_return_enough_values
|
||||
TypeInferFunctions.function_exprs_are_generalized_at_signature_scope_not_enclosing
|
||||
TypeInferFunctions.function_statement_sealed_table_assignment_through_indexer
|
||||
TypeInferFunctions.higher_order_function_2
|
||||
TypeInferFunctions.higher_order_function_4
|
||||
TypeInferFunctions.improved_function_arg_mismatch_errors
|
||||
TypeInferFunctions.infer_anonymous_function_arguments
|
||||
TypeInferFunctions.infer_anonymous_function_arguments_outside_call
|
||||
TypeInferFunctions.infer_that_function_does_not_return_a_table
|
||||
TypeInferFunctions.luau_subtyping_is_np_hard
|
||||
TypeInferFunctions.no_lossy_function_type
|
||||
TypeInferFunctions.occurs_check_failure_in_function_return_type
|
||||
TypeInferFunctions.report_exiting_without_return_strict
|
||||
TypeInferFunctions.return_type_by_overload
|
||||
TypeInferFunctions.too_few_arguments_variadic
|
||||
@ -154,33 +266,53 @@ TypeInferFunctions.too_few_arguments_variadic_generic2
|
||||
TypeInferFunctions.too_many_arguments_error_location
|
||||
TypeInferFunctions.too_many_return_values_in_parentheses
|
||||
TypeInferFunctions.too_many_return_values_no_function
|
||||
TypeInferLoops.dcr_iteration_explore_raycast_minimization
|
||||
TypeInferFunctions.toposort_doesnt_break_mutual_recursion
|
||||
TypeInferFunctions.vararg_function_is_quantified
|
||||
TypeInferLoops.cli_68448_iterators_need_not_accept_nil
|
||||
TypeInferLoops.dcr_iteration_on_never_gives_never
|
||||
TypeInferLoops.for_in_loop
|
||||
TypeInferLoops.for_in_loop_error_on_factory_not_returning_the_right_amount_of_values
|
||||
TypeInferLoops.for_in_loop_with_custom_iterator
|
||||
TypeInferLoops.for_in_loop_with_incompatible_args_to_iterator
|
||||
TypeInferLoops.for_in_loop_with_next
|
||||
TypeInferLoops.ipairs_produces_integral_indices
|
||||
TypeInferLoops.iteration_regression_issue_69967_alt
|
||||
TypeInferLoops.loop_iter_metamethod_nil
|
||||
TypeInferLoops.loop_iter_metamethod_ok_with_inference
|
||||
TypeInferLoops.loop_iter_trailing_nil
|
||||
TypeInferLoops.properly_infer_iteratee_is_a_free_table
|
||||
TypeInferLoops.unreachable_code_after_infinite_loop
|
||||
TypeInferModules.do_not_modify_imported_types_5
|
||||
TypeInferModules.module_type_conflict
|
||||
TypeInferModules.module_type_conflict_instantiated
|
||||
TypeInferOOP.cycle_between_object_constructor_and_alias
|
||||
TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_it_wont_help_2
|
||||
TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_not_defined_with_colon
|
||||
TypeInferOOP.inferring_hundreds_of_self_calls_should_not_suffocate_memory
|
||||
TypeInferOOP.methods_are_topologically_sorted
|
||||
TypeInferOperators.and_binexps_dont_unify
|
||||
TypeInferOperators.cli_38355_recursive_union
|
||||
TypeInferOperators.compound_assign_mismatch_metatable
|
||||
TypeInferOperators.concat_op_on_string_lhs_and_free_rhs
|
||||
TypeInferOperators.disallow_string_and_types_without_metatables_from_arithmetic_binary_ops
|
||||
TypeInferOperators.luau_polyfill_is_array
|
||||
TypeInferOperators.operator_eq_completely_incompatible
|
||||
TypeInferOperators.typecheck_overloaded_multiply_that_is_an_intersection
|
||||
TypeInferOperators.typecheck_overloaded_multiply_that_is_an_intersection_on_rhs
|
||||
TypeInferOperators.typecheck_unary_len_error
|
||||
TypeInferOperators.typecheck_unary_minus
|
||||
TypeInferOperators.typecheck_unary_minus_error
|
||||
TypeInferOperators.unrelated_classes_cannot_be_compared
|
||||
TypeInferOperators.unrelated_primitives_cannot_be_compared
|
||||
TypeInferPrimitives.CheckMethodsOfNumber
|
||||
TypeInferPrimitives.string_index
|
||||
TypeInferUnknownNever.length_of_never
|
||||
TypeInferUnknownNever.math_operators_and_never
|
||||
TypePackTests.detect_cyclic_typepacks2
|
||||
TypePackTests.higher_order_function
|
||||
TypePackTests.pack_tail_unification_check
|
||||
TypePackTests.type_alias_backwards_compatible
|
||||
TypePackTests.type_alias_default_type_errors
|
||||
TypeSingletons.function_args_infer_singletons
|
||||
TypeSingletons.function_call_with_singletons
|
||||
TypeSingletons.function_call_with_singletons_mismatch
|
||||
TypeSingletons.no_widening_from_callsites
|
||||
@ -192,4 +324,5 @@ TypeSingletons.widening_happens_almost_everywhere
|
||||
UnionTypes.dont_allow_cyclic_unions_to_be_inferred
|
||||
UnionTypes.generic_function_with_optional_arg
|
||||
UnionTypes.index_on_a_union_type_with_missing_property
|
||||
UnionTypes.less_greedy_unification_with_union_types
|
||||
UnionTypes.table_union_write_indirect
|
||||
|
@ -126,12 +126,6 @@ def main():
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.write and args.rwp:
|
||||
print_stderr(
|
||||
"Cannot run test_dcr.py with --write *and* --rwp. You don't want to commit local type inference faillist.txt yet."
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
failList = loadFailList()
|
||||
|
||||
flags = ["true", "DebugLuauDeferredConstraintResolution"]
|
||||
|
Loading…
Reference in New Issue
Block a user