mirror of
https://github.com/luau-lang/luau.git
synced 2024-12-12 21:10:37 +00:00
Sync to upstream/release/598
This commit is contained in:
parent
3bfc864280
commit
22e3d1fa46
72 changed files with 1946 additions and 732 deletions
|
@ -187,12 +187,12 @@ private:
|
||||||
* @param generalize If true, generalize any lambdas that are encountered.
|
* @param generalize If true, generalize any lambdas that are encountered.
|
||||||
* @return the type of the expression.
|
* @return the type of the expression.
|
||||||
*/
|
*/
|
||||||
Inference check(const ScopePtr& scope, AstExpr* expr, ValueContext context = ValueContext::RValue, std::optional<TypeId> expectedType = {},
|
Inference check(
|
||||||
bool forceSingleton = false, bool generalize = true);
|
const ScopePtr& scope, AstExpr* expr, std::optional<TypeId> expectedType = {}, bool forceSingleton = false, bool generalize = true);
|
||||||
|
|
||||||
Inference check(const ScopePtr& scope, AstExprConstantString* string, std::optional<TypeId> expectedType, bool forceSingleton);
|
Inference check(const ScopePtr& scope, AstExprConstantString* string, std::optional<TypeId> expectedType, bool forceSingleton);
|
||||||
Inference check(const ScopePtr& scope, AstExprConstantBool* bool_, std::optional<TypeId> expectedType, bool forceSingleton);
|
Inference check(const ScopePtr& scope, AstExprConstantBool* bool_, std::optional<TypeId> expectedType, bool forceSingleton);
|
||||||
Inference check(const ScopePtr& scope, AstExprLocal* local, ValueContext context);
|
Inference check(const ScopePtr& scope, AstExprLocal* local);
|
||||||
Inference check(const ScopePtr& scope, AstExprGlobal* global);
|
Inference check(const ScopePtr& scope, AstExprGlobal* global);
|
||||||
Inference check(const ScopePtr& scope, AstExprIndexName* indexName);
|
Inference check(const ScopePtr& scope, AstExprIndexName* indexName);
|
||||||
Inference check(const ScopePtr& scope, AstExprIndexExpr* indexExpr);
|
Inference check(const ScopePtr& scope, AstExprIndexExpr* indexExpr);
|
||||||
|
@ -208,6 +208,11 @@ private:
|
||||||
std::vector<TypeId> checkLValues(const ScopePtr& scope, AstArray<AstExpr*> exprs);
|
std::vector<TypeId> checkLValues(const ScopePtr& scope, AstArray<AstExpr*> exprs);
|
||||||
|
|
||||||
TypeId checkLValue(const ScopePtr& scope, AstExpr* expr);
|
TypeId checkLValue(const ScopePtr& scope, AstExpr* expr);
|
||||||
|
TypeId checkLValue(const ScopePtr& scope, AstExprLocal* local);
|
||||||
|
TypeId checkLValue(const ScopePtr& scope, AstExprGlobal* global);
|
||||||
|
TypeId checkLValue(const ScopePtr& scope, AstExprIndexName* indexName);
|
||||||
|
TypeId checkLValue(const ScopePtr& scope, AstExprIndexExpr* indexExpr);
|
||||||
|
TypeId updateProperty(const ScopePtr& scope, AstExpr* expr);
|
||||||
|
|
||||||
struct FunctionSignature
|
struct FunctionSignature
|
||||||
{
|
{
|
||||||
|
|
|
@ -52,6 +52,7 @@ struct Scope
|
||||||
void addBuiltinTypeBinding(const Name& name, const TypeFun& tyFun);
|
void addBuiltinTypeBinding(const Name& name, const TypeFun& tyFun);
|
||||||
|
|
||||||
std::optional<TypeId> lookup(Symbol sym) const;
|
std::optional<TypeId> lookup(Symbol sym) const;
|
||||||
|
std::optional<TypeId> lookupLValue(DefId def) const;
|
||||||
std::optional<TypeId> lookup(DefId def) const;
|
std::optional<TypeId> lookup(DefId def) const;
|
||||||
std::optional<std::pair<Binding*, Scope*>> lookupEx(Symbol sym);
|
std::optional<std::pair<Binding*, Scope*>> lookupEx(Symbol sym);
|
||||||
|
|
||||||
|
@ -65,7 +66,15 @@ struct Scope
|
||||||
std::optional<Binding> linearSearchForBinding(const std::string& name, bool traverseScopeChain = true) const;
|
std::optional<Binding> linearSearchForBinding(const std::string& name, bool traverseScopeChain = true) const;
|
||||||
|
|
||||||
RefinementMap refinements;
|
RefinementMap refinements;
|
||||||
DenseHashMap<const Def*, TypeId> dcrRefinements{nullptr};
|
|
||||||
|
// This can be viewed as the "unrefined" type of each binding.
|
||||||
|
DenseHashMap<const Def*, TypeId> lvalueTypes{nullptr};
|
||||||
|
|
||||||
|
// Luau values are routinely refined more narrowly than their actual
|
||||||
|
// inferred type through control flow statements. We retain those refined
|
||||||
|
// types here.
|
||||||
|
DenseHashMap<const Def*, TypeId> rvalueRefinements{nullptr};
|
||||||
|
|
||||||
void inheritRefinements(const ScopePtr& childScope);
|
void inheritRefinements(const ScopePtr& childScope);
|
||||||
|
|
||||||
// For mutually recursive type aliases, it's important that
|
// For mutually recursive type aliases, it's important that
|
||||||
|
|
|
@ -355,6 +355,7 @@ struct FunctionType
|
||||||
// `hasNoFreeOrGenericTypes` should be true if and only if the type does not have any free or generic types present inside it.
|
// `hasNoFreeOrGenericTypes` should be true if and only if the type does not have any free or generic types present inside it.
|
||||||
// this flag is used as an optimization to exit early from procedures that manipulate free or generic types.
|
// this flag is used as an optimization to exit early from procedures that manipulate free or generic types.
|
||||||
bool hasNoFreeOrGenericTypes = false;
|
bool hasNoFreeOrGenericTypes = false;
|
||||||
|
bool isCheckedFunction = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class TableState
|
enum class TableState
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "ConstraintSolver.h"
|
||||||
|
#include "Error.h"
|
||||||
#include "Luau/Error.h"
|
#include "Luau/Error.h"
|
||||||
#include "Luau/NotNull.h"
|
#include "Luau/NotNull.h"
|
||||||
#include "Luau/Variant.h"
|
#include "Luau/Variant.h"
|
||||||
|
#include "NotNull.h"
|
||||||
|
#include "TypeCheckLimits.h"
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
@ -23,6 +27,42 @@ struct BuiltinTypes;
|
||||||
struct TxnLog;
|
struct TxnLog;
|
||||||
class Normalizer;
|
class Normalizer;
|
||||||
|
|
||||||
|
struct TypeFamilyContext
|
||||||
|
{
|
||||||
|
NotNull<TypeArena> arena;
|
||||||
|
NotNull<BuiltinTypes> builtins;
|
||||||
|
NotNull<Scope> scope;
|
||||||
|
NotNull<Normalizer> normalizer;
|
||||||
|
NotNull<InternalErrorReporter> ice;
|
||||||
|
NotNull<TypeCheckLimits> limits;
|
||||||
|
|
||||||
|
// nullptr if the type family is being reduced outside of the constraint solver.
|
||||||
|
ConstraintSolver* solver;
|
||||||
|
|
||||||
|
TypeFamilyContext(NotNull<ConstraintSolver> cs, NotNull<Scope> scope)
|
||||||
|
: arena(cs->arena)
|
||||||
|
, builtins(cs->builtinTypes)
|
||||||
|
, scope(scope)
|
||||||
|
, normalizer(cs->normalizer)
|
||||||
|
, ice(NotNull{&cs->iceReporter})
|
||||||
|
, limits(NotNull{&cs->limits})
|
||||||
|
, solver(cs.get())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeFamilyContext(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtins, NotNull<Scope> scope, NotNull<Normalizer> normalizer,
|
||||||
|
NotNull<InternalErrorReporter> ice, NotNull<TypeCheckLimits> limits)
|
||||||
|
: arena(arena)
|
||||||
|
, builtins(builtins)
|
||||||
|
, scope(scope)
|
||||||
|
, normalizer(normalizer)
|
||||||
|
, ice(ice)
|
||||||
|
, limits(limits)
|
||||||
|
, solver(nullptr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/// Represents a reduction result, which may have successfully reduced the type,
|
/// Represents a reduction result, which may have successfully reduced the type,
|
||||||
/// may have concretely failed to reduce the type, or may simply be stuck
|
/// may have concretely failed to reduce the type, or may simply be stuck
|
||||||
/// without more information.
|
/// without more information.
|
||||||
|
@ -53,9 +93,7 @@ struct TypeFamily
|
||||||
std::string name;
|
std::string name;
|
||||||
|
|
||||||
/// The reducer function for the type family.
|
/// The reducer function for the type family.
|
||||||
std::function<TypeFamilyReductionResult<TypeId>(std::vector<TypeId>, std::vector<TypePackId>, NotNull<TypeArena>, NotNull<BuiltinTypes>,
|
std::function<TypeFamilyReductionResult<TypeId>(std::vector<TypeId>, std::vector<TypePackId>, NotNull<TypeFamilyContext>)> reducer;
|
||||||
NotNull<TxnLog>, NotNull<Scope>, NotNull<Normalizer>, ConstraintSolver*)>
|
|
||||||
reducer;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Represents a type function that may be applied to map a series of types and
|
/// Represents a type function that may be applied to map a series of types and
|
||||||
|
@ -67,9 +105,7 @@ struct TypePackFamily
|
||||||
std::string name;
|
std::string name;
|
||||||
|
|
||||||
/// The reducer function for the type pack family.
|
/// The reducer function for the type pack family.
|
||||||
std::function<TypeFamilyReductionResult<TypePackId>(std::vector<TypeId>, std::vector<TypePackId>, NotNull<TypeArena>, NotNull<BuiltinTypes>,
|
std::function<TypeFamilyReductionResult<TypePackId>(std::vector<TypeId>, std::vector<TypePackId>, NotNull<TypeFamilyContext>)> reducer;
|
||||||
NotNull<TxnLog>, NotNull<Scope>, NotNull<Normalizer>, ConstraintSolver*)>
|
|
||||||
reducer;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FamilyGraphReductionResult
|
struct FamilyGraphReductionResult
|
||||||
|
@ -91,14 +127,11 @@ struct FamilyGraphReductionResult
|
||||||
* @param arena an arena to allocate types into.
|
* @param arena an arena to allocate types into.
|
||||||
* @param builtins the built-in types.
|
* @param builtins the built-in types.
|
||||||
* @param normalizer the normalizer to use when normalizing types
|
* @param normalizer the normalizer to use when normalizing types
|
||||||
* @param log a TxnLog to use. If one is provided, substitution will take place
|
* @param ice the internal error reporter to use for ICEs
|
||||||
* against the TxnLog, otherwise substitutions will directly mutate the type
|
|
||||||
* graph. Do not provide the empty TxnLog, as a result.
|
|
||||||
*/
|
*/
|
||||||
FamilyGraphReductionResult reduceFamilies(TypeId entrypoint, Location location, NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtins,
|
FamilyGraphReductionResult reduceFamilies(TypeId entrypoint, Location location, TypeFamilyContext, bool force = false);
|
||||||
NotNull<Scope> scope, NotNull<Normalizer> normalizer, TxnLog* log = nullptr, bool force = false);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempt to reduce all instances of any type or type pack family in the type
|
* Attempt to reduce all instances of any type or type pack family in the type
|
||||||
* graph provided.
|
* graph provided.
|
||||||
*
|
*
|
||||||
|
@ -108,50 +141,30 @@ FamilyGraphReductionResult reduceFamilies(TypeId entrypoint, Location location,
|
||||||
* @param arena an arena to allocate types into.
|
* @param arena an arena to allocate types into.
|
||||||
* @param builtins the built-in types.
|
* @param builtins the built-in types.
|
||||||
* @param normalizer the normalizer to use when normalizing types
|
* @param normalizer the normalizer to use when normalizing types
|
||||||
* @param log a TxnLog to use. If one is provided, substitution will take place
|
* @param ice the internal error reporter to use for ICEs
|
||||||
* against the TxnLog, otherwise substitutions will directly mutate the type
|
|
||||||
* graph. Do not provide the empty TxnLog, as a result.
|
|
||||||
*/
|
*/
|
||||||
FamilyGraphReductionResult reduceFamilies(TypePackId entrypoint, Location location, NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtins,
|
FamilyGraphReductionResult reduceFamilies(TypePackId entrypoint, Location location, TypeFamilyContext, bool force = false);
|
||||||
NotNull<Scope> scope, NotNull<Normalizer> normalizer, TxnLog* log = nullptr, bool force = false);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempt to reduce all instances of any type or type pack family in the type
|
|
||||||
* graph provided.
|
|
||||||
*
|
|
||||||
* @param solver the constraint solver this reduction is being performed in.
|
|
||||||
* @param entrypoint the entry point to the type graph.
|
|
||||||
* @param location the location the reduction is occurring at; used to populate
|
|
||||||
* type errors.
|
|
||||||
* @param log a TxnLog to use. If one is provided, substitution will take place
|
|
||||||
* against the TxnLog, otherwise substitutions will directly mutate the type
|
|
||||||
* graph. Do not provide the empty TxnLog, as a result.
|
|
||||||
*/
|
|
||||||
FamilyGraphReductionResult reduceFamilies(
|
|
||||||
NotNull<ConstraintSolver> solver, TypeId entrypoint, Location location, NotNull<Scope> scope, TxnLog* log = nullptr, bool force = false);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempt to reduce all instances of any type or type pack family in the type
|
|
||||||
* graph provided.
|
|
||||||
*
|
|
||||||
* @param solver the constraint solver this reduction is being performed in.
|
|
||||||
* @param entrypoint the entry point to the type graph.
|
|
||||||
* @param location the location the reduction is occurring at; used to populate
|
|
||||||
* type errors.
|
|
||||||
* @param log a TxnLog to use. If one is provided, substitution will take place
|
|
||||||
* against the TxnLog, otherwise substitutions will directly mutate the type
|
|
||||||
* graph. Do not provide the empty TxnLog, as a result.
|
|
||||||
*/
|
|
||||||
FamilyGraphReductionResult reduceFamilies(
|
|
||||||
NotNull<ConstraintSolver> solver, TypePackId entrypoint, Location location, NotNull<Scope> scope, TxnLog* log = nullptr, bool force = false);
|
|
||||||
|
|
||||||
struct BuiltinTypeFamilies
|
struct BuiltinTypeFamilies
|
||||||
{
|
{
|
||||||
BuiltinTypeFamilies();
|
BuiltinTypeFamilies();
|
||||||
|
|
||||||
TypeFamily addFamily;
|
TypeFamily addFamily;
|
||||||
|
TypeFamily subFamily;
|
||||||
|
TypeFamily mulFamily;
|
||||||
|
TypeFamily divFamily;
|
||||||
|
TypeFamily idivFamily;
|
||||||
|
TypeFamily powFamily;
|
||||||
|
TypeFamily modFamily;
|
||||||
|
|
||||||
|
TypeFamily andFamily;
|
||||||
|
TypeFamily orFamily;
|
||||||
|
|
||||||
|
void addToScope(NotNull<TypeArena> arena, NotNull<Scope> scope) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const BuiltinTypeFamilies kBuiltinTypeFamilies{};
|
const BuiltinTypeFamilies kBuiltinTypeFamilies{};
|
||||||
|
|
||||||
} // namespace Luau
|
} // namespace Luau
|
||||||
|
|
|
@ -8,7 +8,9 @@
|
||||||
#include "Luau/ToString.h"
|
#include "Luau/ToString.h"
|
||||||
#include "Luau/ConstraintSolver.h"
|
#include "Luau/ConstraintSolver.h"
|
||||||
#include "Luau/ConstraintGraphBuilder.h"
|
#include "Luau/ConstraintGraphBuilder.h"
|
||||||
|
#include "Luau/NotNull.h"
|
||||||
#include "Luau/TypeInfer.h"
|
#include "Luau/TypeInfer.h"
|
||||||
|
#include "Luau/TypeFamily.h"
|
||||||
#include "Luau/TypePack.h"
|
#include "Luau/TypePack.h"
|
||||||
#include "Luau/Type.h"
|
#include "Luau/Type.h"
|
||||||
#include "Luau/TypeUtils.h"
|
#include "Luau/TypeUtils.h"
|
||||||
|
@ -21,6 +23,8 @@
|
||||||
* about a function that takes any number of values, but where each value must have some specific type.
|
* about a function that takes any number of values, but where each value must have some specific type.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -209,6 +213,9 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
|
||||||
TypeArena& arena = globals.globalTypes;
|
TypeArena& arena = globals.globalTypes;
|
||||||
NotNull<BuiltinTypes> builtinTypes = globals.builtinTypes;
|
NotNull<BuiltinTypes> builtinTypes = globals.builtinTypes;
|
||||||
|
|
||||||
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
kBuiltinTypeFamilies.addToScope(NotNull{&arena}, NotNull{globals.globalScope.get()});
|
||||||
|
|
||||||
LoadDefinitionFileResult loadResult = frontend.loadDefinitionFile(
|
LoadDefinitionFileResult loadResult = frontend.loadDefinitionFile(
|
||||||
globals, globals.globalScope, getBuiltinDefinitionSource(), "@luau", /* captureComments */ false, typeCheckForAutocomplete);
|
globals, globals.globalScope, getBuiltinDefinitionSource(), "@luau", /* captureComments */ false, typeCheckForAutocomplete);
|
||||||
LUAU_ASSERT(loadResult.success);
|
LUAU_ASSERT(loadResult.success);
|
||||||
|
|
|
@ -455,7 +455,7 @@ void ConstraintGraphBuilder::applyRefinements(const ScopePtr& scope, Location lo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
scope->dcrRefinements[def] = ty;
|
scope->rvalueRefinements[def] = ty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -627,7 +627,7 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocal* l
|
||||||
if (hasAnnotation)
|
if (hasAnnotation)
|
||||||
expectedType = varTypes.at(i);
|
expectedType = varTypes.at(i);
|
||||||
|
|
||||||
TypeId exprType = check(scope, value, ValueContext::RValue, expectedType, /*forceSingleton*/ false, /*generalize*/ true).ty;
|
TypeId exprType = check(scope, value, expectedType, /*forceSingleton*/ false, /*generalize*/ true).ty;
|
||||||
if (i < varTypes.size())
|
if (i < varTypes.size())
|
||||||
{
|
{
|
||||||
if (varTypes[i])
|
if (varTypes[i])
|
||||||
|
@ -699,7 +699,8 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocal* l
|
||||||
// HACK: In the greedy solver, we say the type state of a variable is the type annotation itself, but
|
// HACK: In the greedy solver, we say the type state of a variable is the type annotation itself, but
|
||||||
// the actual type state is the corresponding initializer expression (if it exists) or nil otherwise.
|
// the actual type state is the corresponding initializer expression (if it exists) or nil otherwise.
|
||||||
BreadcrumbId bc = dfg->getBreadcrumb(l);
|
BreadcrumbId bc = dfg->getBreadcrumb(l);
|
||||||
scope->dcrRefinements[bc->def] = varTypes[i];
|
scope->lvalueTypes[bc->def] = varTypes[i];
|
||||||
|
scope->rvalueRefinements[bc->def] = varTypes[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (local->values.size > 0)
|
if (local->values.size > 0)
|
||||||
|
@ -764,7 +765,8 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFor* for
|
||||||
forScope->bindings[for_->var] = Binding{annotationTy, for_->var->location};
|
forScope->bindings[for_->var] = Binding{annotationTy, for_->var->location};
|
||||||
|
|
||||||
BreadcrumbId bc = dfg->getBreadcrumb(for_->var);
|
BreadcrumbId bc = dfg->getBreadcrumb(for_->var);
|
||||||
forScope->dcrRefinements[bc->def] = annotationTy;
|
forScope->lvalueTypes[bc->def] = annotationTy;
|
||||||
|
forScope->rvalueRefinements[bc->def] = annotationTy;
|
||||||
|
|
||||||
visit(forScope, for_->body);
|
visit(forScope, for_->body);
|
||||||
|
|
||||||
|
@ -791,7 +793,8 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatForIn* f
|
||||||
variableTypes.push_back(ty);
|
variableTypes.push_back(ty);
|
||||||
|
|
||||||
BreadcrumbId bc = dfg->getBreadcrumb(var);
|
BreadcrumbId bc = dfg->getBreadcrumb(var);
|
||||||
loopScope->dcrRefinements[bc->def] = ty;
|
loopScope->lvalueTypes[bc->def] = ty;
|
||||||
|
loopScope->rvalueRefinements[bc->def] = ty;
|
||||||
}
|
}
|
||||||
|
|
||||||
// It is always ok to provide too few variables, so we give this pack a free tail.
|
// It is always ok to provide too few variables, so we give this pack a free tail.
|
||||||
|
@ -845,8 +848,10 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocalFun
|
||||||
sig.bodyScope->bindings[function->name] = Binding{sig.signature, function->func->location};
|
sig.bodyScope->bindings[function->name] = Binding{sig.signature, function->func->location};
|
||||||
|
|
||||||
BreadcrumbId bc = dfg->getBreadcrumb(function->name);
|
BreadcrumbId bc = dfg->getBreadcrumb(function->name);
|
||||||
scope->dcrRefinements[bc->def] = functionType;
|
scope->lvalueTypes[bc->def] = functionType;
|
||||||
sig.bodyScope->dcrRefinements[bc->def] = sig.signature;
|
scope->rvalueRefinements[bc->def] = functionType;
|
||||||
|
sig.bodyScope->lvalueTypes[bc->def] = sig.signature;
|
||||||
|
sig.bodyScope->rvalueRefinements[bc->def] = sig.signature;
|
||||||
|
|
||||||
Checkpoint start = checkpoint(this);
|
Checkpoint start = checkpoint(this);
|
||||||
checkFunctionBody(sig.bodyScope, function->func);
|
checkFunctionBody(sig.bodyScope, function->func);
|
||||||
|
@ -889,9 +894,12 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFunction
|
||||||
|
|
||||||
const NullableBreadcrumbId functionBreadcrumb = dfg->getBreadcrumb(function->name);
|
const NullableBreadcrumbId functionBreadcrumb = dfg->getBreadcrumb(function->name);
|
||||||
|
|
||||||
|
std::optional<TypeId> existingFunctionTy;
|
||||||
|
if (functionBreadcrumb)
|
||||||
|
existingFunctionTy = scope->lookupLValue(functionBreadcrumb->def);
|
||||||
|
|
||||||
if (AstExprLocal* localName = function->name->as<AstExprLocal>())
|
if (AstExprLocal* localName = function->name->as<AstExprLocal>())
|
||||||
{
|
{
|
||||||
std::optional<TypeId> existingFunctionTy = scope->lookup(localName->local);
|
|
||||||
if (existingFunctionTy)
|
if (existingFunctionTy)
|
||||||
{
|
{
|
||||||
addConstraint(scope, function->name->location, SubtypeConstraint{generalizedType, *existingFunctionTy});
|
addConstraint(scope, function->name->location, SubtypeConstraint{generalizedType, *existingFunctionTy});
|
||||||
|
@ -905,11 +913,13 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFunction
|
||||||
sig.bodyScope->bindings[localName->local] = Binding{sig.signature, localName->location};
|
sig.bodyScope->bindings[localName->local] = Binding{sig.signature, localName->location};
|
||||||
|
|
||||||
if (functionBreadcrumb)
|
if (functionBreadcrumb)
|
||||||
sig.bodyScope->dcrRefinements[functionBreadcrumb->def] = sig.signature;
|
{
|
||||||
|
sig.bodyScope->lvalueTypes[functionBreadcrumb->def] = sig.signature;
|
||||||
|
sig.bodyScope->rvalueRefinements[functionBreadcrumb->def] = sig.signature;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (AstExprGlobal* globalName = function->name->as<AstExprGlobal>())
|
else if (AstExprGlobal* globalName = function->name->as<AstExprGlobal>())
|
||||||
{
|
{
|
||||||
std::optional<TypeId> existingFunctionTy = scope->lookup(globalName->name);
|
|
||||||
if (!existingFunctionTy)
|
if (!existingFunctionTy)
|
||||||
ice->ice("prepopulateGlobalScope did not populate a global name", globalName->location);
|
ice->ice("prepopulateGlobalScope did not populate a global name", globalName->location);
|
||||||
|
|
||||||
|
@ -918,7 +928,10 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFunction
|
||||||
sig.bodyScope->bindings[globalName->name] = Binding{sig.signature, globalName->location};
|
sig.bodyScope->bindings[globalName->name] = Binding{sig.signature, globalName->location};
|
||||||
|
|
||||||
if (functionBreadcrumb)
|
if (functionBreadcrumb)
|
||||||
sig.bodyScope->dcrRefinements[functionBreadcrumb->def] = sig.signature;
|
{
|
||||||
|
sig.bodyScope->lvalueTypes[functionBreadcrumb->def] = sig.signature;
|
||||||
|
sig.bodyScope->rvalueRefinements[functionBreadcrumb->def] = sig.signature;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (AstExprIndexName* indexName = function->name->as<AstExprIndexName>())
|
else if (AstExprIndexName* indexName = function->name->as<AstExprIndexName>())
|
||||||
{
|
{
|
||||||
|
@ -946,7 +959,7 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFunction
|
||||||
ice->ice("generalizedType == nullptr", function->location);
|
ice->ice("generalizedType == nullptr", function->location);
|
||||||
|
|
||||||
if (functionBreadcrumb)
|
if (functionBreadcrumb)
|
||||||
scope->dcrRefinements[functionBreadcrumb->def] = generalizedType;
|
scope->rvalueRefinements[functionBreadcrumb->def] = generalizedType;
|
||||||
|
|
||||||
checkFunctionBody(sig.bodyScope, function->func);
|
checkFunctionBody(sig.bodyScope, function->func);
|
||||||
Checkpoint end = checkpoint(this);
|
Checkpoint end = checkpoint(this);
|
||||||
|
@ -1059,7 +1072,7 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatCompound
|
||||||
|
|
||||||
ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatIf* ifStatement)
|
ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatIf* ifStatement)
|
||||||
{
|
{
|
||||||
RefinementId refinement = check(scope, ifStatement->condition, ValueContext::RValue, std::nullopt).refinement;
|
RefinementId refinement = check(scope, ifStatement->condition, std::nullopt).refinement;
|
||||||
|
|
||||||
ScopePtr thenScope = childScope(ifStatement->thenbody, scope);
|
ScopePtr thenScope = childScope(ifStatement->thenbody, scope);
|
||||||
applyRefinements(thenScope, ifStatement->condition->location, refinement);
|
applyRefinements(thenScope, ifStatement->condition->location, refinement);
|
||||||
|
@ -1164,7 +1177,8 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatDeclareG
|
||||||
rootScope->bindings[global->name] = Binding{globalTy, global->location};
|
rootScope->bindings[global->name] = Binding{globalTy, global->location};
|
||||||
|
|
||||||
BreadcrumbId bc = dfg->getBreadcrumb(global);
|
BreadcrumbId bc = dfg->getBreadcrumb(global);
|
||||||
rootScope->dcrRefinements[bc->def] = globalTy;
|
rootScope->lvalueTypes[bc->def] = globalTy;
|
||||||
|
rootScope->rvalueRefinements[bc->def] = globalTy;
|
||||||
|
|
||||||
return ControlFlow::None;
|
return ControlFlow::None;
|
||||||
}
|
}
|
||||||
|
@ -1323,6 +1337,7 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatDeclareF
|
||||||
TypePackId retPack = resolveTypePack(funScope, global->retTypes, /* inTypeArguments */ false);
|
TypePackId retPack = resolveTypePack(funScope, global->retTypes, /* inTypeArguments */ false);
|
||||||
TypeId fnType = arena->addType(FunctionType{TypeLevel{}, funScope.get(), std::move(genericTys), std::move(genericTps), paramPack, retPack});
|
TypeId fnType = arena->addType(FunctionType{TypeLevel{}, funScope.get(), std::move(genericTys), std::move(genericTps), paramPack, retPack});
|
||||||
FunctionType* ftv = getMutable<FunctionType>(fnType);
|
FunctionType* ftv = getMutable<FunctionType>(fnType);
|
||||||
|
ftv->isCheckedFunction = global->checkedFunction;
|
||||||
|
|
||||||
ftv->argNames.reserve(global->paramNames.size);
|
ftv->argNames.reserve(global->paramNames.size);
|
||||||
for (const auto& el : global->paramNames)
|
for (const auto& el : global->paramNames)
|
||||||
|
@ -1334,7 +1349,8 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatDeclareF
|
||||||
scope->bindings[global->name] = Binding{fnType, global->location};
|
scope->bindings[global->name] = Binding{fnType, global->location};
|
||||||
|
|
||||||
BreadcrumbId bc = dfg->getBreadcrumb(global);
|
BreadcrumbId bc = dfg->getBreadcrumb(global);
|
||||||
rootScope->dcrRefinements[bc->def] = fnType;
|
rootScope->lvalueTypes[bc->def] = fnType;
|
||||||
|
rootScope->rvalueRefinements[bc->def] = fnType;
|
||||||
|
|
||||||
return ControlFlow::None;
|
return ControlFlow::None;
|
||||||
}
|
}
|
||||||
|
@ -1363,7 +1379,7 @@ InferencePack ConstraintGraphBuilder::checkPack(
|
||||||
std::optional<TypeId> expectedType;
|
std::optional<TypeId> expectedType;
|
||||||
if (i < expectedTypes.size())
|
if (i < expectedTypes.size())
|
||||||
expectedType = expectedTypes[i];
|
expectedType = expectedTypes[i];
|
||||||
head.push_back(check(scope, expr, ValueContext::RValue, expectedType).ty);
|
head.push_back(check(scope, expr, expectedType).ty);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -1407,7 +1423,7 @@ InferencePack ConstraintGraphBuilder::checkPack(
|
||||||
std::optional<TypeId> expectedType;
|
std::optional<TypeId> expectedType;
|
||||||
if (!expectedTypes.empty())
|
if (!expectedTypes.empty())
|
||||||
expectedType = expectedTypes[0];
|
expectedType = expectedTypes[0];
|
||||||
TypeId t = check(scope, expr, ValueContext::RValue, expectedType, /*forceSingletons*/ false, generalize).ty;
|
TypeId t = check(scope, expr, expectedType, /*forceSingletons*/ false, generalize).ty;
|
||||||
result = InferencePack{arena->addTypePack({t})};
|
result = InferencePack{arena->addTypePack({t})};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1489,8 +1505,7 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCa
|
||||||
}
|
}
|
||||||
else if (i < exprArgs.size() - 1 || !(arg->is<AstExprCall>() || arg->is<AstExprVarargs>()))
|
else if (i < exprArgs.size() - 1 || !(arg->is<AstExprCall>() || arg->is<AstExprVarargs>()))
|
||||||
{
|
{
|
||||||
auto [ty, refinement] =
|
auto [ty, refinement] = check(scope, arg, /*expectedType*/ std::nullopt, /*forceSingleton*/ false, /*generalize*/ false);
|
||||||
check(scope, arg, ValueContext::RValue, /*expectedType*/ std::nullopt, /*forceSingleton*/ false, /*generalize*/ false);
|
|
||||||
args.push_back(ty);
|
args.push_back(ty);
|
||||||
argumentRefinements.push_back(refinement);
|
argumentRefinements.push_back(refinement);
|
||||||
}
|
}
|
||||||
|
@ -1549,7 +1564,8 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCa
|
||||||
scope->bindings[targetLocal->local].typeId = resultTy;
|
scope->bindings[targetLocal->local].typeId = resultTy;
|
||||||
|
|
||||||
BreadcrumbId bc = dfg->getBreadcrumb(targetLocal);
|
BreadcrumbId bc = dfg->getBreadcrumb(targetLocal);
|
||||||
scope->dcrRefinements[bc->def] = resultTy; // TODO: typestates: track this as an assignment
|
scope->lvalueTypes[bc->def] = resultTy; // TODO: typestates: track this as an assignment
|
||||||
|
scope->rvalueRefinements[bc->def] = resultTy; // TODO: typestates: track this as an assignment
|
||||||
}
|
}
|
||||||
|
|
||||||
return InferencePack{arena->addTypePack({resultTy}), {refinementArena.variadic(returnRefinements)}};
|
return InferencePack{arena->addTypePack({resultTy}), {refinementArena.variadic(returnRefinements)}};
|
||||||
|
@ -1588,7 +1604,7 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCa
|
||||||
}
|
}
|
||||||
|
|
||||||
Inference ConstraintGraphBuilder::check(
|
Inference ConstraintGraphBuilder::check(
|
||||||
const ScopePtr& scope, AstExpr* expr, ValueContext context, std::optional<TypeId> expectedType, bool forceSingleton, bool generalize)
|
const ScopePtr& scope, AstExpr* expr, std::optional<TypeId> expectedType, bool forceSingleton, bool generalize)
|
||||||
{
|
{
|
||||||
RecursionCounter counter{&recursionCount};
|
RecursionCounter counter{&recursionCount};
|
||||||
|
|
||||||
|
@ -1601,7 +1617,7 @@ Inference ConstraintGraphBuilder::check(
|
||||||
Inference result;
|
Inference result;
|
||||||
|
|
||||||
if (auto group = expr->as<AstExprGroup>())
|
if (auto group = expr->as<AstExprGroup>())
|
||||||
result = check(scope, group->expr, ValueContext::RValue, expectedType, forceSingleton);
|
result = check(scope, group->expr, expectedType, forceSingleton);
|
||||||
else if (auto stringExpr = expr->as<AstExprConstantString>())
|
else if (auto stringExpr = expr->as<AstExprConstantString>())
|
||||||
result = check(scope, stringExpr, expectedType, forceSingleton);
|
result = check(scope, stringExpr, expectedType, forceSingleton);
|
||||||
else if (expr->is<AstExprConstantNumber>())
|
else if (expr->is<AstExprConstantNumber>())
|
||||||
|
@ -1611,7 +1627,7 @@ Inference ConstraintGraphBuilder::check(
|
||||||
else if (expr->is<AstExprConstantNil>())
|
else if (expr->is<AstExprConstantNil>())
|
||||||
result = Inference{builtinTypes->nilType};
|
result = Inference{builtinTypes->nilType};
|
||||||
else if (auto local = expr->as<AstExprLocal>())
|
else if (auto local = expr->as<AstExprLocal>())
|
||||||
result = check(scope, local, context);
|
result = check(scope, local);
|
||||||
else if (auto global = expr->as<AstExprGlobal>())
|
else if (auto global = expr->as<AstExprGlobal>())
|
||||||
result = check(scope, global);
|
result = check(scope, global);
|
||||||
else if (expr->is<AstExprVarargs>())
|
else if (expr->is<AstExprVarargs>())
|
||||||
|
@ -1706,13 +1722,11 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprConstantBo
|
||||||
return Inference{builtinTypes->booleanType};
|
return Inference{builtinTypes->booleanType};
|
||||||
}
|
}
|
||||||
|
|
||||||
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprLocal* local, ValueContext context)
|
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprLocal* local)
|
||||||
{
|
{
|
||||||
BreadcrumbId bc = dfg->getBreadcrumb(local);
|
BreadcrumbId bc = dfg->getBreadcrumb(local);
|
||||||
|
|
||||||
if (auto ty = scope->lookup(bc->def); ty && context == ValueContext::RValue)
|
if (auto ty = scope->lookup(bc->def))
|
||||||
return Inference{*ty, refinementArena.proposition(bc, builtinTypes->truthyType)};
|
|
||||||
else if (auto ty = scope->lookup(local->local))
|
|
||||||
return Inference{*ty, refinementArena.proposition(bc, builtinTypes->truthyType)};
|
return Inference{*ty, refinementArena.proposition(bc, builtinTypes->truthyType)};
|
||||||
else
|
else
|
||||||
ice->ice("AstExprLocal came before its declaration?");
|
ice->ice("AstExprLocal came before its declaration?");
|
||||||
|
@ -1729,7 +1743,7 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprGlobal* gl
|
||||||
return Inference{*ty, refinementArena.proposition(bc, builtinTypes->truthyType)};
|
return Inference{*ty, refinementArena.proposition(bc, builtinTypes->truthyType)};
|
||||||
else if (auto ty = scope->lookup(global->name))
|
else if (auto ty = scope->lookup(global->name))
|
||||||
{
|
{
|
||||||
rootScope->dcrRefinements[bc->def] = *ty;
|
rootScope->rvalueRefinements[bc->def] = *ty;
|
||||||
return Inference{*ty, refinementArena.proposition(bc, builtinTypes->truthyType)};
|
return Inference{*ty, refinementArena.proposition(bc, builtinTypes->truthyType)};
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -1750,7 +1764,7 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIndexName*
|
||||||
if (auto ty = scope->lookup(bc->def))
|
if (auto ty = scope->lookup(bc->def))
|
||||||
return Inference{*ty, refinementArena.proposition(NotNull{bc}, builtinTypes->truthyType)};
|
return Inference{*ty, refinementArena.proposition(NotNull{bc}, builtinTypes->truthyType)};
|
||||||
|
|
||||||
scope->dcrRefinements[bc->def] = result;
|
scope->rvalueRefinements[bc->def] = result;
|
||||||
}
|
}
|
||||||
|
|
||||||
addConstraint(scope, indexName->expr->location, HasPropConstraint{result, obj, indexName->index.value});
|
addConstraint(scope, indexName->expr->location, HasPropConstraint{result, obj, indexName->index.value});
|
||||||
|
@ -1773,7 +1787,7 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIndexExpr*
|
||||||
if (auto ty = scope->lookup(bc->def))
|
if (auto ty = scope->lookup(bc->def))
|
||||||
return Inference{*ty, refinementArena.proposition(NotNull{bc}, builtinTypes->truthyType)};
|
return Inference{*ty, refinementArena.proposition(NotNull{bc}, builtinTypes->truthyType)};
|
||||||
|
|
||||||
scope->dcrRefinements[bc->def] = result;
|
scope->rvalueRefinements[bc->def] = result;
|
||||||
}
|
}
|
||||||
|
|
||||||
TableIndexer indexer{indexType, result};
|
TableIndexer indexer{indexType, result};
|
||||||
|
@ -1836,7 +1850,9 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprBinary* bi
|
||||||
{
|
{
|
||||||
auto [leftType, rightType, refinement] = checkBinary(scope, binary, expectedType);
|
auto [leftType, rightType, refinement] = checkBinary(scope, binary, expectedType);
|
||||||
|
|
||||||
if (binary->op == AstExprBinary::Op::Add)
|
switch (binary->op)
|
||||||
|
{
|
||||||
|
case AstExprBinary::Op::Add:
|
||||||
{
|
{
|
||||||
TypeId resultType = arena->addType(TypeFamilyInstanceType{
|
TypeId resultType = arena->addType(TypeFamilyInstanceType{
|
||||||
NotNull{&kBuiltinTypeFamilies.addFamily},
|
NotNull{&kBuiltinTypeFamilies.addFamily},
|
||||||
|
@ -1846,11 +1862,94 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprBinary* bi
|
||||||
addConstraint(scope, binary->location, ReduceConstraint{resultType});
|
addConstraint(scope, binary->location, ReduceConstraint{resultType});
|
||||||
return Inference{resultType, std::move(refinement)};
|
return Inference{resultType, std::move(refinement)};
|
||||||
}
|
}
|
||||||
|
case AstExprBinary::Op::Sub:
|
||||||
|
{
|
||||||
|
TypeId resultType = arena->addType(TypeFamilyInstanceType{
|
||||||
|
NotNull{&kBuiltinTypeFamilies.subFamily},
|
||||||
|
{leftType, rightType},
|
||||||
|
{},
|
||||||
|
});
|
||||||
|
addConstraint(scope, binary->location, ReduceConstraint{resultType});
|
||||||
|
return Inference{resultType, std::move(refinement)};
|
||||||
|
}
|
||||||
|
case AstExprBinary::Op::Mul:
|
||||||
|
{
|
||||||
|
TypeId resultType = arena->addType(TypeFamilyInstanceType{
|
||||||
|
NotNull{&kBuiltinTypeFamilies.mulFamily},
|
||||||
|
{leftType, rightType},
|
||||||
|
{},
|
||||||
|
});
|
||||||
|
addConstraint(scope, binary->location, ReduceConstraint{resultType});
|
||||||
|
return Inference{resultType, std::move(refinement)};
|
||||||
|
}
|
||||||
|
case AstExprBinary::Op::Div:
|
||||||
|
{
|
||||||
|
TypeId resultType = arena->addType(TypeFamilyInstanceType{
|
||||||
|
NotNull{&kBuiltinTypeFamilies.divFamily},
|
||||||
|
{leftType, rightType},
|
||||||
|
{},
|
||||||
|
});
|
||||||
|
addConstraint(scope, binary->location, ReduceConstraint{resultType});
|
||||||
|
return Inference{resultType, std::move(refinement)};
|
||||||
|
}
|
||||||
|
case AstExprBinary::Op::FloorDiv:
|
||||||
|
{
|
||||||
|
TypeId resultType = arena->addType(TypeFamilyInstanceType{
|
||||||
|
NotNull{&kBuiltinTypeFamilies.idivFamily},
|
||||||
|
{leftType, rightType},
|
||||||
|
{},
|
||||||
|
});
|
||||||
|
addConstraint(scope, binary->location, ReduceConstraint{resultType});
|
||||||
|
return Inference{resultType, std::move(refinement)};
|
||||||
|
}
|
||||||
|
case AstExprBinary::Op::Pow:
|
||||||
|
{
|
||||||
|
TypeId resultType = arena->addType(TypeFamilyInstanceType{
|
||||||
|
NotNull{&kBuiltinTypeFamilies.powFamily},
|
||||||
|
{leftType, rightType},
|
||||||
|
{},
|
||||||
|
});
|
||||||
|
addConstraint(scope, binary->location, ReduceConstraint{resultType});
|
||||||
|
return Inference{resultType, std::move(refinement)};
|
||||||
|
}
|
||||||
|
case AstExprBinary::Op::Mod:
|
||||||
|
{
|
||||||
|
TypeId resultType = arena->addType(TypeFamilyInstanceType{
|
||||||
|
NotNull{&kBuiltinTypeFamilies.modFamily},
|
||||||
|
{leftType, rightType},
|
||||||
|
{},
|
||||||
|
});
|
||||||
|
addConstraint(scope, binary->location, ReduceConstraint{resultType});
|
||||||
|
return Inference{resultType, std::move(refinement)};
|
||||||
|
}
|
||||||
|
case AstExprBinary::Op::And:
|
||||||
|
{
|
||||||
|
TypeId resultType = arena->addType(TypeFamilyInstanceType{
|
||||||
|
NotNull{&kBuiltinTypeFamilies.andFamily},
|
||||||
|
{leftType, rightType},
|
||||||
|
{},
|
||||||
|
});
|
||||||
|
addConstraint(scope, binary->location, ReduceConstraint{resultType});
|
||||||
|
return Inference{resultType, std::move(refinement)};
|
||||||
|
}
|
||||||
|
case AstExprBinary::Op::Or:
|
||||||
|
{
|
||||||
|
TypeId resultType = arena->addType(TypeFamilyInstanceType{
|
||||||
|
NotNull{&kBuiltinTypeFamilies.orFamily},
|
||||||
|
{leftType, rightType},
|
||||||
|
{},
|
||||||
|
});
|
||||||
|
addConstraint(scope, binary->location, ReduceConstraint{resultType});
|
||||||
|
return Inference{resultType, std::move(refinement)};
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
TypeId resultType = arena->addType(BlockedType{});
|
TypeId resultType = arena->addType(BlockedType{});
|
||||||
addConstraint(scope, binary->location,
|
addConstraint(scope, binary->location,
|
||||||
BinaryConstraint{binary->op, leftType, rightType, resultType, binary, &module->astOriginalCallTypes, &module->astOverloadResolvedTypes});
|
BinaryConstraint{binary->op, leftType, rightType, resultType, binary, &module->astOriginalCallTypes, &module->astOverloadResolvedTypes});
|
||||||
return Inference{resultType, std::move(refinement)};
|
return Inference{resultType, std::move(refinement)};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIfElse* ifElse, std::optional<TypeId> expectedType)
|
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIfElse* ifElse, std::optional<TypeId> expectedType)
|
||||||
|
@ -1860,18 +1959,18 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIfElse* if
|
||||||
|
|
||||||
ScopePtr thenScope = childScope(ifElse->trueExpr, scope);
|
ScopePtr thenScope = childScope(ifElse->trueExpr, scope);
|
||||||
applyRefinements(thenScope, ifElse->trueExpr->location, refinement);
|
applyRefinements(thenScope, ifElse->trueExpr->location, refinement);
|
||||||
TypeId thenType = check(thenScope, ifElse->trueExpr, ValueContext::RValue, expectedType).ty;
|
TypeId thenType = check(thenScope, ifElse->trueExpr, expectedType).ty;
|
||||||
|
|
||||||
ScopePtr elseScope = childScope(ifElse->falseExpr, scope);
|
ScopePtr elseScope = childScope(ifElse->falseExpr, scope);
|
||||||
applyRefinements(elseScope, ifElse->falseExpr->location, refinementArena.negation(refinement));
|
applyRefinements(elseScope, ifElse->falseExpr->location, refinementArena.negation(refinement));
|
||||||
TypeId elseType = check(elseScope, ifElse->falseExpr, ValueContext::RValue, expectedType).ty;
|
TypeId elseType = check(elseScope, ifElse->falseExpr, expectedType).ty;
|
||||||
|
|
||||||
return Inference{expectedType ? *expectedType : simplifyUnion(builtinTypes, arena, thenType, elseType).result};
|
return Inference{expectedType ? *expectedType : simplifyUnion(builtinTypes, arena, thenType, elseType).result};
|
||||||
}
|
}
|
||||||
|
|
||||||
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprTypeAssertion* typeAssert)
|
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprTypeAssertion* typeAssert)
|
||||||
{
|
{
|
||||||
check(scope, typeAssert->expr, ValueContext::RValue, std::nullopt);
|
check(scope, typeAssert->expr, std::nullopt);
|
||||||
return Inference{resolveType(scope, typeAssert->annotation, /* inTypeArguments */ false)};
|
return Inference{resolveType(scope, typeAssert->annotation, /* inTypeArguments */ false)};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1893,11 +1992,11 @@ std::tuple<TypeId, TypeId, RefinementId> ConstraintGraphBuilder::checkBinary(
|
||||||
if (expectedType)
|
if (expectedType)
|
||||||
relaxedExpectedLhs = arena->addType(UnionType{{builtinTypes->falsyType, *expectedType}});
|
relaxedExpectedLhs = arena->addType(UnionType{{builtinTypes->falsyType, *expectedType}});
|
||||||
|
|
||||||
auto [leftType, leftRefinement] = check(scope, binary->left, ValueContext::RValue, relaxedExpectedLhs);
|
auto [leftType, leftRefinement] = check(scope, binary->left, relaxedExpectedLhs);
|
||||||
|
|
||||||
ScopePtr rightScope = childScope(binary->right, scope);
|
ScopePtr rightScope = childScope(binary->right, scope);
|
||||||
applyRefinements(rightScope, binary->right->location, leftRefinement);
|
applyRefinements(rightScope, binary->right->location, leftRefinement);
|
||||||
auto [rightType, rightRefinement] = check(rightScope, binary->right, ValueContext::RValue, expectedType);
|
auto [rightType, rightRefinement] = check(rightScope, binary->right, expectedType);
|
||||||
|
|
||||||
return {leftType, rightType, refinementArena.conjunction(leftRefinement, rightRefinement)};
|
return {leftType, rightType, refinementArena.conjunction(leftRefinement, rightRefinement)};
|
||||||
}
|
}
|
||||||
|
@ -1908,11 +2007,11 @@ std::tuple<TypeId, TypeId, RefinementId> ConstraintGraphBuilder::checkBinary(
|
||||||
if (expectedType)
|
if (expectedType)
|
||||||
relaxedExpectedLhs = arena->addType(UnionType{{builtinTypes->falsyType, *expectedType}});
|
relaxedExpectedLhs = arena->addType(UnionType{{builtinTypes->falsyType, *expectedType}});
|
||||||
|
|
||||||
auto [leftType, leftRefinement] = check(scope, binary->left, ValueContext::RValue, relaxedExpectedLhs);
|
auto [leftType, leftRefinement] = check(scope, binary->left, relaxedExpectedLhs);
|
||||||
|
|
||||||
ScopePtr rightScope = childScope(binary->right, scope);
|
ScopePtr rightScope = childScope(binary->right, scope);
|
||||||
applyRefinements(rightScope, binary->right->location, refinementArena.negation(leftRefinement));
|
applyRefinements(rightScope, binary->right->location, refinementArena.negation(leftRefinement));
|
||||||
auto [rightType, rightRefinement] = check(rightScope, binary->right, ValueContext::RValue, expectedType);
|
auto [rightType, rightRefinement] = check(rightScope, binary->right, expectedType);
|
||||||
|
|
||||||
return {leftType, rightType, refinementArena.disjunction(leftRefinement, rightRefinement)};
|
return {leftType, rightType, refinementArena.disjunction(leftRefinement, rightRefinement)};
|
||||||
}
|
}
|
||||||
|
@ -1973,8 +2072,8 @@ std::tuple<TypeId, TypeId, RefinementId> ConstraintGraphBuilder::checkBinary(
|
||||||
{
|
{
|
||||||
// We are checking a binary expression of the form a op b
|
// We are checking a binary expression of the form a op b
|
||||||
// Just because a op b is epxected to return a bool, doesn't mean a, b are expected to be bools too
|
// Just because a op b is epxected to return a bool, doesn't mean a, b are expected to be bools too
|
||||||
TypeId leftType = check(scope, binary->left, ValueContext::RValue, {}, true).ty;
|
TypeId leftType = check(scope, binary->left, {}, true).ty;
|
||||||
TypeId rightType = check(scope, binary->right, ValueContext::RValue, {}, true).ty;
|
TypeId rightType = check(scope, binary->right, {}, true).ty;
|
||||||
|
|
||||||
RefinementId leftRefinement = nullptr;
|
RefinementId leftRefinement = nullptr;
|
||||||
if (auto bc = dfg->getBreadcrumb(binary->left))
|
if (auto bc = dfg->getBreadcrumb(binary->left))
|
||||||
|
@ -1994,8 +2093,8 @@ std::tuple<TypeId, TypeId, RefinementId> ConstraintGraphBuilder::checkBinary(
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
TypeId leftType = check(scope, binary->left, ValueContext::RValue).ty;
|
TypeId leftType = check(scope, binary->left).ty;
|
||||||
TypeId rightType = check(scope, binary->right, ValueContext::RValue).ty;
|
TypeId rightType = check(scope, binary->right).ty;
|
||||||
return {leftType, rightType, nullptr};
|
return {leftType, rightType, nullptr};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2011,6 +2110,47 @@ std::vector<TypeId> ConstraintGraphBuilder::checkLValues(const ScopePtr& scope,
|
||||||
return types;
|
return types;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TypeId ConstraintGraphBuilder::checkLValue(const ScopePtr& scope, AstExpr* expr)
|
||||||
|
{
|
||||||
|
if (auto local = expr->as<AstExprLocal>())
|
||||||
|
return checkLValue(scope, local);
|
||||||
|
else if (auto global = expr->as<AstExprGlobal>())
|
||||||
|
return checkLValue(scope, global);
|
||||||
|
else if (auto indexName = expr->as<AstExprIndexName>())
|
||||||
|
return checkLValue(scope, indexName);
|
||||||
|
else if (auto indexExpr = expr->as<AstExprIndexExpr>())
|
||||||
|
return checkLValue(scope, indexExpr);
|
||||||
|
else if (auto error = expr->as<AstExprError>())
|
||||||
|
{
|
||||||
|
check(scope, error);
|
||||||
|
return builtinTypes->errorRecoveryType();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
ice->ice("checkLValue is inexhaustive");
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeId ConstraintGraphBuilder::checkLValue(const ScopePtr& scope, AstExprLocal* local)
|
||||||
|
{
|
||||||
|
std::optional<TypeId> upperBound = scope->lookup(Symbol{local->local});
|
||||||
|
LUAU_ASSERT(upperBound);
|
||||||
|
return *upperBound;
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeId ConstraintGraphBuilder::checkLValue(const ScopePtr& scope, AstExprGlobal* global)
|
||||||
|
{
|
||||||
|
return scope->lookup(Symbol{global->name}).value_or(builtinTypes->errorRecoveryType());
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeId ConstraintGraphBuilder::checkLValue(const ScopePtr& scope, AstExprIndexName* indexName)
|
||||||
|
{
|
||||||
|
return updateProperty(scope, indexName);
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeId ConstraintGraphBuilder::checkLValue(const ScopePtr& scope, AstExprIndexExpr* indexExpr)
|
||||||
|
{
|
||||||
|
return updateProperty(scope, indexExpr);
|
||||||
|
}
|
||||||
|
|
||||||
static bool isIndexNameEquivalent(AstExpr* expr)
|
static bool isIndexNameEquivalent(AstExpr* expr)
|
||||||
{
|
{
|
||||||
if (expr->is<AstExprIndexName>())
|
if (expr->is<AstExprIndexName>())
|
||||||
|
@ -2031,7 +2171,7 @@ static bool isIndexNameEquivalent(AstExpr* expr)
|
||||||
*
|
*
|
||||||
* If expr has the form name.a.b.c
|
* If expr has the form name.a.b.c
|
||||||
*/
|
*/
|
||||||
TypeId ConstraintGraphBuilder::checkLValue(const ScopePtr& scope, AstExpr* expr)
|
TypeId ConstraintGraphBuilder::updateProperty(const ScopePtr& scope, AstExpr* expr)
|
||||||
{
|
{
|
||||||
if (auto indexExpr = expr->as<AstExprIndexExpr>(); indexExpr && !indexExpr->index->is<AstExprConstantString>())
|
if (auto indexExpr = expr->as<AstExprIndexExpr>(); indexExpr && !indexExpr->index->is<AstExprConstantString>())
|
||||||
{
|
{
|
||||||
|
@ -2058,7 +2198,7 @@ TypeId ConstraintGraphBuilder::checkLValue(const ScopePtr& scope, AstExpr* expr)
|
||||||
return propType;
|
return propType;
|
||||||
}
|
}
|
||||||
else if (!isIndexNameEquivalent(expr))
|
else if (!isIndexNameEquivalent(expr))
|
||||||
return check(scope, expr, ValueContext::LValue).ty;
|
return check(scope, expr).ty;
|
||||||
|
|
||||||
Symbol sym;
|
Symbol sym;
|
||||||
std::vector<std::string> segments;
|
std::vector<std::string> segments;
|
||||||
|
@ -2086,7 +2226,7 @@ TypeId ConstraintGraphBuilder::checkLValue(const ScopePtr& scope, AstExpr* expr)
|
||||||
else if (auto indexExpr = e->as<AstExprIndexExpr>())
|
else if (auto indexExpr = e->as<AstExprIndexExpr>())
|
||||||
{
|
{
|
||||||
// We need to populate the type for the index value
|
// We need to populate the type for the index value
|
||||||
check(scope, indexExpr->index, ValueContext::RValue);
|
check(scope, indexExpr->index);
|
||||||
if (auto strIndex = indexExpr->index->as<AstExprConstantString>())
|
if (auto strIndex = indexExpr->index->as<AstExprConstantString>())
|
||||||
{
|
{
|
||||||
segments.push_back(std::string(strIndex->value.data, strIndex->value.size));
|
segments.push_back(std::string(strIndex->value.data, strIndex->value.size));
|
||||||
|
@ -2095,11 +2235,11 @@ TypeId ConstraintGraphBuilder::checkLValue(const ScopePtr& scope, AstExpr* expr)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return check(scope, expr, ValueContext::LValue).ty;
|
return check(scope, expr).ty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
return check(scope, expr, ValueContext::LValue).ty;
|
return check(scope, expr).ty;
|
||||||
}
|
}
|
||||||
|
|
||||||
LUAU_ASSERT(!segments.empty());
|
LUAU_ASSERT(!segments.empty());
|
||||||
|
@ -2109,7 +2249,7 @@ TypeId ConstraintGraphBuilder::checkLValue(const ScopePtr& scope, AstExpr* expr)
|
||||||
|
|
||||||
auto lookupResult = scope->lookupEx(sym);
|
auto lookupResult = scope->lookupEx(sym);
|
||||||
if (!lookupResult)
|
if (!lookupResult)
|
||||||
return check(scope, expr, ValueContext::LValue).ty;
|
return check(scope, expr).ty;
|
||||||
const auto [subjectBinding, symbolScope] = std::move(*lookupResult);
|
const auto [subjectBinding, symbolScope] = std::move(*lookupResult);
|
||||||
TypeId subjectType = subjectBinding->typeId;
|
TypeId subjectType = subjectBinding->typeId;
|
||||||
|
|
||||||
|
@ -2139,7 +2279,10 @@ TypeId ConstraintGraphBuilder::checkLValue(const ScopePtr& scope, AstExpr* expr)
|
||||||
// This can fail if the user is erroneously trying to augment a builtin
|
// This can fail if the user is erroneously trying to augment a builtin
|
||||||
// table like os or string.
|
// table like os or string.
|
||||||
if (auto bc = dfg->getBreadcrumb(e))
|
if (auto bc = dfg->getBreadcrumb(e))
|
||||||
symbolScope->dcrRefinements[bc->def] = updatedType;
|
{
|
||||||
|
symbolScope->lvalueTypes[bc->def] = updatedType;
|
||||||
|
symbolScope->rvalueRefinements[bc->def] = updatedType;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return propTy;
|
return propTy;
|
||||||
|
@ -2232,7 +2375,7 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprTable* exp
|
||||||
checkExpectedIndexResultType = pinnedIndexResultType;
|
checkExpectedIndexResultType = pinnedIndexResultType;
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeId itemTy = check(scope, item.value, ValueContext::RValue, checkExpectedIndexResultType).ty;
|
TypeId itemTy = check(scope, item.value, checkExpectedIndexResultType).ty;
|
||||||
|
|
||||||
if (isIndexedResultType && !pinnedIndexResultType)
|
if (isIndexedResultType && !pinnedIndexResultType)
|
||||||
pinnedIndexResultType = itemTy;
|
pinnedIndexResultType = itemTy;
|
||||||
|
@ -2242,7 +2385,7 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprTable* exp
|
||||||
// Even though we don't need to use the type of the item's key if
|
// Even though we don't need to use the type of the item's key if
|
||||||
// it's a string constant, we still want to check it to populate
|
// it's a string constant, we still want to check it to populate
|
||||||
// astTypes.
|
// astTypes.
|
||||||
TypeId keyTy = check(scope, item.key, ValueContext::RValue, annotatedKeyType).ty;
|
TypeId keyTy = check(scope, item.key, annotatedKeyType).ty;
|
||||||
|
|
||||||
if (AstExprConstantString* key = item.key->as<AstExprConstantString>())
|
if (AstExprConstantString* key = item.key->as<AstExprConstantString>())
|
||||||
{
|
{
|
||||||
|
@ -2346,7 +2489,8 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS
|
||||||
signatureScope->bindings[fn->self] = Binding{selfType, fn->self->location};
|
signatureScope->bindings[fn->self] = Binding{selfType, fn->self->location};
|
||||||
|
|
||||||
BreadcrumbId bc = dfg->getBreadcrumb(fn->self);
|
BreadcrumbId bc = dfg->getBreadcrumb(fn->self);
|
||||||
signatureScope->dcrRefinements[bc->def] = selfType;
|
signatureScope->lvalueTypes[bc->def] = selfType;
|
||||||
|
signatureScope->rvalueRefinements[bc->def] = selfType;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (size_t i = 0; i < fn->args.size; ++i)
|
for (size_t i = 0; i < fn->args.size; ++i)
|
||||||
|
@ -2370,7 +2514,8 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS
|
||||||
signatureScope->bindings[local] = Binding{argTy, local->location};
|
signatureScope->bindings[local] = Binding{argTy, local->location};
|
||||||
|
|
||||||
BreadcrumbId bc = dfg->getBreadcrumb(local);
|
BreadcrumbId bc = dfg->getBreadcrumb(local);
|
||||||
signatureScope->dcrRefinements[bc->def] = argTy;
|
signatureScope->lvalueTypes[bc->def] = argTy;
|
||||||
|
signatureScope->rvalueRefinements[bc->def] = argTy;
|
||||||
}
|
}
|
||||||
|
|
||||||
TypePackId varargPack = nullptr;
|
TypePackId varargPack = nullptr;
|
||||||
|
@ -2612,6 +2757,7 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, b
|
||||||
// TODO: FunctionType needs a pointer to the scope so that we know
|
// TODO: FunctionType needs a pointer to the scope so that we know
|
||||||
// how to quantify/instantiate it.
|
// how to quantify/instantiate it.
|
||||||
FunctionType ftv{TypeLevel{}, scope.get(), {}, {}, argTypes, returnTypes};
|
FunctionType ftv{TypeLevel{}, scope.get(), {}, {}, argTypes, returnTypes};
|
||||||
|
ftv.isCheckedFunction = fn->checkedFunction;
|
||||||
|
|
||||||
// This replicates the behavior of the appropriate FunctionType
|
// This replicates the behavior of the appropriate FunctionType
|
||||||
// constructors.
|
// constructors.
|
||||||
|
@ -2836,17 +2982,27 @@ struct GlobalPrepopulator : AstVisitor
|
||||||
{
|
{
|
||||||
const NotNull<Scope> globalScope;
|
const NotNull<Scope> globalScope;
|
||||||
const NotNull<TypeArena> arena;
|
const NotNull<TypeArena> arena;
|
||||||
|
const NotNull<const DataFlowGraph> dfg;
|
||||||
|
|
||||||
GlobalPrepopulator(NotNull<Scope> globalScope, NotNull<TypeArena> arena)
|
GlobalPrepopulator(NotNull<Scope> globalScope, NotNull<TypeArena> arena, NotNull<const DataFlowGraph> dfg)
|
||||||
: globalScope(globalScope)
|
: globalScope(globalScope)
|
||||||
, arena(arena)
|
, arena(arena)
|
||||||
|
, dfg(dfg)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
bool visit(AstStatFunction* function) override
|
bool visit(AstStatFunction* function) override
|
||||||
{
|
{
|
||||||
if (AstExprGlobal* g = function->name->as<AstExprGlobal>())
|
if (AstExprGlobal* g = function->name->as<AstExprGlobal>())
|
||||||
globalScope->bindings[g->name] = Binding{arena->addType(BlockedType{})};
|
{
|
||||||
|
TypeId bt = arena->addType(BlockedType{});
|
||||||
|
globalScope->bindings[g->name] = Binding{bt};
|
||||||
|
|
||||||
|
NullableBreadcrumbId bc = dfg->getBreadcrumb(function->name);
|
||||||
|
LUAU_ASSERT(bc);
|
||||||
|
|
||||||
|
globalScope->lvalueTypes[bc->def] = bt;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -2854,7 +3010,7 @@ struct GlobalPrepopulator : AstVisitor
|
||||||
|
|
||||||
void ConstraintGraphBuilder::prepopulateGlobalScope(const ScopePtr& globalScope, AstStatBlock* program)
|
void ConstraintGraphBuilder::prepopulateGlobalScope(const ScopePtr& globalScope, AstStatBlock* program)
|
||||||
{
|
{
|
||||||
GlobalPrepopulator gp{NotNull{globalScope.get()}, arena};
|
GlobalPrepopulator gp{NotNull{globalScope.get()}, arena, dfg};
|
||||||
|
|
||||||
if (prepareModuleScope)
|
if (prepareModuleScope)
|
||||||
prepareModuleScope(module->name, globalScope);
|
prepareModuleScope(module->name, globalScope);
|
||||||
|
|
|
@ -1344,7 +1344,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
|
||||||
head.insert(head.begin(), fn);
|
head.insert(head.begin(), fn);
|
||||||
|
|
||||||
argsPack = arena->addTypePack(TypePack{std::move(head), tail});
|
argsPack = arena->addTypePack(TypePack{std::move(head), tail});
|
||||||
fn = *callMm;
|
fn = follow(*callMm);
|
||||||
asMutable(c.result)->ty.emplace<FreeTypePack>(constraint->scope);
|
asMutable(c.result)->ty.emplace<FreeTypePack>(constraint->scope);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -1887,6 +1887,9 @@ bool ConstraintSolver::tryDispatch(const RefineConstraint& c, NotNull<const Cons
|
||||||
|
|
||||||
const TypeId type = follow(c.type);
|
const TypeId type = follow(c.type);
|
||||||
|
|
||||||
|
if (hasUnresolvedConstraints(type))
|
||||||
|
return block(type, constraint);
|
||||||
|
|
||||||
LUAU_ASSERT(get<BlockedType>(c.resultType));
|
LUAU_ASSERT(get<BlockedType>(c.resultType));
|
||||||
|
|
||||||
if (type == c.resultType)
|
if (type == c.resultType)
|
||||||
|
@ -1946,8 +1949,7 @@ bool ConstraintSolver::tryDispatch(const RefineConstraint& c, NotNull<const Cons
|
||||||
bool ConstraintSolver::tryDispatch(const ReduceConstraint& c, NotNull<const Constraint> constraint, bool force)
|
bool ConstraintSolver::tryDispatch(const ReduceConstraint& c, NotNull<const Constraint> constraint, bool force)
|
||||||
{
|
{
|
||||||
TypeId ty = follow(c.ty);
|
TypeId ty = follow(c.ty);
|
||||||
FamilyGraphReductionResult result =
|
FamilyGraphReductionResult result = reduceFamilies(ty, constraint->location, TypeFamilyContext{NotNull{this}, constraint->scope}, force);
|
||||||
reduceFamilies(NotNull{this}, ty, constraint->location, constraint->scope, nullptr, force);
|
|
||||||
|
|
||||||
for (TypeId r : result.reducedTypes)
|
for (TypeId r : result.reducedTypes)
|
||||||
unblock(r, constraint->location);
|
unblock(r, constraint->location);
|
||||||
|
@ -1970,8 +1972,7 @@ bool ConstraintSolver::tryDispatch(const ReduceConstraint& c, NotNull<const Cons
|
||||||
bool ConstraintSolver::tryDispatch(const ReducePackConstraint& c, NotNull<const Constraint> constraint, bool force)
|
bool ConstraintSolver::tryDispatch(const ReducePackConstraint& c, NotNull<const Constraint> constraint, bool force)
|
||||||
{
|
{
|
||||||
TypePackId tp = follow(c.tp);
|
TypePackId tp = follow(c.tp);
|
||||||
FamilyGraphReductionResult result =
|
FamilyGraphReductionResult result = reduceFamilies(tp, constraint->location, TypeFamilyContext{NotNull{this}, constraint->scope}, force);
|
||||||
reduceFamilies(NotNull{this}, tp, constraint->location, constraint->scope, nullptr, force);
|
|
||||||
|
|
||||||
for (TypeId r : result.reducedTypes)
|
for (TypeId r : result.reducedTypes)
|
||||||
unblock(r, constraint->location);
|
unblock(r, constraint->location);
|
||||||
|
|
|
@ -55,12 +55,25 @@ std::optional<std::pair<Binding*, Scope*>> Scope::lookupEx(Symbol sym)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<TypeId> Scope::lookupLValue(DefId def) const
|
||||||
|
{
|
||||||
|
for (const Scope* current = this; current; current = current->parent.get())
|
||||||
|
{
|
||||||
|
if (auto ty = current->lvalueTypes.find(def))
|
||||||
|
return *ty;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: We might kill Scope::lookup(Symbol) once data flow is fully fleshed out with type states and control flow analysis.
|
// TODO: We might kill Scope::lookup(Symbol) once data flow is fully fleshed out with type states and control flow analysis.
|
||||||
std::optional<TypeId> Scope::lookup(DefId def) const
|
std::optional<TypeId> Scope::lookup(DefId def) const
|
||||||
{
|
{
|
||||||
for (const Scope* current = this; current; current = current->parent.get())
|
for (const Scope* current = this; current; current = current->parent.get())
|
||||||
{
|
{
|
||||||
if (auto ty = current->dcrRefinements.find(def))
|
if (auto ty = current->rvalueRefinements.find(def))
|
||||||
|
return *ty;
|
||||||
|
if (auto ty = current->lvalueTypes.find(def))
|
||||||
return *ty;
|
return *ty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,10 +169,10 @@ void Scope::inheritRefinements(const ScopePtr& childScope)
|
||||||
{
|
{
|
||||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
{
|
{
|
||||||
for (const auto& [k, a] : childScope->dcrRefinements)
|
for (const auto& [k, a] : childScope->rvalueRefinements)
|
||||||
{
|
{
|
||||||
if (lookup(NotNull{k}))
|
if (lookup(NotNull{k}))
|
||||||
dcrRefinements[k] = a;
|
rvalueRefinements[k] = a;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -101,6 +101,27 @@ SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy)
|
||||||
TypeId lowerBound = makeAggregateType<UnionType>(lb, builtinTypes->neverType);
|
TypeId lowerBound = makeAggregateType<UnionType>(lb, builtinTypes->neverType);
|
||||||
TypeId upperBound = makeAggregateType<IntersectionType>(ub, builtinTypes->unknownType);
|
TypeId upperBound = makeAggregateType<IntersectionType>(ub, builtinTypes->unknownType);
|
||||||
|
|
||||||
|
const NormalizedType* nt = normalizer->normalize(upperBound);
|
||||||
|
if (!nt)
|
||||||
|
result.normalizationTooComplex = true;
|
||||||
|
else if (!normalizer->isInhabited(nt))
|
||||||
|
{
|
||||||
|
/* If the normalized upper bound we're mapping to a generic is
|
||||||
|
* uninhabited, then we must consider the subtyping relation not to
|
||||||
|
* hold.
|
||||||
|
*
|
||||||
|
* This happens eg in <T>() -> (T, T) <: () -> (string, number)
|
||||||
|
*
|
||||||
|
* T appears in covariant position and would have to be both string
|
||||||
|
* and number at once.
|
||||||
|
*
|
||||||
|
* No actual value is both a string and a number, so the test fails.
|
||||||
|
*
|
||||||
|
* TODO: We'll need to add explanitory context here.
|
||||||
|
*/
|
||||||
|
result.isSubtype = false;
|
||||||
|
}
|
||||||
|
|
||||||
result.andAlso(isCovariantWith(lowerBound, upperBound));
|
result.andAlso(isCovariantWith(lowerBound, upperBound));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1044,6 +1044,14 @@ void persist(TypeId ty)
|
||||||
else if (get<GenericType>(t) || get<AnyType>(t) || get<FreeType>(t) || get<SingletonType>(t) || get<PrimitiveType>(t) || get<NegationType>(t))
|
else if (get<GenericType>(t) || get<AnyType>(t) || get<FreeType>(t) || get<SingletonType>(t) || get<PrimitiveType>(t) || get<NegationType>(t))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
else if (auto tfit = get<TypeFamilyInstanceType>(t))
|
||||||
|
{
|
||||||
|
for (auto ty : tfit->typeArguments)
|
||||||
|
queue.push_back(ty);
|
||||||
|
|
||||||
|
for (auto tp : tfit->packArguments)
|
||||||
|
persist(tp);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(!"TypeId is not supported in a persist call");
|
LUAU_ASSERT(!"TypeId is not supported in a persist call");
|
||||||
|
@ -1072,6 +1080,14 @@ void persist(TypePackId tp)
|
||||||
else if (get<GenericTypePack>(tp))
|
else if (get<GenericTypePack>(tp))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
else if (auto tfitp = get<TypeFamilyInstanceTypePack>(tp))
|
||||||
|
{
|
||||||
|
for (auto ty : tfitp->typeArguments)
|
||||||
|
persist(ty);
|
||||||
|
|
||||||
|
for (auto tp : tfitp->packArguments)
|
||||||
|
persist(tp);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(!"TypePackId is not supported in a persist call");
|
LUAU_ASSERT(!"TypePackId is not supported in a persist call");
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
#include "Luau/Type.h"
|
#include "Luau/Type.h"
|
||||||
#include "Luau/TypePack.h"
|
#include "Luau/TypePack.h"
|
||||||
#include "Luau/TypeUtils.h"
|
#include "Luau/TypeUtils.h"
|
||||||
#include "Luau/Unifier.h"
|
|
||||||
#include "Luau/TypeFamily.h"
|
#include "Luau/TypeFamily.h"
|
||||||
#include "Luau/VisitType.h"
|
#include "Luau/VisitType.h"
|
||||||
|
|
||||||
|
@ -242,7 +241,8 @@ struct TypeChecker2
|
||||||
DenseHashSet<TypeId> noTypeFamilyErrors{nullptr};
|
DenseHashSet<TypeId> noTypeFamilyErrors{nullptr};
|
||||||
|
|
||||||
Normalizer normalizer;
|
Normalizer normalizer;
|
||||||
Subtyping subtyping;
|
Subtyping _subtyping;
|
||||||
|
NotNull<Subtyping> subtyping;
|
||||||
|
|
||||||
TypeChecker2(NotNull<BuiltinTypes> builtinTypes, NotNull<UnifierSharedState> unifierState, NotNull<TypeCheckLimits> limits, DcrLogger* logger,
|
TypeChecker2(NotNull<BuiltinTypes> builtinTypes, NotNull<UnifierSharedState> unifierState, NotNull<TypeCheckLimits> limits, DcrLogger* logger,
|
||||||
const SourceModule* sourceModule, Module* module)
|
const SourceModule* sourceModule, Module* module)
|
||||||
|
@ -253,7 +253,9 @@ struct TypeChecker2
|
||||||
, sourceModule(sourceModule)
|
, sourceModule(sourceModule)
|
||||||
, module(module)
|
, module(module)
|
||||||
, normalizer{&testArena, builtinTypes, unifierState, /* cacheInhabitance */ true}
|
, normalizer{&testArena, builtinTypes, unifierState, /* cacheInhabitance */ true}
|
||||||
, subtyping{builtinTypes, NotNull{&testArena}, NotNull{&normalizer}, NotNull{unifierState->iceHandler}, NotNull{module->getModuleScope().get()}}
|
, _subtyping{builtinTypes, NotNull{&testArena}, NotNull{&normalizer}, NotNull{unifierState->iceHandler},
|
||||||
|
NotNull{module->getModuleScope().get()}}
|
||||||
|
, subtyping(&_subtyping)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -282,9 +284,9 @@ struct TypeChecker2
|
||||||
if (noTypeFamilyErrors.find(instance))
|
if (noTypeFamilyErrors.find(instance))
|
||||||
return instance;
|
return instance;
|
||||||
|
|
||||||
TxnLog fake{};
|
ErrorVec errors = reduceFamilies(
|
||||||
ErrorVec errors =
|
instance, location, TypeFamilyContext{NotNull{&testArena}, builtinTypes, stack.back(), NotNull{&normalizer}, ice, limits}, true)
|
||||||
reduceFamilies(instance, location, NotNull{&testArena}, builtinTypes, stack.back(), NotNull{&normalizer}, &fake, true).errors;
|
.errors;
|
||||||
|
|
||||||
if (errors.empty())
|
if (errors.empty())
|
||||||
noTypeFamilyErrors.insert(instance);
|
noTypeFamilyErrors.insert(instance);
|
||||||
|
@ -491,17 +493,7 @@ struct TypeChecker2
|
||||||
TypeArena* arena = &testArena;
|
TypeArena* arena = &testArena;
|
||||||
TypePackId actualRetType = reconstructPack(ret->list, *arena);
|
TypePackId actualRetType = reconstructPack(ret->list, *arena);
|
||||||
|
|
||||||
Unifier u{NotNull{&normalizer}, stack.back(), ret->location, Covariant};
|
testIsSubtype(actualRetType, expectedRetType, ret->location);
|
||||||
u.hideousFixMeGenericsAreActuallyFree = true;
|
|
||||||
|
|
||||||
u.tryUnify(actualRetType, expectedRetType);
|
|
||||||
const bool ok = (u.errors.empty() && u.log.empty()) || isErrorSuppressing(ret->location, actualRetType, ret->location, expectedRetType);
|
|
||||||
|
|
||||||
if (!ok)
|
|
||||||
{
|
|
||||||
for (const TypeError& e : u.errors)
|
|
||||||
reportError(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (AstExpr* expr : ret->list)
|
for (AstExpr* expr : ret->list)
|
||||||
visit(expr, ValueContext::RValue);
|
visit(expr, ValueContext::RValue);
|
||||||
|
@ -532,9 +524,7 @@ struct TypeChecker2
|
||||||
TypeId annotationType = lookupAnnotation(var->annotation);
|
TypeId annotationType = lookupAnnotation(var->annotation);
|
||||||
TypeId valueType = value ? lookupType(value) : nullptr;
|
TypeId valueType = value ? lookupType(value) : nullptr;
|
||||||
if (valueType)
|
if (valueType)
|
||||||
{
|
testIsSubtype(valueType, annotationType, value->location);
|
||||||
reportErrors(tryUnify(stack.back(), value->location, valueType, annotationType));
|
|
||||||
}
|
|
||||||
|
|
||||||
visit(var->annotation);
|
visit(var->annotation);
|
||||||
}
|
}
|
||||||
|
@ -559,7 +549,7 @@ struct TypeChecker2
|
||||||
if (var->annotation)
|
if (var->annotation)
|
||||||
{
|
{
|
||||||
TypeId varType = lookupAnnotation(var->annotation);
|
TypeId varType = lookupAnnotation(var->annotation);
|
||||||
reportErrors(tryUnify(stack.back(), value->location, valueTypes.head[j - i], varType));
|
testIsSubtype(valueTypes.head[j - i], varType, value->location);
|
||||||
|
|
||||||
visit(var->annotation);
|
visit(var->annotation);
|
||||||
}
|
}
|
||||||
|
@ -586,20 +576,20 @@ struct TypeChecker2
|
||||||
|
|
||||||
void visit(AstStatFor* forStatement)
|
void visit(AstStatFor* forStatement)
|
||||||
{
|
{
|
||||||
NotNull<Scope> scope = stack.back();
|
|
||||||
|
|
||||||
if (forStatement->var->annotation)
|
if (forStatement->var->annotation)
|
||||||
{
|
{
|
||||||
visit(forStatement->var->annotation);
|
visit(forStatement->var->annotation);
|
||||||
reportErrors(tryUnify(scope, forStatement->var->location, builtinTypes->numberType, lookupAnnotation(forStatement->var->annotation)));
|
|
||||||
|
TypeId annotatedType = lookupAnnotation(forStatement->var->annotation);
|
||||||
|
testIsSubtype(builtinTypes->numberType, annotatedType, forStatement->var->location);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto checkNumber = [this, scope](AstExpr* expr) {
|
auto checkNumber = [this](AstExpr* expr) {
|
||||||
if (!expr)
|
if (!expr)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
visit(expr, ValueContext::RValue);
|
visit(expr, ValueContext::RValue);
|
||||||
reportErrors(tryUnify(scope, expr->location, lookupType(expr), builtinTypes->numberType));
|
testIsSubtype(lookupType(expr), builtinTypes->numberType, expr->location);
|
||||||
};
|
};
|
||||||
|
|
||||||
checkNumber(forStatement->from);
|
checkNumber(forStatement->from);
|
||||||
|
@ -689,8 +679,7 @@ struct TypeChecker2
|
||||||
}
|
}
|
||||||
TypeId iteratorTy = follow(iteratorTypes.head[0]);
|
TypeId iteratorTy = follow(iteratorTypes.head[0]);
|
||||||
|
|
||||||
auto checkFunction = [this, &arena, &scope, &forInStatement, &variableTypes](
|
auto checkFunction = [this, &arena, &forInStatement, &variableTypes](const FunctionType* iterFtv, std::vector<TypeId> iterTys, bool isMm) {
|
||||||
const FunctionType* iterFtv, std::vector<TypeId> iterTys, bool isMm) {
|
|
||||||
if (iterTys.size() < 1 || iterTys.size() > 3)
|
if (iterTys.size() < 1 || iterTys.size() > 3)
|
||||||
{
|
{
|
||||||
if (isMm)
|
if (isMm)
|
||||||
|
@ -713,7 +702,7 @@ struct TypeChecker2
|
||||||
}
|
}
|
||||||
|
|
||||||
for (size_t i = 0; i < std::min(expectedVariableTypes.head.size(), variableTypes.size()); ++i)
|
for (size_t i = 0; i < std::min(expectedVariableTypes.head.size(), variableTypes.size()); ++i)
|
||||||
reportErrors(tryUnify(scope, forInStatement->vars.data[i]->location, variableTypes[i], expectedVariableTypes.head[i]));
|
testIsSubtype(variableTypes[i], expectedVariableTypes.head[i], forInStatement->vars.data[i]->location);
|
||||||
|
|
||||||
// nextFn is going to be invoked with (arrayTy, startIndexTy)
|
// nextFn is going to be invoked with (arrayTy, startIndexTy)
|
||||||
|
|
||||||
|
@ -757,13 +746,13 @@ struct TypeChecker2
|
||||||
if (iterTys.size() >= 2 && flattenedArgTypes.head.size() > 0)
|
if (iterTys.size() >= 2 && flattenedArgTypes.head.size() > 0)
|
||||||
{
|
{
|
||||||
size_t valueIndex = forInStatement->values.size > 1 ? 1 : 0;
|
size_t valueIndex = forInStatement->values.size > 1 ? 1 : 0;
|
||||||
reportErrors(tryUnify(scope, forInStatement->values.data[valueIndex]->location, iterTys[1], flattenedArgTypes.head[0]));
|
testIsSubtype(iterTys[1], flattenedArgTypes.head[0], forInStatement->values.data[valueIndex]->location);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (iterTys.size() == 3 && flattenedArgTypes.head.size() > 1)
|
if (iterTys.size() == 3 && flattenedArgTypes.head.size() > 1)
|
||||||
{
|
{
|
||||||
size_t valueIndex = forInStatement->values.size > 2 ? 2 : 0;
|
size_t valueIndex = forInStatement->values.size > 2 ? 2 : 0;
|
||||||
reportErrors(tryUnify(scope, forInStatement->values.data[valueIndex]->location, iterTys[2], flattenedArgTypes.head[1]));
|
testIsSubtype(iterTys[2], flattenedArgTypes.head[1], forInStatement->values.data[valueIndex]->location);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -795,9 +784,9 @@ struct TypeChecker2
|
||||||
{
|
{
|
||||||
if ((forInStatement->vars.size == 1 || forInStatement->vars.size == 2) && ttv->indexer)
|
if ((forInStatement->vars.size == 1 || forInStatement->vars.size == 2) && ttv->indexer)
|
||||||
{
|
{
|
||||||
reportErrors(tryUnify(scope, forInStatement->vars.data[0]->location, variableTypes[0], ttv->indexer->indexType));
|
testIsSubtype(variableTypes[0], ttv->indexer->indexType, forInStatement->vars.data[0]->location);
|
||||||
if (variableTypes.size() == 2)
|
if (variableTypes.size() == 2)
|
||||||
reportErrors(tryUnify(scope, forInStatement->vars.data[1]->location, variableTypes[1], ttv->indexer->indexResultType));
|
testIsSubtype(variableTypes[1], ttv->indexer->indexResultType, forInStatement->vars.data[1]->location);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
reportError(GenericError{"Cannot iterate over a table without indexer"}, forInStatement->values.data[0]->location);
|
reportError(GenericError{"Cannot iterate over a table without indexer"}, forInStatement->values.data[0]->location);
|
||||||
|
@ -820,7 +809,7 @@ struct TypeChecker2
|
||||||
if (const FunctionType* iterMmFtv = get<FunctionType>(*instantiatedIterMmTy))
|
if (const FunctionType* iterMmFtv = get<FunctionType>(*instantiatedIterMmTy))
|
||||||
{
|
{
|
||||||
TypePackId argPack = arena.addTypePack({iteratorTy});
|
TypePackId argPack = arena.addTypePack({iteratorTy});
|
||||||
reportErrors(tryUnify(scope, forInStatement->values.data[0]->location, argPack, iterMmFtv->argTypes));
|
testIsSubtype(argPack, iterMmFtv->argTypes, forInStatement->values.data[0]->location);
|
||||||
|
|
||||||
TypePack mmIteratorTypes = extendTypePack(arena, builtinTypes, iterMmFtv->retTypes, 3);
|
TypePack mmIteratorTypes = extendTypePack(arena, builtinTypes, iterMmFtv->retTypes, 3);
|
||||||
|
|
||||||
|
@ -894,12 +883,7 @@ struct TypeChecker2
|
||||||
if (get<NeverType>(lhsType))
|
if (get<NeverType>(lhsType))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
testIsSubtype(rhsType, lhsType, rhs->location);
|
||||||
if (!isSubtype(rhsType, lhsType, stack.back()) &&
|
|
||||||
!isErrorSuppressing(assign->vars.data[i]->location, lhsType, assign->values.data[i]->location, rhsType))
|
|
||||||
{
|
|
||||||
reportError(TypeMismatch{lhsType, rhsType}, rhs->location);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -909,7 +893,7 @@ struct TypeChecker2
|
||||||
TypeId resultTy = visit(&fake, stat);
|
TypeId resultTy = visit(&fake, stat);
|
||||||
TypeId varTy = lookupType(stat->var);
|
TypeId varTy = lookupType(stat->var);
|
||||||
|
|
||||||
reportErrors(tryUnify(stack.back(), stat->location, resultTy, varTy));
|
testIsSubtype(resultTy, varTy, stat->location);
|
||||||
}
|
}
|
||||||
|
|
||||||
void visit(AstStatFunction* stat)
|
void visit(AstStatFunction* stat)
|
||||||
|
@ -1029,34 +1013,38 @@ struct TypeChecker2
|
||||||
|
|
||||||
void visit(AstExprConstantNil* expr)
|
void visit(AstExprConstantNil* expr)
|
||||||
{
|
{
|
||||||
NotNull<Scope> scope = stack.back();
|
|
||||||
TypeId actualType = lookupType(expr);
|
TypeId actualType = lookupType(expr);
|
||||||
TypeId expectedType = builtinTypes->nilType;
|
TypeId expectedType = builtinTypes->nilType;
|
||||||
LUAU_ASSERT(isSubtype(actualType, expectedType, scope));
|
|
||||||
|
SubtypingResult r = subtyping->isSubtype(actualType, expectedType);
|
||||||
|
LUAU_ASSERT(r.isSubtype || r.isErrorSuppressing);
|
||||||
}
|
}
|
||||||
|
|
||||||
void visit(AstExprConstantBool* expr)
|
void visit(AstExprConstantBool* expr)
|
||||||
{
|
{
|
||||||
NotNull<Scope> scope = stack.back();
|
|
||||||
TypeId actualType = lookupType(expr);
|
TypeId actualType = lookupType(expr);
|
||||||
TypeId expectedType = builtinTypes->booleanType;
|
TypeId expectedType = builtinTypes->booleanType;
|
||||||
LUAU_ASSERT(isSubtype(actualType, expectedType, scope));
|
|
||||||
|
SubtypingResult r = subtyping->isSubtype(actualType, expectedType);
|
||||||
|
LUAU_ASSERT(r.isSubtype || r.isErrorSuppressing);
|
||||||
}
|
}
|
||||||
|
|
||||||
void visit(AstExprConstantNumber* expr)
|
void visit(AstExprConstantNumber* expr)
|
||||||
{
|
{
|
||||||
NotNull<Scope> scope = stack.back();
|
|
||||||
TypeId actualType = lookupType(expr);
|
TypeId actualType = lookupType(expr);
|
||||||
TypeId expectedType = builtinTypes->numberType;
|
TypeId expectedType = builtinTypes->numberType;
|
||||||
LUAU_ASSERT(isSubtype(actualType, expectedType, scope));
|
|
||||||
|
SubtypingResult r = subtyping->isSubtype(actualType, expectedType);
|
||||||
|
LUAU_ASSERT(r.isSubtype || r.isErrorSuppressing);
|
||||||
}
|
}
|
||||||
|
|
||||||
void visit(AstExprConstantString* expr)
|
void visit(AstExprConstantString* expr)
|
||||||
{
|
{
|
||||||
NotNull<Scope> scope = stack.back();
|
|
||||||
TypeId actualType = lookupType(expr);
|
TypeId actualType = lookupType(expr);
|
||||||
TypeId expectedType = builtinTypes->stringType;
|
TypeId expectedType = builtinTypes->stringType;
|
||||||
LUAU_ASSERT(isSubtype(actualType, expectedType, scope));
|
|
||||||
|
SubtypingResult r = subtyping->isSubtype(actualType, expectedType);
|
||||||
|
LUAU_ASSERT(r.isSubtype || r.isErrorSuppressing);
|
||||||
}
|
}
|
||||||
|
|
||||||
void visit(AstExprLocal* expr)
|
void visit(AstExprLocal* expr)
|
||||||
|
@ -1089,7 +1077,7 @@ struct TypeChecker2
|
||||||
TypeId fnTy = *originalCallTy;
|
TypeId fnTy = *originalCallTy;
|
||||||
if (selectedOverloadTy)
|
if (selectedOverloadTy)
|
||||||
{
|
{
|
||||||
SubtypingResult result = subtyping.isSubtype(*originalCallTy, *selectedOverloadTy);
|
SubtypingResult result = subtyping->isSubtype(*originalCallTy, *selectedOverloadTy);
|
||||||
if (result.isSubtype)
|
if (result.isSubtype)
|
||||||
fnTy = *selectedOverloadTy;
|
fnTy = *selectedOverloadTy;
|
||||||
|
|
||||||
|
@ -1152,6 +1140,8 @@ struct TypeChecker2
|
||||||
NotNull{&normalizer},
|
NotNull{&normalizer},
|
||||||
NotNull{stack.back()},
|
NotNull{stack.back()},
|
||||||
ice,
|
ice,
|
||||||
|
limits,
|
||||||
|
subtyping,
|
||||||
call->location,
|
call->location,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1243,6 +1233,8 @@ struct TypeChecker2
|
||||||
NotNull<Normalizer> normalizer;
|
NotNull<Normalizer> normalizer;
|
||||||
NotNull<Scope> scope;
|
NotNull<Scope> scope;
|
||||||
NotNull<InternalErrorReporter> ice;
|
NotNull<InternalErrorReporter> ice;
|
||||||
|
NotNull<TypeCheckLimits> limits;
|
||||||
|
NotNull<Subtyping> subtyping;
|
||||||
Location callLoc;
|
Location callLoc;
|
||||||
|
|
||||||
std::vector<TypeId> ok;
|
std::vector<TypeId> ok;
|
||||||
|
@ -1252,32 +1244,38 @@ struct TypeChecker2
|
||||||
InsertionOrderedMap<TypeId, std::pair<Analysis, size_t>> resolution;
|
InsertionOrderedMap<TypeId, std::pair<Analysis, size_t>> resolution;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::optional<ErrorVec> tryUnify(const Location& location, TypeId subTy, TypeId superTy, const LiteralProperties* literalProperties = nullptr)
|
std::optional<ErrorVec> testIsSubtype(const Location& location, TypeId subTy, TypeId superTy)
|
||||||
{
|
{
|
||||||
Unifier u{normalizer, scope, location, Covariant};
|
auto r = subtyping->isSubtype(subTy, superTy);
|
||||||
u.ctx = CountMismatch::Arg;
|
ErrorVec errors;
|
||||||
u.hideousFixMeGenericsAreActuallyFree = true;
|
|
||||||
u.enableNewSolver();
|
|
||||||
u.tryUnify(subTy, superTy, /*isFunctionCall*/ false, /*isIntersection*/ false, literalProperties);
|
|
||||||
|
|
||||||
if (u.errors.empty())
|
if (r.normalizationTooComplex)
|
||||||
|
errors.push_back(TypeError{location, NormalizationTooComplex{}});
|
||||||
|
|
||||||
|
if (!r.isSubtype && !r.isErrorSuppressing)
|
||||||
|
errors.push_back(TypeError{location, TypeMismatch{superTy, subTy}});
|
||||||
|
|
||||||
|
if (errors.empty())
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
|
||||||
return std::move(u.errors);
|
return errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<ErrorVec> tryUnify(const Location& location, TypePackId subTy, TypePackId superTy)
|
std::optional<ErrorVec> testIsSubtype(const Location& location, TypePackId subTy, TypePackId superTy)
|
||||||
{
|
{
|
||||||
Unifier u{normalizer, scope, location, Covariant};
|
auto r = subtyping->isSubtype(subTy, superTy);
|
||||||
u.ctx = CountMismatch::Arg;
|
ErrorVec errors;
|
||||||
u.hideousFixMeGenericsAreActuallyFree = true;
|
|
||||||
u.enableNewSolver();
|
|
||||||
u.tryUnify(subTy, superTy);
|
|
||||||
|
|
||||||
if (u.errors.empty())
|
if (r.normalizationTooComplex)
|
||||||
|
errors.push_back(TypeError{location, NormalizationTooComplex{}});
|
||||||
|
|
||||||
|
if (!r.isSubtype && !r.isErrorSuppressing)
|
||||||
|
errors.push_back(TypeError{location, TypePackMismatch{superTy, subTy}});
|
||||||
|
|
||||||
|
if (errors.empty())
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
|
||||||
return std::move(u.errors);
|
return errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<Analysis, ErrorVec> checkOverload(
|
std::pair<Analysis, ErrorVec> checkOverload(
|
||||||
|
@ -1316,35 +1314,12 @@ struct TypeChecker2
|
||||||
expr->is<AstExprConstantString>() || expr->is<AstExprFunction>() || expr->is<AstExprTable>();
|
expr->is<AstExprConstantString>() || expr->is<AstExprFunction>() || expr->is<AstExprTable>();
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::unique_ptr<LiteralProperties> buildLiteralPropertiesSet(AstExpr* expr)
|
|
||||||
{
|
|
||||||
const AstExprTable* table = expr->as<AstExprTable>();
|
|
||||||
if (!table)
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
std::unique_ptr<LiteralProperties> result = std::make_unique<LiteralProperties>(Name{});
|
|
||||||
|
|
||||||
for (const AstExprTable::Item& item : table->items)
|
|
||||||
{
|
|
||||||
if (item.kind != AstExprTable::Item::Record)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
AstExprConstantString* keyExpr = item.key->as<AstExprConstantString>();
|
|
||||||
LUAU_ASSERT(keyExpr);
|
|
||||||
|
|
||||||
if (isLiteral(item.value))
|
|
||||||
result->insert(Name{keyExpr->value.begin(), keyExpr->value.end()});
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
LUAU_NOINLINE
|
LUAU_NOINLINE
|
||||||
std::pair<Analysis, ErrorVec> checkOverload_(
|
std::pair<Analysis, ErrorVec> checkOverload_(
|
||||||
TypeId fnTy, const FunctionType* fn, const TypePack* args, AstExpr* fnExpr, const std::vector<AstExpr*>* argExprs)
|
TypeId fnTy, const FunctionType* fn, const TypePack* args, AstExpr* fnExpr, const std::vector<AstExpr*>* argExprs)
|
||||||
{
|
{
|
||||||
TxnLog fake;
|
FamilyGraphReductionResult result =
|
||||||
FamilyGraphReductionResult result = reduceFamilies(fnTy, callLoc, arena, builtinTypes, scope, normalizer, &fake, /*force=*/true);
|
reduceFamilies(fnTy, callLoc, TypeFamilyContext{arena, builtinTypes, scope, normalizer, ice, limits}, /*force=*/true);
|
||||||
if (!result.errors.empty())
|
if (!result.errors.empty())
|
||||||
return {OverloadIsNonviable, result.errors};
|
return {OverloadIsNonviable, result.errors};
|
||||||
|
|
||||||
|
@ -1363,9 +1338,7 @@ struct TypeChecker2
|
||||||
TypeId argTy = args->head[argOffset];
|
TypeId argTy = args->head[argOffset];
|
||||||
AstExpr* argLoc = argExprs->at(argOffset >= argExprs->size() ? argExprs->size() - 1 : argOffset);
|
AstExpr* argLoc = argExprs->at(argOffset >= argExprs->size() ? argExprs->size() - 1 : argOffset);
|
||||||
|
|
||||||
std::unique_ptr<LiteralProperties> literalProperties{buildLiteralPropertiesSet(argLoc)};
|
if (auto errors = testIsSubtype(argLoc->location, argTy, paramTy))
|
||||||
|
|
||||||
if (auto errors = tryUnify(argLoc->location, argTy, paramTy, literalProperties.get()))
|
|
||||||
{
|
{
|
||||||
// Since we're stopping right here, we need to decide if this is a nonviable overload or if there is an arity mismatch.
|
// Since we're stopping right here, we need to decide if this is a nonviable overload or if there is an arity mismatch.
|
||||||
// If it's a nonviable overload, then we need to keep going to get all type errors.
|
// If it's a nonviable overload, then we need to keep going to get all type errors.
|
||||||
|
@ -1395,7 +1368,7 @@ struct TypeChecker2
|
||||||
}
|
}
|
||||||
else if (auto vtp = get<VariadicTypePack>(follow(paramIter.tail())))
|
else if (auto vtp = get<VariadicTypePack>(follow(paramIter.tail())))
|
||||||
{
|
{
|
||||||
if (auto errors = tryUnify(argExpr->location, args->head[argOffset], vtp->ty))
|
if (auto errors = testIsSubtype(argExpr->location, args->head[argOffset], vtp->ty))
|
||||||
argumentErrors.insert(argumentErrors.end(), errors->begin(), errors->end());
|
argumentErrors.insert(argumentErrors.end(), errors->begin(), errors->end());
|
||||||
}
|
}
|
||||||
else if (get<GenericTypePack>(follow(paramIter.tail())))
|
else if (get<GenericTypePack>(follow(paramIter.tail())))
|
||||||
|
@ -1414,7 +1387,7 @@ struct TypeChecker2
|
||||||
{
|
{
|
||||||
AstExpr* argExpr = argExprs->at(argExprs->size() - 1);
|
AstExpr* argExpr = argExprs->at(argExprs->size() - 1);
|
||||||
|
|
||||||
if (auto errors = tryUnify(argExpr->location, vtp->ty, *paramIter))
|
if (auto errors = testIsSubtype(argExpr->location, vtp->ty, *paramIter))
|
||||||
argumentErrors.insert(argumentErrors.end(), errors->begin(), errors->end());
|
argumentErrors.insert(argumentErrors.end(), errors->begin(), errors->end());
|
||||||
}
|
}
|
||||||
else if (!isOptional(*paramIter))
|
else if (!isOptional(*paramIter))
|
||||||
|
@ -1439,7 +1412,7 @@ struct TypeChecker2
|
||||||
|
|
||||||
if (paramIter.tail() && args->tail)
|
if (paramIter.tail() && args->tail)
|
||||||
{
|
{
|
||||||
if (auto errors = tryUnify(argLoc, *args->tail, *paramIter.tail()))
|
if (auto errors = testIsSubtype(argLoc, *args->tail, *paramIter.tail()))
|
||||||
argumentErrors.insert(argumentErrors.end(), errors->begin(), errors->end());
|
argumentErrors.insert(argumentErrors.end(), errors->begin(), errors->end());
|
||||||
}
|
}
|
||||||
else if (paramIter.tail())
|
else if (paramIter.tail())
|
||||||
|
@ -1604,20 +1577,18 @@ struct TypeChecker2
|
||||||
visit(indexExpr->expr, ValueContext::RValue);
|
visit(indexExpr->expr, ValueContext::RValue);
|
||||||
visit(indexExpr->index, ValueContext::RValue);
|
visit(indexExpr->index, ValueContext::RValue);
|
||||||
|
|
||||||
NotNull<Scope> scope = stack.back();
|
|
||||||
|
|
||||||
TypeId exprType = lookupType(indexExpr->expr);
|
TypeId exprType = lookupType(indexExpr->expr);
|
||||||
TypeId indexType = lookupType(indexExpr->index);
|
TypeId indexType = lookupType(indexExpr->index);
|
||||||
|
|
||||||
if (auto tt = get<TableType>(exprType))
|
if (auto tt = get<TableType>(exprType))
|
||||||
{
|
{
|
||||||
if (tt->indexer)
|
if (tt->indexer)
|
||||||
reportErrors(tryUnify(scope, indexExpr->index->location, indexType, tt->indexer->indexType));
|
testIsSubtype(indexType, tt->indexer->indexType, indexExpr->index->location);
|
||||||
else
|
else
|
||||||
reportError(CannotExtendTable{exprType, CannotExtendTable::Indexer, "indexer??"}, indexExpr->location);
|
reportError(CannotExtendTable{exprType, CannotExtendTable::Indexer, "indexer??"}, indexExpr->location);
|
||||||
}
|
}
|
||||||
else if (auto cls = get<ClassType>(exprType); cls && cls->indexer)
|
else if (auto cls = get<ClassType>(exprType); cls && cls->indexer)
|
||||||
reportErrors(tryUnify(scope, indexExpr->index->location, indexType, cls->indexer->indexType));
|
testIsSubtype(indexType, cls->indexer->indexType, indexExpr->index->location);
|
||||||
else if (get<UnionType>(exprType) && isOptional(exprType))
|
else if (get<UnionType>(exprType) && isOptional(exprType))
|
||||||
reportError(OptionalValueAccess{exprType}, indexExpr->location);
|
reportError(OptionalValueAccess{exprType}, indexExpr->location);
|
||||||
}
|
}
|
||||||
|
@ -1668,11 +1639,7 @@ struct TypeChecker2
|
||||||
TypeId inferredArgTy = *argIt;
|
TypeId inferredArgTy = *argIt;
|
||||||
TypeId annotatedArgTy = lookupAnnotation(arg->annotation);
|
TypeId annotatedArgTy = lookupAnnotation(arg->annotation);
|
||||||
|
|
||||||
if (!isSubtype(inferredArgTy, annotatedArgTy, stack.back()) &&
|
testIsSubtype(inferredArgTy, annotatedArgTy, arg->location);
|
||||||
!isErrorSuppressing(arg->location, inferredArgTy, arg->annotation->location, annotatedArgTy))
|
|
||||||
{
|
|
||||||
reportError(TypeMismatch{inferredArgTy, annotatedArgTy}, arg->location);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
++argIt;
|
++argIt;
|
||||||
|
@ -1699,7 +1666,6 @@ struct TypeChecker2
|
||||||
{
|
{
|
||||||
visit(expr->expr, ValueContext::RValue);
|
visit(expr->expr, ValueContext::RValue);
|
||||||
|
|
||||||
NotNull<Scope> scope = stack.back();
|
|
||||||
TypeId operandType = lookupType(expr->expr);
|
TypeId operandType = lookupType(expr->expr);
|
||||||
TypeId resultType = lookupType(expr);
|
TypeId resultType = lookupType(expr);
|
||||||
|
|
||||||
|
@ -1717,7 +1683,7 @@ struct TypeChecker2
|
||||||
{
|
{
|
||||||
if (expr->op == AstExprUnary::Op::Len)
|
if (expr->op == AstExprUnary::Op::Len)
|
||||||
{
|
{
|
||||||
reportErrors(tryUnify(scope, expr->location, follow(*ret), builtinTypes->numberType));
|
testIsSubtype(follow(*ret), builtinTypes->numberType, expr->location);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -1737,13 +1703,10 @@ struct TypeChecker2
|
||||||
|
|
||||||
TypeId expectedFunction = testArena.addType(FunctionType{expectedArgs, expectedRet});
|
TypeId expectedFunction = testArena.addType(FunctionType{expectedArgs, expectedRet});
|
||||||
|
|
||||||
ErrorVec errors = tryUnify(scope, expr->location, *mm, expectedFunction);
|
bool success = testIsSubtype(*mm, expectedFunction, expr->location);
|
||||||
if (!errors.empty() && !isErrorSuppressing(expr->expr->location, *firstArg, expr->expr->location, operandType))
|
if (!success)
|
||||||
{
|
|
||||||
reportError(TypeMismatch{*firstArg, operandType}, expr->location);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1768,7 +1731,7 @@ struct TypeChecker2
|
||||||
}
|
}
|
||||||
else if (expr->op == AstExprUnary::Op::Minus)
|
else if (expr->op == AstExprUnary::Op::Minus)
|
||||||
{
|
{
|
||||||
reportErrors(tryUnify(scope, expr->location, operandType, builtinTypes->numberType));
|
testIsSubtype(operandType, builtinTypes->numberType, expr->location);
|
||||||
}
|
}
|
||||||
else if (expr->op == AstExprUnary::Op::Not)
|
else if (expr->op == AstExprUnary::Op::Not)
|
||||||
{
|
{
|
||||||
|
@ -1792,7 +1755,7 @@ struct TypeChecker2
|
||||||
|
|
||||||
TypeId leftType = lookupType(expr->left);
|
TypeId leftType = lookupType(expr->left);
|
||||||
TypeId rightType = lookupType(expr->right);
|
TypeId rightType = lookupType(expr->right);
|
||||||
TypeId expectedResult = lookupType(expr);
|
TypeId expectedResult = follow(lookupType(expr));
|
||||||
|
|
||||||
if (get<TypeFamilyInstanceType>(expectedResult))
|
if (get<TypeFamilyInstanceType>(expectedResult))
|
||||||
{
|
{
|
||||||
|
@ -1928,7 +1891,7 @@ struct TypeChecker2
|
||||||
|
|
||||||
TypeId expectedTy = testArena.addType(FunctionType(expectedArgs, expectedRets));
|
TypeId expectedTy = testArena.addType(FunctionType(expectedArgs, expectedRets));
|
||||||
|
|
||||||
reportErrors(tryUnify(scope, expr->location, follow(*mm), expectedTy));
|
testIsSubtype(follow(*mm), expectedTy, expr->location);
|
||||||
|
|
||||||
std::optional<TypeId> ret = first(ftv->retTypes);
|
std::optional<TypeId> ret = first(ftv->retTypes);
|
||||||
if (ret)
|
if (ret)
|
||||||
|
@ -2022,13 +1985,13 @@ struct TypeChecker2
|
||||||
case AstExprBinary::Op::Mod:
|
case AstExprBinary::Op::Mod:
|
||||||
LUAU_ASSERT(FFlag::LuauFloorDivision || expr->op != AstExprBinary::Op::FloorDiv);
|
LUAU_ASSERT(FFlag::LuauFloorDivision || expr->op != AstExprBinary::Op::FloorDiv);
|
||||||
|
|
||||||
reportErrors(tryUnify(scope, expr->left->location, leftType, builtinTypes->numberType));
|
testIsSubtype(leftType, builtinTypes->numberType, expr->left->location);
|
||||||
reportErrors(tryUnify(scope, expr->right->location, rightType, builtinTypes->numberType));
|
testIsSubtype(rightType, builtinTypes->numberType, expr->right->location);
|
||||||
|
|
||||||
return builtinTypes->numberType;
|
return builtinTypes->numberType;
|
||||||
case AstExprBinary::Op::Concat:
|
case AstExprBinary::Op::Concat:
|
||||||
reportErrors(tryUnify(scope, expr->left->location, leftType, builtinTypes->stringType));
|
testIsSubtype(leftType, builtinTypes->stringType, expr->left->location);
|
||||||
reportErrors(tryUnify(scope, expr->right->location, rightType, builtinTypes->stringType));
|
testIsSubtype(rightType, builtinTypes->stringType, expr->right->location);
|
||||||
|
|
||||||
return builtinTypes->stringType;
|
return builtinTypes->stringType;
|
||||||
case AstExprBinary::Op::CompareGe:
|
case AstExprBinary::Op::CompareGe:
|
||||||
|
@ -2041,12 +2004,12 @@ struct TypeChecker2
|
||||||
|
|
||||||
if (normLeft && normLeft->isExactlyNumber())
|
if (normLeft && normLeft->isExactlyNumber())
|
||||||
{
|
{
|
||||||
reportErrors(tryUnify(scope, expr->right->location, rightType, builtinTypes->numberType));
|
testIsSubtype(rightType, builtinTypes->numberType, expr->right->location);
|
||||||
return builtinTypes->numberType;
|
return builtinTypes->numberType;
|
||||||
}
|
}
|
||||||
else if (normLeft && normLeft->isSubtypeOfString())
|
else if (normLeft && normLeft->isSubtypeOfString())
|
||||||
{
|
{
|
||||||
reportErrors(tryUnify(scope, expr->right->location, rightType, builtinTypes->stringType));
|
testIsSubtype(rightType, builtinTypes->stringType, expr->right->location);
|
||||||
return builtinTypes->stringType;
|
return builtinTypes->stringType;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -2081,10 +2044,10 @@ struct TypeChecker2
|
||||||
TypeId computedType = lookupType(expr->expr);
|
TypeId computedType = lookupType(expr->expr);
|
||||||
|
|
||||||
// Note: As an optimization, we try 'number <: number | string' first, as that is the more likely case.
|
// Note: As an optimization, we try 'number <: number | string' first, as that is the more likely case.
|
||||||
if (isSubtype(annotationType, computedType, stack.back(), true))
|
if (auto r = subtyping->isSubtype(annotationType, computedType); r.isSubtype || r.isErrorSuppressing)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (isSubtype(computedType, annotationType, stack.back(), true))
|
if (auto r = subtyping->isSubtype(computedType, annotationType); r.isSubtype || r.isErrorSuppressing)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
reportError(TypesAreUnrelated{computedType, annotationType}, expr->location);
|
reportError(TypesAreUnrelated{computedType, annotationType}, expr->location);
|
||||||
|
@ -2424,33 +2387,30 @@ struct TypeChecker2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename TID>
|
bool testIsSubtype(TypeId subTy, TypeId superTy, Location location)
|
||||||
bool isSubtype(TID subTy, TID superTy, NotNull<Scope> scope, bool genericsOkay = false)
|
|
||||||
{
|
{
|
||||||
TypeArena arena;
|
SubtypingResult r = subtyping->isSubtype(subTy, superTy);
|
||||||
Unifier u{NotNull{&normalizer}, scope, Location{}, Covariant};
|
|
||||||
u.hideousFixMeGenericsAreActuallyFree = genericsOkay;
|
|
||||||
u.enableNewSolver();
|
|
||||||
|
|
||||||
u.tryUnify(subTy, superTy);
|
if (r.normalizationTooComplex)
|
||||||
const bool ok = u.errors.empty() && u.log.empty();
|
reportError(NormalizationTooComplex{}, location);
|
||||||
return ok;
|
|
||||||
|
if (!r.isSubtype && !r.isErrorSuppressing)
|
||||||
|
reportError(TypeMismatch{superTy, subTy}, location);
|
||||||
|
|
||||||
|
return r.isSubtype;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename TID>
|
bool testIsSubtype(TypePackId subTy, TypePackId superTy, Location location)
|
||||||
ErrorVec tryUnify(NotNull<Scope> scope, const Location& location, TID subTy, TID superTy, CountMismatch::Context context = CountMismatch::Arg,
|
|
||||||
bool genericsOkay = false)
|
|
||||||
{
|
{
|
||||||
Unifier u{NotNull{&normalizer}, scope, location, Covariant};
|
SubtypingResult r = subtyping->isSubtype(subTy, superTy);
|
||||||
u.ctx = context;
|
|
||||||
u.hideousFixMeGenericsAreActuallyFree = genericsOkay;
|
|
||||||
u.enableNewSolver();
|
|
||||||
u.tryUnify(subTy, superTy);
|
|
||||||
|
|
||||||
if (isErrorSuppressing(location, subTy, location, superTy))
|
if (r.normalizationTooComplex)
|
||||||
return {};
|
reportError(NormalizationTooComplex{}, location);
|
||||||
|
|
||||||
return std::move(u.errors);
|
if (!r.isSubtype && !r.isErrorSuppressing)
|
||||||
|
reportError(TypePackMismatch{superTy, subTy}, location);
|
||||||
|
|
||||||
|
return r.isSubtype;
|
||||||
}
|
}
|
||||||
|
|
||||||
void reportError(TypeErrorData data, const Location& location)
|
void reportError(TypeErrorData data, const Location& location)
|
||||||
|
|
|
@ -6,12 +6,13 @@
|
||||||
#include "Luau/DenseHash.h"
|
#include "Luau/DenseHash.h"
|
||||||
#include "Luau/Instantiation.h"
|
#include "Luau/Instantiation.h"
|
||||||
#include "Luau/Normalize.h"
|
#include "Luau/Normalize.h"
|
||||||
|
#include "Luau/Simplify.h"
|
||||||
#include "Luau/Substitution.h"
|
#include "Luau/Substitution.h"
|
||||||
#include "Luau/ToString.h"
|
#include "Luau/ToString.h"
|
||||||
#include "Luau/TxnLog.h"
|
#include "Luau/TxnLog.h"
|
||||||
#include "Luau/TypeCheckLimits.h"
|
#include "Luau/TypeCheckLimits.h"
|
||||||
#include "Luau/TypeUtils.h"
|
#include "Luau/TypeUtils.h"
|
||||||
#include "Luau/Unifier.h"
|
#include "Luau/Unifier2.h"
|
||||||
#include "Luau/VisitType.h"
|
#include "Luau/VisitType.h"
|
||||||
|
|
||||||
LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyGraphReductionMaximumSteps, 1'000'000);
|
LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyGraphReductionMaximumSteps, 1'000'000);
|
||||||
|
@ -56,38 +57,23 @@ struct InstanceCollector : TypeOnceVisitor
|
||||||
|
|
||||||
struct FamilyReducer
|
struct FamilyReducer
|
||||||
{
|
{
|
||||||
ConstraintSolver* solver = nullptr;
|
TypeFamilyContext ctx;
|
||||||
|
|
||||||
// Conditionally from the solver if one is provided.
|
|
||||||
NotNull<TypeArena> arena;
|
|
||||||
NotNull<BuiltinTypes> builtins;
|
|
||||||
NotNull<Normalizer> normalizer;
|
|
||||||
|
|
||||||
std::deque<TypeId> queuedTys;
|
std::deque<TypeId> queuedTys;
|
||||||
std::deque<TypePackId> queuedTps;
|
std::deque<TypePackId> queuedTps;
|
||||||
DenseHashSet<const void*> irreducible{nullptr};
|
DenseHashSet<const void*> irreducible{nullptr};
|
||||||
FamilyGraphReductionResult result;
|
FamilyGraphReductionResult result;
|
||||||
TxnLog* parentLog = nullptr;
|
|
||||||
TxnLog log;
|
|
||||||
bool force = false;
|
bool force = false;
|
||||||
|
|
||||||
// Local to the constraint being reduced.
|
// Local to the constraint being reduced.
|
||||||
Location location;
|
Location location;
|
||||||
NotNull<Scope> scope;
|
|
||||||
|
|
||||||
FamilyReducer(std::deque<TypeId> queuedTys, std::deque<TypePackId> queuedTps, Location location, NotNull<TypeArena> arena,
|
FamilyReducer(std::deque<TypeId> queuedTys, std::deque<TypePackId> queuedTps, Location location, TypeFamilyContext ctx, bool force = false)
|
||||||
NotNull<BuiltinTypes> builtins, NotNull<Scope> scope, NotNull<Normalizer> normalizer, ConstraintSolver* solver, TxnLog* parentLog = nullptr, bool force = false)
|
: ctx(ctx)
|
||||||
: solver(solver)
|
|
||||||
, arena(arena)
|
|
||||||
, builtins(builtins)
|
|
||||||
, normalizer(normalizer)
|
|
||||||
, queuedTys(std::move(queuedTys))
|
, queuedTys(std::move(queuedTys))
|
||||||
, queuedTps(std::move(queuedTps))
|
, queuedTps(std::move(queuedTps))
|
||||||
, parentLog(parentLog)
|
|
||||||
, log(parentLog)
|
|
||||||
, force(force)
|
, force(force)
|
||||||
, location(location)
|
, location(location)
|
||||||
, scope(scope)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,16 +86,16 @@ struct FamilyReducer
|
||||||
|
|
||||||
SkipTestResult testForSkippability(TypeId ty)
|
SkipTestResult testForSkippability(TypeId ty)
|
||||||
{
|
{
|
||||||
ty = log.follow(ty);
|
ty = follow(ty);
|
||||||
|
|
||||||
if (log.is<TypeFamilyInstanceType>(ty))
|
if (is<TypeFamilyInstanceType>(ty))
|
||||||
{
|
{
|
||||||
if (!irreducible.contains(ty))
|
if (!irreducible.contains(ty))
|
||||||
return SkipTestResult::Defer;
|
return SkipTestResult::Defer;
|
||||||
else
|
else
|
||||||
return SkipTestResult::Irreducible;
|
return SkipTestResult::Irreducible;
|
||||||
}
|
}
|
||||||
else if (log.is<GenericType>(ty))
|
else if (is<GenericType>(ty))
|
||||||
{
|
{
|
||||||
return SkipTestResult::Irreducible;
|
return SkipTestResult::Irreducible;
|
||||||
}
|
}
|
||||||
|
@ -119,16 +105,16 @@ struct FamilyReducer
|
||||||
|
|
||||||
SkipTestResult testForSkippability(TypePackId ty)
|
SkipTestResult testForSkippability(TypePackId ty)
|
||||||
{
|
{
|
||||||
ty = log.follow(ty);
|
ty = follow(ty);
|
||||||
|
|
||||||
if (log.is<TypeFamilyInstanceTypePack>(ty))
|
if (is<TypeFamilyInstanceTypePack>(ty))
|
||||||
{
|
{
|
||||||
if (!irreducible.contains(ty))
|
if (!irreducible.contains(ty))
|
||||||
return SkipTestResult::Defer;
|
return SkipTestResult::Defer;
|
||||||
else
|
else
|
||||||
return SkipTestResult::Irreducible;
|
return SkipTestResult::Irreducible;
|
||||||
}
|
}
|
||||||
else if (log.is<GenericTypePack>(ty))
|
else if (is<GenericTypePack>(ty))
|
||||||
{
|
{
|
||||||
return SkipTestResult::Irreducible;
|
return SkipTestResult::Irreducible;
|
||||||
}
|
}
|
||||||
|
@ -139,9 +125,6 @@ struct FamilyReducer
|
||||||
template<typename T>
|
template<typename T>
|
||||||
void replace(T subject, T replacement)
|
void replace(T subject, T replacement)
|
||||||
{
|
{
|
||||||
if (parentLog)
|
|
||||||
parentLog->replace(subject, Unifiable::Bound{replacement});
|
|
||||||
else
|
|
||||||
asMutable(subject)->ty.template emplace<Unifiable::Bound<T>>(replacement);
|
asMutable(subject)->ty.template emplace<Unifiable::Bound<T>>(replacement);
|
||||||
|
|
||||||
if constexpr (std::is_same_v<T, TypeId>)
|
if constexpr (std::is_same_v<T, TypeId>)
|
||||||
|
@ -230,38 +213,36 @@ struct FamilyReducer
|
||||||
|
|
||||||
void stepType()
|
void stepType()
|
||||||
{
|
{
|
||||||
TypeId subject = log.follow(queuedTys.front());
|
TypeId subject = follow(queuedTys.front());
|
||||||
queuedTys.pop_front();
|
queuedTys.pop_front();
|
||||||
|
|
||||||
if (irreducible.contains(subject))
|
if (irreducible.contains(subject))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (const TypeFamilyInstanceType* tfit = log.get<TypeFamilyInstanceType>(subject))
|
if (const TypeFamilyInstanceType* tfit = get<TypeFamilyInstanceType>(subject))
|
||||||
{
|
{
|
||||||
if (!testParameters(subject, tfit))
|
if (!testParameters(subject, tfit))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
TypeFamilyReductionResult<TypeId> result =
|
TypeFamilyReductionResult<TypeId> result = tfit->family->reducer(tfit->typeArguments, tfit->packArguments, NotNull{&ctx});
|
||||||
tfit->family->reducer(tfit->typeArguments, tfit->packArguments, arena, builtins, NotNull{&log}, scope, normalizer, solver);
|
|
||||||
handleFamilyReduction(subject, result);
|
handleFamilyReduction(subject, result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void stepPack()
|
void stepPack()
|
||||||
{
|
{
|
||||||
TypePackId subject = log.follow(queuedTps.front());
|
TypePackId subject = follow(queuedTps.front());
|
||||||
queuedTps.pop_front();
|
queuedTps.pop_front();
|
||||||
|
|
||||||
if (irreducible.contains(subject))
|
if (irreducible.contains(subject))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (const TypeFamilyInstanceTypePack* tfit = log.get<TypeFamilyInstanceTypePack>(subject))
|
if (const TypeFamilyInstanceTypePack* tfit = get<TypeFamilyInstanceTypePack>(subject))
|
||||||
{
|
{
|
||||||
if (!testParameters(subject, tfit))
|
if (!testParameters(subject, tfit))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
TypeFamilyReductionResult<TypePackId> result =
|
TypeFamilyReductionResult<TypePackId> result = tfit->family->reducer(tfit->typeArguments, tfit->packArguments, NotNull{&ctx});
|
||||||
tfit->family->reducer(tfit->typeArguments, tfit->packArguments, arena, builtins, NotNull{&log}, scope, normalizer, solver);
|
|
||||||
handleFamilyReduction(subject, result);
|
handleFamilyReduction(subject, result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -275,11 +256,10 @@ struct FamilyReducer
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
static FamilyGraphReductionResult reduceFamiliesInternal(std::deque<TypeId> queuedTys, std::deque<TypePackId> queuedTps, Location location,
|
static FamilyGraphReductionResult reduceFamiliesInternal(
|
||||||
NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtins, NotNull<Scope> scope, NotNull<Normalizer> normalizer, ConstraintSolver* solver,
|
std::deque<TypeId> queuedTys, std::deque<TypePackId> queuedTps, Location location, TypeFamilyContext ctx, bool force)
|
||||||
TxnLog* log, bool force)
|
|
||||||
{
|
{
|
||||||
FamilyReducer reducer{std::move(queuedTys), std::move(queuedTps), location, arena, builtins, scope, normalizer, solver, log, force};
|
FamilyReducer reducer{std::move(queuedTys), std::move(queuedTps), location, ctx, force};
|
||||||
int iterationCount = 0;
|
int iterationCount = 0;
|
||||||
|
|
||||||
while (!reducer.done())
|
while (!reducer.done())
|
||||||
|
@ -297,8 +277,7 @@ static FamilyGraphReductionResult reduceFamiliesInternal(std::deque<TypeId> queu
|
||||||
return std::move(reducer.result);
|
return std::move(reducer.result);
|
||||||
}
|
}
|
||||||
|
|
||||||
FamilyGraphReductionResult reduceFamilies(TypeId entrypoint, Location location, NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtins,
|
FamilyGraphReductionResult reduceFamilies(TypeId entrypoint, Location location, TypeFamilyContext ctx, bool force)
|
||||||
NotNull<Scope> scope, NotNull<Normalizer> normalizer, TxnLog* log, bool force)
|
|
||||||
{
|
{
|
||||||
InstanceCollector collector;
|
InstanceCollector collector;
|
||||||
|
|
||||||
|
@ -314,11 +293,10 @@ FamilyGraphReductionResult reduceFamilies(TypeId entrypoint, Location location,
|
||||||
if (collector.tys.empty() && collector.tps.empty())
|
if (collector.tys.empty() && collector.tps.empty())
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
return reduceFamiliesInternal(std::move(collector.tys), std::move(collector.tps), location, arena, builtins, scope, normalizer, nullptr, log, force);
|
return reduceFamiliesInternal(std::move(collector.tys), std::move(collector.tps), location, ctx, force);
|
||||||
}
|
}
|
||||||
|
|
||||||
FamilyGraphReductionResult reduceFamilies(TypePackId entrypoint, Location location, NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtins,
|
FamilyGraphReductionResult reduceFamilies(TypePackId entrypoint, Location location, TypeFamilyContext ctx, bool force)
|
||||||
NotNull<Scope> scope, NotNull<Normalizer> normalizer, TxnLog* log, bool force)
|
|
||||||
{
|
{
|
||||||
InstanceCollector collector;
|
InstanceCollector collector;
|
||||||
|
|
||||||
|
@ -334,94 +312,52 @@ FamilyGraphReductionResult reduceFamilies(TypePackId entrypoint, Location locati
|
||||||
if (collector.tys.empty() && collector.tps.empty())
|
if (collector.tys.empty() && collector.tps.empty())
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
return reduceFamiliesInternal(std::move(collector.tys), std::move(collector.tps), location, arena, builtins, scope, normalizer, nullptr, log, force);
|
return reduceFamiliesInternal(std::move(collector.tys), std::move(collector.tps), location, ctx, force);
|
||||||
}
|
}
|
||||||
|
|
||||||
FamilyGraphReductionResult reduceFamilies(
|
bool isPending(TypeId ty, ConstraintSolver* solver)
|
||||||
NotNull<ConstraintSolver> solver, TypeId entrypoint, Location location, NotNull<Scope> scope, TxnLog* log, bool force)
|
|
||||||
{
|
{
|
||||||
InstanceCollector collector;
|
return is<BlockedType>(ty) || is<PendingExpansionType>(ty) || is<TypeFamilyInstanceType>(ty) || (solver && solver->hasUnresolvedConstraints(ty));
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
collector.traverse(entrypoint);
|
|
||||||
}
|
|
||||||
catch (RecursionLimitException&)
|
|
||||||
{
|
|
||||||
return FamilyGraphReductionResult{};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (collector.tys.empty() && collector.tps.empty())
|
|
||||||
return {};
|
|
||||||
|
|
||||||
return reduceFamiliesInternal(std::move(collector.tys), std::move(collector.tps), location, solver->arena, solver->builtinTypes, scope, solver->normalizer, solver.get(), log, force);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FamilyGraphReductionResult reduceFamilies(
|
TypeFamilyReductionResult<TypeId> numericBinopFamilyFn(
|
||||||
NotNull<ConstraintSolver> solver, TypePackId entrypoint, Location location, NotNull<Scope> scope, TxnLog* log, bool force)
|
std::vector<TypeId> typeParams, std::vector<TypePackId> packParams, NotNull<TypeFamilyContext> ctx, const std::string metamethod)
|
||||||
{
|
|
||||||
InstanceCollector collector;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
collector.traverse(entrypoint);
|
|
||||||
}
|
|
||||||
catch (RecursionLimitException&)
|
|
||||||
{
|
|
||||||
return FamilyGraphReductionResult{};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (collector.tys.empty() && collector.tps.empty())
|
|
||||||
return {};
|
|
||||||
|
|
||||||
return reduceFamiliesInternal(std::move(collector.tys), std::move(collector.tps), location, solver->arena, solver->builtinTypes, scope, solver->normalizer, solver.get(), log, force);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isPending(TypeId ty, NotNull<TxnLog> log, ConstraintSolver* solver)
|
|
||||||
{
|
|
||||||
return log->is<BlockedType>(ty) || log->is<PendingExpansionType>(ty) || log->is<TypeFamilyInstanceType>(ty)
|
|
||||||
|| (solver && solver->hasUnresolvedConstraints(ty));
|
|
||||||
}
|
|
||||||
|
|
||||||
TypeFamilyReductionResult<TypeId> addFamilyFn(std::vector<TypeId> typeParams, std::vector<TypePackId> packParams, NotNull<TypeArena> arena,
|
|
||||||
NotNull<BuiltinTypes> builtins, NotNull<TxnLog> log, NotNull<Scope> scope, NotNull<Normalizer> normalizer, ConstraintSolver* solver)
|
|
||||||
{
|
{
|
||||||
if (typeParams.size() != 2 || !packParams.empty())
|
if (typeParams.size() != 2 || !packParams.empty())
|
||||||
{
|
{
|
||||||
// TODO: ICE?
|
ctx->ice->ice("encountered a type family instance without the required argument structure");
|
||||||
LUAU_ASSERT(false);
|
LUAU_ASSERT(false);
|
||||||
return {std::nullopt, true, {}, {}};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeId lhsTy = log->follow(typeParams.at(0));
|
TypeId lhsTy = follow(typeParams.at(0));
|
||||||
TypeId rhsTy = log->follow(typeParams.at(1));
|
TypeId rhsTy = follow(typeParams.at(1));
|
||||||
const NormalizedType* normLhsTy = normalizer->normalize(lhsTy);
|
const NormalizedType* normLhsTy = ctx->normalizer->normalize(lhsTy);
|
||||||
const NormalizedType* normRhsTy = normalizer->normalize(rhsTy);
|
const NormalizedType* normRhsTy = ctx->normalizer->normalize(rhsTy);
|
||||||
if (!normLhsTy || !normRhsTy)
|
if (!normLhsTy || !normRhsTy)
|
||||||
{
|
{
|
||||||
return {std::nullopt, false, {}, {}};
|
return {std::nullopt, false, {}, {}};
|
||||||
}
|
}
|
||||||
else if (log->is<AnyType>(normLhsTy->tops) || log->is<AnyType>(normRhsTy->tops))
|
else if (is<AnyType>(normLhsTy->tops) || is<AnyType>(normRhsTy->tops))
|
||||||
{
|
{
|
||||||
return {builtins->anyType, false, {}, {}};
|
return {ctx->builtins->anyType, false, {}, {}};
|
||||||
}
|
}
|
||||||
else if ((normLhsTy->hasNumbers() || normLhsTy->hasTops()) && (normRhsTy->hasNumbers() || normRhsTy->hasTops()))
|
else if ((normLhsTy->hasNumbers() || normLhsTy->hasTops()) && (normRhsTy->hasNumbers() || normRhsTy->hasTops()))
|
||||||
{
|
{
|
||||||
return {builtins->numberType, false, {}, {}};
|
return {ctx->builtins->numberType, false, {}, {}};
|
||||||
}
|
}
|
||||||
else if (log->is<ErrorType>(lhsTy) || log->is<ErrorType>(rhsTy))
|
else if (is<ErrorType>(lhsTy) || is<ErrorType>(rhsTy))
|
||||||
{
|
{
|
||||||
return {builtins->errorRecoveryType(), false, {}, {}};
|
return {ctx->builtins->errorRecoveryType(), false, {}, {}};
|
||||||
}
|
}
|
||||||
else if (log->is<NeverType>(lhsTy) || log->is<NeverType>(rhsTy))
|
else if (is<NeverType>(lhsTy) || is<NeverType>(rhsTy))
|
||||||
{
|
{
|
||||||
return {builtins->neverType, false, {}, {}};
|
return {ctx->builtins->neverType, false, {}, {}};
|
||||||
}
|
}
|
||||||
else if (isPending(lhsTy, log, solver))
|
else if (isPending(lhsTy, ctx->solver))
|
||||||
{
|
{
|
||||||
return {std::nullopt, false, {lhsTy}, {}};
|
return {std::nullopt, false, {lhsTy}, {}};
|
||||||
}
|
}
|
||||||
else if (isPending(rhsTy, log, solver))
|
else if (isPending(rhsTy, ctx->solver))
|
||||||
{
|
{
|
||||||
return {std::nullopt, false, {rhsTy}, {}};
|
return {std::nullopt, false, {rhsTy}, {}};
|
||||||
}
|
}
|
||||||
|
@ -430,28 +366,28 @@ TypeFamilyReductionResult<TypeId> addFamilyFn(std::vector<TypeId> typeParams, st
|
||||||
// the necessary state to do that, even if we intend to just eat the errors.
|
// the necessary state to do that, even if we intend to just eat the errors.
|
||||||
ErrorVec dummy;
|
ErrorVec dummy;
|
||||||
|
|
||||||
std::optional<TypeId> addMm = findMetatableEntry(builtins, dummy, lhsTy, "__add", Location{});
|
std::optional<TypeId> mmType = findMetatableEntry(ctx->builtins, dummy, lhsTy, metamethod, Location{});
|
||||||
bool reversed = false;
|
bool reversed = false;
|
||||||
if (!addMm)
|
if (!mmType)
|
||||||
{
|
{
|
||||||
addMm = findMetatableEntry(builtins, dummy, rhsTy, "__add", Location{});
|
mmType = findMetatableEntry(ctx->builtins, dummy, rhsTy, metamethod, Location{});
|
||||||
reversed = true;
|
reversed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!addMm)
|
if (!mmType)
|
||||||
return {std::nullopt, true, {}, {}};
|
return {std::nullopt, true, {}, {}};
|
||||||
|
|
||||||
if (isPending(log->follow(*addMm), log, solver))
|
mmType = follow(*mmType);
|
||||||
return {std::nullopt, false, {log->follow(*addMm)}, {}};
|
if (isPending(*mmType, ctx->solver))
|
||||||
|
return {std::nullopt, false, {*mmType}, {}};
|
||||||
|
|
||||||
const FunctionType* mmFtv = log->get<FunctionType>(log->follow(*addMm));
|
const FunctionType* mmFtv = get<FunctionType>(*mmType);
|
||||||
if (!mmFtv)
|
if (!mmFtv)
|
||||||
return {std::nullopt, true, {}, {}};
|
return {std::nullopt, true, {}, {}};
|
||||||
|
|
||||||
TypeCheckLimits limits; // TODO: We need to thread TypeCheckLimits in from Frontend to here.
|
if (std::optional<TypeId> instantiatedMmType = instantiate(ctx->builtins, ctx->arena, ctx->limits, ctx->scope, *mmType))
|
||||||
if (std::optional<TypeId> instantiatedAddMm = instantiate(builtins, arena, NotNull{&limits}, scope, log->follow(*addMm)))
|
|
||||||
{
|
{
|
||||||
if (const FunctionType* instantiatedMmFtv = get<FunctionType>(*instantiatedAddMm))
|
if (const FunctionType* instantiatedMmFtv = get<FunctionType>(*instantiatedMmType))
|
||||||
{
|
{
|
||||||
std::vector<TypeId> inferredArgs;
|
std::vector<TypeId> inferredArgs;
|
||||||
if (!reversed)
|
if (!reversed)
|
||||||
|
@ -459,22 +395,19 @@ TypeFamilyReductionResult<TypeId> addFamilyFn(std::vector<TypeId> typeParams, st
|
||||||
else
|
else
|
||||||
inferredArgs = {rhsTy, lhsTy};
|
inferredArgs = {rhsTy, lhsTy};
|
||||||
|
|
||||||
TypePackId inferredArgPack = arena->addTypePack(std::move(inferredArgs));
|
TypePackId inferredArgPack = ctx->arena->addTypePack(std::move(inferredArgs));
|
||||||
Unifier u{normalizer, scope, Location{}, Variance::Covariant, log.get()};
|
Unifier2 u2{ctx->arena, ctx->builtins, ctx->scope, ctx->ice};
|
||||||
u.tryUnify(inferredArgPack, instantiatedMmFtv->argTypes);
|
if (!u2.unify(inferredArgPack, instantiatedMmFtv->argTypes))
|
||||||
|
return {std::nullopt, true, {}, {}}; // occurs check failed
|
||||||
|
|
||||||
if (std::optional<TypeId> ret = first(instantiatedMmFtv->retTypes); ret && u.errors.empty())
|
if (std::optional<TypeId> ret = first(instantiatedMmFtv->retTypes))
|
||||||
{
|
return {*ret, false, {}, {}};
|
||||||
return {u.log.follow(*ret), false, {}, {}};
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
|
||||||
return {std::nullopt, true, {}, {}};
|
return {std::nullopt, true, {}, {}};
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return {builtins->errorRecoveryType(), false, {}, {}};
|
return {ctx->builtins->errorRecoveryType(), false, {}, {}};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -484,9 +417,171 @@ TypeFamilyReductionResult<TypeId> addFamilyFn(std::vector<TypeId> typeParams, st
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TypeFamilyReductionResult<TypeId> addFamilyFn(std::vector<TypeId> typeParams, std::vector<TypePackId> packParams, NotNull<TypeFamilyContext> ctx)
|
||||||
|
{
|
||||||
|
if (typeParams.size() != 2 || !packParams.empty())
|
||||||
|
{
|
||||||
|
ctx->ice->ice("add type family: encountered a type family instance without the required argument structure");
|
||||||
|
LUAU_ASSERT(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return numericBinopFamilyFn(typeParams, packParams, ctx, "__add");
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeFamilyReductionResult<TypeId> subFamilyFn(std::vector<TypeId> typeParams, std::vector<TypePackId> packParams, NotNull<TypeFamilyContext> ctx)
|
||||||
|
{
|
||||||
|
if (typeParams.size() != 2 || !packParams.empty())
|
||||||
|
{
|
||||||
|
ctx->ice->ice("sub type family: encountered a type family instance without the required argument structure");
|
||||||
|
LUAU_ASSERT(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return numericBinopFamilyFn(typeParams, packParams, ctx, "__sub");
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeFamilyReductionResult<TypeId> mulFamilyFn(std::vector<TypeId> typeParams, std::vector<TypePackId> packParams, NotNull<TypeFamilyContext> ctx)
|
||||||
|
{
|
||||||
|
if (typeParams.size() != 2 || !packParams.empty())
|
||||||
|
{
|
||||||
|
ctx->ice->ice("mul type family: encountered a type family instance without the required argument structure");
|
||||||
|
LUAU_ASSERT(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return numericBinopFamilyFn(typeParams, packParams, ctx, "__mul");
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeFamilyReductionResult<TypeId> divFamilyFn(std::vector<TypeId> typeParams, std::vector<TypePackId> packParams, NotNull<TypeFamilyContext> ctx)
|
||||||
|
{
|
||||||
|
if (typeParams.size() != 2 || !packParams.empty())
|
||||||
|
{
|
||||||
|
ctx->ice->ice("div type family: encountered a type family instance without the required argument structure");
|
||||||
|
LUAU_ASSERT(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return numericBinopFamilyFn(typeParams, packParams, ctx, "__div");
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeFamilyReductionResult<TypeId> idivFamilyFn(std::vector<TypeId> typeParams, std::vector<TypePackId> packParams, NotNull<TypeFamilyContext> ctx)
|
||||||
|
{
|
||||||
|
if (typeParams.size() != 2 || !packParams.empty())
|
||||||
|
{
|
||||||
|
ctx->ice->ice("integer div type family: encountered a type family instance without the required argument structure");
|
||||||
|
LUAU_ASSERT(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return numericBinopFamilyFn(typeParams, packParams, ctx, "__idiv");
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeFamilyReductionResult<TypeId> powFamilyFn(std::vector<TypeId> typeParams, std::vector<TypePackId> packParams, NotNull<TypeFamilyContext> ctx)
|
||||||
|
{
|
||||||
|
if (typeParams.size() != 2 || !packParams.empty())
|
||||||
|
{
|
||||||
|
ctx->ice->ice("pow type family: encountered a type family instance without the required argument structure");
|
||||||
|
LUAU_ASSERT(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return numericBinopFamilyFn(typeParams, packParams, ctx, "__pow");
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeFamilyReductionResult<TypeId> modFamilyFn(std::vector<TypeId> typeParams, std::vector<TypePackId> packParams, NotNull<TypeFamilyContext> ctx)
|
||||||
|
{
|
||||||
|
if (typeParams.size() != 2 || !packParams.empty())
|
||||||
|
{
|
||||||
|
ctx->ice->ice("modulo type family: encountered a type family instance without the required argument structure");
|
||||||
|
LUAU_ASSERT(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return numericBinopFamilyFn(typeParams, packParams, ctx, "__mod");
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeFamilyReductionResult<TypeId> andFamilyFn(std::vector<TypeId> typeParams, std::vector<TypePackId> packParams, NotNull<TypeFamilyContext> ctx)
|
||||||
|
{
|
||||||
|
if (typeParams.size() != 2 || !packParams.empty())
|
||||||
|
{
|
||||||
|
ctx->ice->ice("and type family: encountered a type family instance without the required argument structure");
|
||||||
|
LUAU_ASSERT(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeId lhsTy = follow(typeParams.at(0));
|
||||||
|
TypeId rhsTy = follow(typeParams.at(1));
|
||||||
|
|
||||||
|
if (isPending(lhsTy, ctx->solver))
|
||||||
|
{
|
||||||
|
return {std::nullopt, false, {lhsTy}, {}};
|
||||||
|
}
|
||||||
|
else if (isPending(rhsTy, ctx->solver))
|
||||||
|
{
|
||||||
|
return {std::nullopt, false, {rhsTy}, {}};
|
||||||
|
}
|
||||||
|
|
||||||
|
// And evalutes to a boolean if the LHS is falsey, and the RHS type if LHS is truthy.
|
||||||
|
SimplifyResult filteredLhs = simplifyIntersection(ctx->builtins, ctx->arena, lhsTy, ctx->builtins->falsyType);
|
||||||
|
SimplifyResult overallResult = simplifyUnion(ctx->builtins, ctx->arena, rhsTy, filteredLhs.result);
|
||||||
|
std::vector<TypeId> blockedTypes(filteredLhs.blockedTypes.begin(), filteredLhs.blockedTypes.end());
|
||||||
|
blockedTypes.insert(blockedTypes.end(), overallResult.blockedTypes.begin(), overallResult.blockedTypes.end());
|
||||||
|
return {overallResult.result, false, std::move(blockedTypes), {}};
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeFamilyReductionResult<TypeId> orFamilyFn(std::vector<TypeId> typeParams, std::vector<TypePackId> packParams, NotNull<TypeFamilyContext> ctx)
|
||||||
|
{
|
||||||
|
if (typeParams.size() != 2 || !packParams.empty())
|
||||||
|
{
|
||||||
|
ctx->ice->ice("or type family: encountered a type family instance without the required argument structure");
|
||||||
|
LUAU_ASSERT(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeId lhsTy = follow(typeParams.at(0));
|
||||||
|
TypeId rhsTy = follow(typeParams.at(1));
|
||||||
|
|
||||||
|
if (isPending(lhsTy, ctx->solver))
|
||||||
|
{
|
||||||
|
return {std::nullopt, false, {lhsTy}, {}};
|
||||||
|
}
|
||||||
|
else if (isPending(rhsTy, ctx->solver))
|
||||||
|
{
|
||||||
|
return {std::nullopt, false, {rhsTy}, {}};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Or evalutes to the LHS type if the LHS is truthy, and the RHS type if LHS is falsy.
|
||||||
|
SimplifyResult filteredLhs = simplifyIntersection(ctx->builtins, ctx->arena, lhsTy, ctx->builtins->truthyType);
|
||||||
|
SimplifyResult overallResult = simplifyUnion(ctx->builtins, ctx->arena, rhsTy, filteredLhs.result);
|
||||||
|
std::vector<TypeId> blockedTypes(filteredLhs.blockedTypes.begin(), filteredLhs.blockedTypes.end());
|
||||||
|
blockedTypes.insert(blockedTypes.end(), overallResult.blockedTypes.begin(), overallResult.blockedTypes.end());
|
||||||
|
return {overallResult.result, false, std::move(blockedTypes), {}};
|
||||||
|
}
|
||||||
|
|
||||||
BuiltinTypeFamilies::BuiltinTypeFamilies()
|
BuiltinTypeFamilies::BuiltinTypeFamilies()
|
||||||
: addFamily{"Add", addFamilyFn}
|
: addFamily{"Add", addFamilyFn}
|
||||||
|
, subFamily{"Sub", subFamilyFn}
|
||||||
|
, mulFamily{"Mul", mulFamilyFn}
|
||||||
|
, divFamily{"Div", divFamilyFn}
|
||||||
|
, idivFamily{"FloorDiv", idivFamilyFn}
|
||||||
|
, powFamily{"Exp", powFamilyFn}
|
||||||
|
, modFamily{"Mod", modFamilyFn}
|
||||||
|
, andFamily{"And", andFamilyFn}
|
||||||
|
, orFamily{"Or", orFamilyFn}
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BuiltinTypeFamilies::addToScope(NotNull<TypeArena> arena, NotNull<Scope> scope) const
|
||||||
|
{
|
||||||
|
// make a type function for a two-argument type family
|
||||||
|
auto mkBinaryTypeFamily = [&](const TypeFamily* family) {
|
||||||
|
TypeId t = arena->addType(GenericType{"T"});
|
||||||
|
TypeId u = arena->addType(GenericType{"U"});
|
||||||
|
GenericTypeDefinition genericT{t};
|
||||||
|
GenericTypeDefinition genericU{u};
|
||||||
|
|
||||||
|
return TypeFun{{genericT, genericU}, arena->addType(TypeFamilyInstanceType{NotNull{family}, {t, u}, {}})};
|
||||||
|
};
|
||||||
|
|
||||||
|
scope->exportedTypeBindings[addFamily.name] = mkBinaryTypeFamily(&addFamily);
|
||||||
|
scope->exportedTypeBindings[subFamily.name] = mkBinaryTypeFamily(&subFamily);
|
||||||
|
scope->exportedTypeBindings[mulFamily.name] = mkBinaryTypeFamily(&mulFamily);
|
||||||
|
scope->exportedTypeBindings[divFamily.name] = mkBinaryTypeFamily(&divFamily);
|
||||||
|
scope->exportedTypeBindings[idivFamily.name] = mkBinaryTypeFamily(&idivFamily);
|
||||||
|
scope->exportedTypeBindings[powFamily.name] = mkBinaryTypeFamily(&powFamily);
|
||||||
|
scope->exportedTypeBindings[modFamily.name] = mkBinaryTypeFamily(&modFamily);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Luau
|
} // namespace Luau
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
#ifndef NOMINMAX
|
#ifndef NOMINMAX
|
||||||
#define NOMINMAX
|
#define NOMINMAX
|
||||||
#endif
|
#endif
|
||||||
#include <Windows.h>
|
#include <windows.h>
|
||||||
|
|
||||||
const size_t kPageSize = 4096;
|
const size_t kPageSize = 4096;
|
||||||
#else
|
#else
|
||||||
|
|
|
@ -454,17 +454,21 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
|
||||||
|
|
||||||
if (log.get<TypeFamilyInstanceType>(superTy))
|
if (log.get<TypeFamilyInstanceType>(superTy))
|
||||||
{
|
{
|
||||||
// We do not report errors from reducing here. This is because we will
|
// FIXME: we should be be ICEing here because the old unifier is legacy and should not interact with type families at all.
|
||||||
// "double-report" errors in some cases, like when trying to unify
|
// Unfortunately, there are, at the time of writing, still uses of the old unifier under local type inference.
|
||||||
// identical type family instantiations like Add<false, false> with
|
TypeCheckLimits limits;
|
||||||
// Add<false, false>.
|
reduceFamilies(
|
||||||
reduceFamilies(superTy, location, NotNull(types), builtinTypes, scope, normalizer, &log);
|
superTy, location, TypeFamilyContext{NotNull(types), builtinTypes, scope, normalizer, NotNull{sharedState.iceHandler}, NotNull{&limits}});
|
||||||
superTy = log.follow(superTy);
|
superTy = log.follow(superTy);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (log.get<TypeFamilyInstanceType>(subTy))
|
if (log.get<TypeFamilyInstanceType>(subTy))
|
||||||
{
|
{
|
||||||
reduceFamilies(subTy, location, NotNull(types), builtinTypes, scope, normalizer, &log);
|
// FIXME: we should be be ICEing here because the old unifier is legacy and should not interact with type families at all.
|
||||||
|
// Unfortunately, there are, at the time of writing, still uses of the old unifier under local type inference.
|
||||||
|
TypeCheckLimits limits;
|
||||||
|
reduceFamilies(
|
||||||
|
subTy, location, TypeFamilyContext{NotNull(types), builtinTypes, scope, normalizer, NotNull{sharedState.iceHandler}, NotNull{&limits}});
|
||||||
subTy = log.follow(subTy);
|
subTy = log.follow(subTy);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -154,6 +154,9 @@ bool Unifier2::unify(TypeId subTy, const FunctionType* superFn) {
|
||||||
if (shouldInstantiate)
|
if (shouldInstantiate)
|
||||||
{
|
{
|
||||||
std::optional<TypeId> instantiated = instantiate(builtinTypes, arena, NotNull{&limits}, scope, subTy);
|
std::optional<TypeId> instantiated = instantiate(builtinTypes, arena, NotNull{&limits}, scope, subTy);
|
||||||
|
if (!instantiated)
|
||||||
|
return false;
|
||||||
|
|
||||||
subFn = get<FunctionType>(*instantiated);
|
subFn = get<FunctionType>(*instantiated);
|
||||||
|
|
||||||
LUAU_ASSERT(subFn); // instantiation should not make a function type _not_ a function type.
|
LUAU_ASSERT(subFn); // instantiation should not make a function type _not_ a function type.
|
||||||
|
|
|
@ -795,6 +795,11 @@ public:
|
||||||
const AstArray<AstGenericTypePack>& genericPacks, const AstTypeList& params, const AstArray<AstArgumentName>& paramNames,
|
const AstArray<AstGenericTypePack>& genericPacks, const AstTypeList& params, const AstArray<AstArgumentName>& paramNames,
|
||||||
const AstTypeList& retTypes);
|
const AstTypeList& retTypes);
|
||||||
|
|
||||||
|
AstStatDeclareFunction(const Location& location, const AstName& name, const AstArray<AstGenericType>& generics,
|
||||||
|
const AstArray<AstGenericTypePack>& genericPacks, const AstTypeList& params, const AstArray<AstArgumentName>& paramNames,
|
||||||
|
const AstTypeList& retTypes, bool checkedFunction);
|
||||||
|
|
||||||
|
|
||||||
void visit(AstVisitor* visitor) override;
|
void visit(AstVisitor* visitor) override;
|
||||||
|
|
||||||
AstName name;
|
AstName name;
|
||||||
|
@ -803,6 +808,7 @@ public:
|
||||||
AstTypeList params;
|
AstTypeList params;
|
||||||
AstArray<AstArgumentName> paramNames;
|
AstArray<AstArgumentName> paramNames;
|
||||||
AstTypeList retTypes;
|
AstTypeList retTypes;
|
||||||
|
bool checkedFunction;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct AstDeclaredClassProp
|
struct AstDeclaredClassProp
|
||||||
|
@ -903,6 +909,9 @@ public:
|
||||||
AstTypeFunction(const Location& location, const AstArray<AstGenericType>& generics, const AstArray<AstGenericTypePack>& genericPacks,
|
AstTypeFunction(const Location& location, const AstArray<AstGenericType>& generics, const AstArray<AstGenericTypePack>& genericPacks,
|
||||||
const AstTypeList& argTypes, const AstArray<std::optional<AstArgumentName>>& argNames, const AstTypeList& returnTypes);
|
const AstTypeList& argTypes, const AstArray<std::optional<AstArgumentName>>& argNames, const AstTypeList& returnTypes);
|
||||||
|
|
||||||
|
AstTypeFunction(const Location& location, const AstArray<AstGenericType>& generics, const AstArray<AstGenericTypePack>& genericPacks,
|
||||||
|
const AstTypeList& argTypes, const AstArray<std::optional<AstArgumentName>>& argNames, const AstTypeList& returnTypes, bool checkedFunction);
|
||||||
|
|
||||||
void visit(AstVisitor* visitor) override;
|
void visit(AstVisitor* visitor) override;
|
||||||
|
|
||||||
AstArray<AstGenericType> generics;
|
AstArray<AstGenericType> generics;
|
||||||
|
@ -910,6 +919,7 @@ public:
|
||||||
AstTypeList argTypes;
|
AstTypeList argTypes;
|
||||||
AstArray<std::optional<AstArgumentName>> argNames;
|
AstArray<std::optional<AstArgumentName>> argNames;
|
||||||
AstTypeList returnTypes;
|
AstTypeList returnTypes;
|
||||||
|
bool checkedFunction;
|
||||||
};
|
};
|
||||||
|
|
||||||
class AstTypeTypeof : public AstType
|
class AstTypeTypeof : public AstType
|
||||||
|
|
|
@ -91,7 +91,6 @@ struct Lexeme
|
||||||
BrokenComment,
|
BrokenComment,
|
||||||
BrokenUnicode,
|
BrokenUnicode,
|
||||||
BrokenInterpDoubleBrace,
|
BrokenInterpDoubleBrace,
|
||||||
|
|
||||||
Error,
|
Error,
|
||||||
|
|
||||||
Reserved_BEGIN,
|
Reserved_BEGIN,
|
||||||
|
@ -116,6 +115,7 @@ struct Lexeme
|
||||||
ReservedTrue,
|
ReservedTrue,
|
||||||
ReservedUntil,
|
ReservedUntil,
|
||||||
ReservedWhile,
|
ReservedWhile,
|
||||||
|
ReservedChecked,
|
||||||
Reserved_END
|
Reserved_END
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -176,15 +176,16 @@ private:
|
||||||
|
|
||||||
AstTableIndexer* parseTableIndexer();
|
AstTableIndexer* parseTableIndexer();
|
||||||
|
|
||||||
AstTypeOrPack parseFunctionType(bool allowPack);
|
AstTypeOrPack parseFunctionType(bool allowPack, bool isCheckedFunction = false);
|
||||||
AstType* parseFunctionTypeTail(const Lexeme& begin, AstArray<AstGenericType> generics, AstArray<AstGenericTypePack> genericPacks,
|
AstType* parseFunctionTypeTail(const Lexeme& begin, AstArray<AstGenericType> generics, AstArray<AstGenericTypePack> genericPacks,
|
||||||
AstArray<AstType*> params, AstArray<std::optional<AstArgumentName>> paramNames, AstTypePack* varargAnnotation);
|
AstArray<AstType*> params, AstArray<std::optional<AstArgumentName>> paramNames, AstTypePack* varargAnnotation,
|
||||||
|
bool isCheckedFunction = false);
|
||||||
|
|
||||||
AstType* parseTableType();
|
AstType* parseTableType(bool inDeclarationContext = false);
|
||||||
AstTypeOrPack parseSimpleType(bool allowPack);
|
AstTypeOrPack parseSimpleType(bool allowPack, bool inDeclarationContext = false);
|
||||||
|
|
||||||
AstTypeOrPack parseTypeOrPack();
|
AstTypeOrPack parseTypeOrPack();
|
||||||
AstType* parseType();
|
AstType* parseType(bool inDeclarationContext = false);
|
||||||
|
|
||||||
AstTypePack* parseTypePack();
|
AstTypePack* parseTypePack();
|
||||||
AstTypePack* parseVariadicArgumentTypePack();
|
AstTypePack* parseVariadicArgumentTypePack();
|
||||||
|
|
|
@ -709,6 +709,21 @@ AstStatDeclareFunction::AstStatDeclareFunction(const Location& location, const A
|
||||||
, params(params)
|
, params(params)
|
||||||
, paramNames(paramNames)
|
, paramNames(paramNames)
|
||||||
, retTypes(retTypes)
|
, retTypes(retTypes)
|
||||||
|
, checkedFunction(false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
AstStatDeclareFunction::AstStatDeclareFunction(const Location& location, const AstName& name, const AstArray<AstGenericType>& generics,
|
||||||
|
const AstArray<AstGenericTypePack>& genericPacks, const AstTypeList& params, const AstArray<AstArgumentName>& paramNames,
|
||||||
|
const AstTypeList& retTypes, bool checkedFunction)
|
||||||
|
: AstStat(ClassIndex(), location)
|
||||||
|
, name(name)
|
||||||
|
, generics(generics)
|
||||||
|
, genericPacks(genericPacks)
|
||||||
|
, params(params)
|
||||||
|
, paramNames(paramNames)
|
||||||
|
, retTypes(retTypes)
|
||||||
|
, checkedFunction(checkedFunction)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -817,6 +832,20 @@ AstTypeFunction::AstTypeFunction(const Location& location, const AstArray<AstGen
|
||||||
, argTypes(argTypes)
|
, argTypes(argTypes)
|
||||||
, argNames(argNames)
|
, argNames(argNames)
|
||||||
, returnTypes(returnTypes)
|
, returnTypes(returnTypes)
|
||||||
|
, checkedFunction(false)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(argNames.size == 0 || argNames.size == argTypes.types.size);
|
||||||
|
}
|
||||||
|
|
||||||
|
AstTypeFunction::AstTypeFunction(const Location& location, const AstArray<AstGenericType>& generics, const AstArray<AstGenericTypePack>& genericPacks,
|
||||||
|
const AstTypeList& argTypes, const AstArray<std::optional<AstArgumentName>>& argNames, const AstTypeList& returnTypes, bool checkedFunction)
|
||||||
|
: AstType(ClassIndex(), location)
|
||||||
|
, generics(generics)
|
||||||
|
, genericPacks(genericPacks)
|
||||||
|
, argTypes(argTypes)
|
||||||
|
, argNames(argNames)
|
||||||
|
, returnTypes(returnTypes)
|
||||||
|
, checkedFunction(checkedFunction)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(argNames.size == 0 || argNames.size == argTypes.types.size);
|
LUAU_ASSERT(argNames.size == 0 || argNames.size == argTypes.types.size);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
#include "Luau/Lexer.h"
|
#include "Luau/Lexer.h"
|
||||||
|
|
||||||
|
#include "Luau/Common.h"
|
||||||
#include "Luau/Confusables.h"
|
#include "Luau/Confusables.h"
|
||||||
#include "Luau/StringUtils.h"
|
#include "Luau/StringUtils.h"
|
||||||
|
|
||||||
|
@ -8,6 +9,7 @@
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauFloorDivision, false)
|
LUAU_FASTFLAGVARIABLE(LuauFloorDivision, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauLexerLookaheadRemembersBraceType, false)
|
LUAU_FASTFLAGVARIABLE(LuauLexerLookaheadRemembersBraceType, false)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauCheckedFunctionSyntax, false)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -106,7 +108,7 @@ Lexeme::Lexeme(const Location& location, Type type, const char* name)
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char* kReserved[] = {"and", "break", "do", "else", "elseif", "end", "false", "for", "function", "if", "in", "local", "nil", "not", "or",
|
static const char* kReserved[] = {"and", "break", "do", "else", "elseif", "end", "false", "for", "function", "if", "in", "local", "nil", "not", "or",
|
||||||
"repeat", "return", "then", "true", "until", "while"};
|
"repeat", "return", "then", "true", "until", "while", "@checked"};
|
||||||
|
|
||||||
std::string Lexeme::toString() const
|
std::string Lexeme::toString() const
|
||||||
{
|
{
|
||||||
|
@ -709,7 +711,7 @@ Lexeme Lexer::readNumber(const Position& start, unsigned int startOffset)
|
||||||
|
|
||||||
std::pair<AstName, Lexeme::Type> Lexer::readName()
|
std::pair<AstName, Lexeme::Type> Lexer::readName()
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(isAlpha(peekch()) || peekch() == '_');
|
LUAU_ASSERT(isAlpha(peekch()) || peekch() == '_' || peekch() == '@');
|
||||||
|
|
||||||
unsigned int startOffset = offset;
|
unsigned int startOffset = offset;
|
||||||
|
|
||||||
|
@ -1007,7 +1009,20 @@ Lexeme Lexer::readNext()
|
||||||
|
|
||||||
return Lexeme(Location(start, 1), ch);
|
return Lexeme(Location(start, 1), ch);
|
||||||
}
|
}
|
||||||
|
case '@':
|
||||||
|
{
|
||||||
|
if (FFlag::LuauCheckedFunctionSyntax)
|
||||||
|
{
|
||||||
|
// We're trying to lex the token @checked
|
||||||
|
LUAU_ASSERT(peekch() == '@');
|
||||||
|
|
||||||
|
std::pair<AstName, Lexeme::Type> maybeChecked = readName();
|
||||||
|
if (maybeChecked.second != Lexeme::ReservedChecked)
|
||||||
|
return Lexeme(Location(start, position()), Lexeme::Error);
|
||||||
|
|
||||||
|
return Lexeme(Location(start, position()), maybeChecked.second, maybeChecked.first.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
if (isDigit(peekch()))
|
if (isDigit(peekch()))
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
#include "Luau/Parser.h"
|
#include "Luau/Parser.h"
|
||||||
|
|
||||||
|
#include "Luau/Common.h"
|
||||||
#include "Luau/TimeTrace.h"
|
#include "Luau/TimeTrace.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
@ -15,6 +16,7 @@ LUAU_FASTINTVARIABLE(LuauRecursionLimit, 1000)
|
||||||
LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
|
LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauParseDeclareClassIndexer, false)
|
LUAU_FASTFLAGVARIABLE(LuauParseDeclareClassIndexer, false)
|
||||||
LUAU_FASTFLAG(LuauFloorDivision)
|
LUAU_FASTFLAG(LuauFloorDivision)
|
||||||
|
LUAU_FASTFLAG(LuauCheckedFunctionSyntax)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -823,8 +825,14 @@ AstStat* Parser::parseDeclaration(const Location& start)
|
||||||
if (lexer.current().type == Lexeme::ReservedFunction)
|
if (lexer.current().type == Lexeme::ReservedFunction)
|
||||||
{
|
{
|
||||||
nextLexeme();
|
nextLexeme();
|
||||||
Name globalName = parseName("global function name");
|
bool checkedFunction = false;
|
||||||
|
if (FFlag::LuauCheckedFunctionSyntax && lexer.current().type == Lexeme::ReservedChecked)
|
||||||
|
{
|
||||||
|
checkedFunction = true;
|
||||||
|
nextLexeme();
|
||||||
|
}
|
||||||
|
|
||||||
|
Name globalName = parseName("global function name");
|
||||||
auto [generics, genericPacks] = parseGenericTypeList(/* withDefaultValues= */ false);
|
auto [generics, genericPacks] = parseGenericTypeList(/* withDefaultValues= */ false);
|
||||||
|
|
||||||
MatchLexeme matchParen = lexer.current();
|
MatchLexeme matchParen = lexer.current();
|
||||||
|
@ -860,8 +868,8 @@ AstStat* Parser::parseDeclaration(const Location& start)
|
||||||
if (vararg && !varargAnnotation)
|
if (vararg && !varargAnnotation)
|
||||||
return reportStatError(Location(start, end), {}, {}, "All declaration parameters must be annotated");
|
return reportStatError(Location(start, end), {}, {}, "All declaration parameters must be annotated");
|
||||||
|
|
||||||
return allocator.alloc<AstStatDeclareFunction>(
|
return allocator.alloc<AstStatDeclareFunction>(Location(start, end), globalName.name, generics, genericPacks,
|
||||||
Location(start, end), globalName.name, generics, genericPacks, AstTypeList{copy(vars), varargAnnotation}, copy(varNames), retTypes);
|
AstTypeList{copy(vars), varargAnnotation}, copy(varNames), retTypes, checkedFunction);
|
||||||
}
|
}
|
||||||
else if (AstName(lexer.current().name) == "class")
|
else if (AstName(lexer.current().name) == "class")
|
||||||
{
|
{
|
||||||
|
@ -940,7 +948,7 @@ AstStat* Parser::parseDeclaration(const Location& start)
|
||||||
{
|
{
|
||||||
expectAndConsume(':', "global variable declaration");
|
expectAndConsume(':', "global variable declaration");
|
||||||
|
|
||||||
AstType* type = parseType();
|
AstType* type = parseType(/* in declaration context */ true);
|
||||||
return allocator.alloc<AstStatDeclareGlobal>(Location(start, type->location), globalName->name, type);
|
return allocator.alloc<AstStatDeclareGlobal>(Location(start, type->location), globalName->name, type);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -1302,7 +1310,7 @@ AstTableIndexer* Parser::parseTableIndexer()
|
||||||
// TablePropOrIndexer ::= TableProp | TableIndexer
|
// TablePropOrIndexer ::= TableProp | TableIndexer
|
||||||
// PropList ::= TablePropOrIndexer {fieldsep TablePropOrIndexer} [fieldsep]
|
// PropList ::= TablePropOrIndexer {fieldsep TablePropOrIndexer} [fieldsep]
|
||||||
// TableType ::= `{' PropList `}'
|
// TableType ::= `{' PropList `}'
|
||||||
AstType* Parser::parseTableType()
|
AstType* Parser::parseTableType(bool inDeclarationContext)
|
||||||
{
|
{
|
||||||
incrementRecursionCounter("type annotation");
|
incrementRecursionCounter("type annotation");
|
||||||
|
|
||||||
|
@ -1370,7 +1378,7 @@ AstType* Parser::parseTableType()
|
||||||
|
|
||||||
expectAndConsume(':', "table field");
|
expectAndConsume(':', "table field");
|
||||||
|
|
||||||
AstType* type = parseType();
|
AstType* type = parseType(inDeclarationContext);
|
||||||
|
|
||||||
props.push_back({name->name, name->location, type});
|
props.push_back({name->name, name->location, type});
|
||||||
}
|
}
|
||||||
|
@ -1396,7 +1404,7 @@ AstType* Parser::parseTableType()
|
||||||
|
|
||||||
// ReturnType ::= Type | `(' TypeList `)'
|
// ReturnType ::= Type | `(' TypeList `)'
|
||||||
// FunctionType ::= [`<' varlist `>'] `(' [TypeList] `)' `->` ReturnType
|
// FunctionType ::= [`<' varlist `>'] `(' [TypeList] `)' `->` ReturnType
|
||||||
AstTypeOrPack Parser::parseFunctionType(bool allowPack)
|
AstTypeOrPack Parser::parseFunctionType(bool allowPack, bool isCheckedFunction)
|
||||||
{
|
{
|
||||||
incrementRecursionCounter("type annotation");
|
incrementRecursionCounter("type annotation");
|
||||||
|
|
||||||
|
@ -1444,11 +1452,11 @@ AstTypeOrPack Parser::parseFunctionType(bool allowPack)
|
||||||
|
|
||||||
AstArray<std::optional<AstArgumentName>> paramNames = copy(names);
|
AstArray<std::optional<AstArgumentName>> paramNames = copy(names);
|
||||||
|
|
||||||
return {parseFunctionTypeTail(begin, generics, genericPacks, paramTypes, paramNames, varargAnnotation), {}};
|
return {parseFunctionTypeTail(begin, generics, genericPacks, paramTypes, paramNames, varargAnnotation, isCheckedFunction), {}};
|
||||||
}
|
}
|
||||||
|
|
||||||
AstType* Parser::parseFunctionTypeTail(const Lexeme& begin, AstArray<AstGenericType> generics, AstArray<AstGenericTypePack> genericPacks,
|
AstType* Parser::parseFunctionTypeTail(const Lexeme& begin, AstArray<AstGenericType> generics, AstArray<AstGenericTypePack> genericPacks,
|
||||||
AstArray<AstType*> params, AstArray<std::optional<AstArgumentName>> paramNames, AstTypePack* varargAnnotation)
|
AstArray<AstType*> params, AstArray<std::optional<AstArgumentName>> paramNames, AstTypePack* varargAnnotation, bool isCheckedFunction)
|
||||||
{
|
{
|
||||||
incrementRecursionCounter("type annotation");
|
incrementRecursionCounter("type annotation");
|
||||||
|
|
||||||
|
@ -1472,7 +1480,8 @@ AstType* Parser::parseFunctionTypeTail(const Lexeme& begin, AstArray<AstGenericT
|
||||||
auto [endLocation, returnTypeList] = parseReturnType();
|
auto [endLocation, returnTypeList] = parseReturnType();
|
||||||
|
|
||||||
AstTypeList paramTypes = AstTypeList{params, varargAnnotation};
|
AstTypeList paramTypes = AstTypeList{params, varargAnnotation};
|
||||||
return allocator.alloc<AstTypeFunction>(Location(begin.location, endLocation), generics, genericPacks, paramTypes, paramNames, returnTypeList);
|
return allocator.alloc<AstTypeFunction>(
|
||||||
|
Location(begin.location, endLocation), generics, genericPacks, paramTypes, paramNames, returnTypeList, isCheckedFunction);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type ::=
|
// Type ::=
|
||||||
|
@ -1565,14 +1574,14 @@ AstTypeOrPack Parser::parseTypeOrPack()
|
||||||
return {parseTypeSuffix(type, begin), {}};
|
return {parseTypeSuffix(type, begin), {}};
|
||||||
}
|
}
|
||||||
|
|
||||||
AstType* Parser::parseType()
|
AstType* Parser::parseType(bool inDeclarationContext)
|
||||||
{
|
{
|
||||||
unsigned int oldRecursionCount = recursionCounter;
|
unsigned int oldRecursionCount = recursionCounter;
|
||||||
incrementRecursionCounter("type annotation");
|
incrementRecursionCounter("type annotation");
|
||||||
|
|
||||||
Location begin = lexer.current().location;
|
Location begin = lexer.current().location;
|
||||||
|
|
||||||
AstType* type = parseSimpleType(/* allowPack= */ false).type;
|
AstType* type = parseSimpleType(/* allowPack= */ false, /* in declaration context */ inDeclarationContext).type;
|
||||||
|
|
||||||
recursionCounter = oldRecursionCount;
|
recursionCounter = oldRecursionCount;
|
||||||
|
|
||||||
|
@ -1581,7 +1590,7 @@ AstType* Parser::parseType()
|
||||||
|
|
||||||
// Type ::= nil | Name[`.' Name] [ `<' Type [`,' ...] `>' ] | `typeof' `(' expr `)' | `{' [PropList] `}'
|
// Type ::= nil | Name[`.' Name] [ `<' Type [`,' ...] `>' ] | `typeof' `(' expr `)' | `{' [PropList] `}'
|
||||||
// | [`<' varlist `>'] `(' [TypeList] `)' `->` ReturnType
|
// | [`<' varlist `>'] `(' [TypeList] `)' `->` ReturnType
|
||||||
AstTypeOrPack Parser::parseSimpleType(bool allowPack)
|
AstTypeOrPack Parser::parseSimpleType(bool allowPack, bool inDeclarationContext)
|
||||||
{
|
{
|
||||||
incrementRecursionCounter("type annotation");
|
incrementRecursionCounter("type annotation");
|
||||||
|
|
||||||
|
@ -1673,7 +1682,13 @@ AstTypeOrPack Parser::parseSimpleType(bool allowPack)
|
||||||
}
|
}
|
||||||
else if (lexer.current().type == '{')
|
else if (lexer.current().type == '{')
|
||||||
{
|
{
|
||||||
return {parseTableType(), {}};
|
return {parseTableType(/* inDeclarationContext */ inDeclarationContext), {}};
|
||||||
|
}
|
||||||
|
else if (FFlag::LuauCheckedFunctionSyntax && inDeclarationContext && lexer.current().type == Lexeme::ReservedChecked)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(FFlag::LuauCheckedFunctionSyntax);
|
||||||
|
nextLexeme();
|
||||||
|
return parseFunctionType(allowPack, /* isCheckedFunction */ true);
|
||||||
}
|
}
|
||||||
else if (lexer.current().type == '(' || lexer.current().type == '<')
|
else if (lexer.current().type == '(' || lexer.current().type == '<')
|
||||||
{
|
{
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
#ifndef NOMINMAX
|
#ifndef NOMINMAX
|
||||||
#define NOMINMAX
|
#define NOMINMAX
|
||||||
#endif
|
#endif
|
||||||
#include <Windows.h>
|
#include <windows.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
|
|
|
@ -369,10 +369,9 @@ int main(int argc, char** argv)
|
||||||
stats.bytecode == 0 ? 0.0 : double(stats.codegen) / double(stats.bytecode), stats.readTime, stats.parseTime, stats.compileTime,
|
stats.bytecode == 0 ? 0.0 : double(stats.codegen) / double(stats.bytecode), stats.readTime, stats.parseTime, stats.compileTime,
|
||||||
stats.codegenTime);
|
stats.codegenTime);
|
||||||
|
|
||||||
printf("Lowering stats:\n");
|
printf("Lowering: regalloc failed: %d, lowering failed %d; spills to stack: %d, spills to restore: %d, max spill slot %u\n",
|
||||||
printf("- spills to stack: %d, spills to restore: %d, max spill slot %u\n", stats.lowerStats.spillsToSlot, stats.lowerStats.spillsToRestore,
|
stats.lowerStats.regAllocErrors, stats.lowerStats.loweringErrors, stats.lowerStats.spillsToSlot, stats.lowerStats.spillsToRestore,
|
||||||
stats.lowerStats.maxSpillSlotsUsed);
|
stats.lowerStats.maxSpillSlotsUsed);
|
||||||
printf("- regalloc failed: %d, lowering failed %d\n", stats.lowerStats.regAllocErrors, stats.lowerStats.loweringErrors);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return failed ? 1 : 0;
|
return failed ? 1 : 0;
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
#ifndef NOMINMAX
|
#ifndef NOMINMAX
|
||||||
#define NOMINMAX
|
#define NOMINMAX
|
||||||
#endif
|
#endif
|
||||||
#include <Windows.h>
|
#include <windows.h>
|
||||||
#else
|
#else
|
||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
|
|
|
@ -85,6 +85,7 @@ public:
|
||||||
void test(OperandX64 lhs, OperandX64 rhs);
|
void test(OperandX64 lhs, OperandX64 rhs);
|
||||||
void lea(OperandX64 lhs, OperandX64 rhs);
|
void lea(OperandX64 lhs, OperandX64 rhs);
|
||||||
void setcc(ConditionX64 cond, OperandX64 op);
|
void setcc(ConditionX64 cond, OperandX64 op);
|
||||||
|
void cmov(ConditionX64 cond, RegisterX64 lhs, OperandX64 rhs);
|
||||||
|
|
||||||
void push(OperandX64 op);
|
void push(OperandX64 op);
|
||||||
void pop(OperandX64 op);
|
void pop(OperandX64 op);
|
||||||
|
|
|
@ -17,6 +17,8 @@ enum CodeGenFlags
|
||||||
{
|
{
|
||||||
// Only run native codegen for modules that have been marked with --!native
|
// Only run native codegen for modules that have been marked with --!native
|
||||||
CodeGen_OnlyNativeModules = 1 << 0,
|
CodeGen_OnlyNativeModules = 1 << 0,
|
||||||
|
// Run native codegen for functions that the compiler considers not profitable
|
||||||
|
CodeGen_ColdFunctions = 1 << 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class CodeGenCompilationResult
|
enum class CodeGenCompilationResult
|
||||||
|
|
|
@ -27,6 +27,11 @@ static const char* setccTextForCondition[] = {"seto", "setno", "setc", "setnc",
|
||||||
"setge", "setnb", "setnbe", "setna", "setnae", "setne", "setnl", "setnle", "setng", "setnge", "setz", "setnz", "setp", "setnp"};
|
"setge", "setnb", "setnbe", "setna", "setnae", "setne", "setnl", "setnle", "setng", "setnge", "setz", "setnz", "setp", "setnp"};
|
||||||
static_assert(sizeof(setccTextForCondition) / sizeof(setccTextForCondition[0]) == size_t(ConditionX64::Count), "all conditions have to be covered");
|
static_assert(sizeof(setccTextForCondition) / sizeof(setccTextForCondition[0]) == size_t(ConditionX64::Count), "all conditions have to be covered");
|
||||||
|
|
||||||
|
static const char* cmovTextForCondition[] = {"cmovo", "cmovno", "cmovc", "cmovnc", "cmovb", "cmovbe", "cmova", "cmovae", "cmove", "cmovl", "cmovle",
|
||||||
|
"cmovg", "cmovge", "cmovnb", "cmovnbe", "cmovna", "cmovnae", "cmovne", "cmovnl", "cmovnle", "cmovng", "cmovnge", "cmovz", "cmovnz", "cmovp",
|
||||||
|
"cmovnp"};
|
||||||
|
static_assert(sizeof(cmovTextForCondition) / sizeof(cmovTextForCondition[0]) == size_t(ConditionX64::Count), "all conditions have to be covered");
|
||||||
|
|
||||||
#define OP_PLUS_REG(op, reg) ((op) + (reg & 0x7))
|
#define OP_PLUS_REG(op, reg) ((op) + (reg & 0x7))
|
||||||
#define OP_PLUS_CC(op, cc) ((op) + uint8_t(cc))
|
#define OP_PLUS_CC(op, cc) ((op) + uint8_t(cc))
|
||||||
|
|
||||||
|
@ -404,6 +409,20 @@ void AssemblyBuilderX64::setcc(ConditionX64 cond, OperandX64 op)
|
||||||
commit();
|
commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AssemblyBuilderX64::cmov(ConditionX64 cond, RegisterX64 lhs, OperandX64 rhs)
|
||||||
|
{
|
||||||
|
SizeX64 size = rhs.cat == CategoryX64::reg ? rhs.base.size : rhs.memSize;
|
||||||
|
LUAU_ASSERT(size != SizeX64::byte && size == lhs.size);
|
||||||
|
|
||||||
|
if (logText)
|
||||||
|
log(cmovTextForCondition[size_t(cond)], lhs, rhs);
|
||||||
|
placeRex(lhs, rhs);
|
||||||
|
place(0x0f);
|
||||||
|
place(0x40 | codeForCondition[size_t(cond)]);
|
||||||
|
placeRegAndModRegMem(lhs, rhs);
|
||||||
|
commit();
|
||||||
|
}
|
||||||
|
|
||||||
void AssemblyBuilderX64::jcc(ConditionX64 cond, Label& label)
|
void AssemblyBuilderX64::jcc(ConditionX64 cond, Label& label)
|
||||||
{
|
{
|
||||||
placeJcc(jccTextForCondition[size_t(cond)], label, codeForCondition[size_t(cond)]);
|
placeJcc(jccTextForCondition[size_t(cond)], label, codeForCondition[size_t(cond)]);
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
#ifndef NOMINMAX
|
#ifndef NOMINMAX
|
||||||
#define NOMINMAX
|
#define NOMINMAX
|
||||||
#endif
|
#endif
|
||||||
#include <Windows.h>
|
#include <windows.h>
|
||||||
|
|
||||||
const size_t kPageSize = 4096;
|
const size_t kPageSize = 4096;
|
||||||
#else
|
#else
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
#ifndef NOMINMAX
|
#ifndef NOMINMAX
|
||||||
#define NOMINMAX
|
#define NOMINMAX
|
||||||
#endif
|
#endif
|
||||||
#include <Windows.h>
|
#include <windows.h>
|
||||||
|
|
||||||
#elif defined(__linux__) || defined(__APPLE__)
|
#elif defined(__linux__) || defined(__APPLE__)
|
||||||
|
|
||||||
|
|
|
@ -259,7 +259,7 @@ CodeGenCompilationResult compile(lua_State* L, int idx, unsigned int flags, Comp
|
||||||
return CodeGenCompilationResult::CodeGenNotInitialized;
|
return CodeGenCompilationResult::CodeGenNotInitialized;
|
||||||
|
|
||||||
std::vector<Proto*> protos;
|
std::vector<Proto*> protos;
|
||||||
gatherFunctions(protos, root);
|
gatherFunctions(protos, root, flags);
|
||||||
|
|
||||||
// Skip protos that have been compiled during previous invocations of CodeGen::compile
|
// Skip protos that have been compiled during previous invocations of CodeGen::compile
|
||||||
protos.erase(std::remove_if(protos.begin(), protos.end(),
|
protos.erase(std::remove_if(protos.begin(), protos.end(),
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#include "Luau/UnwindBuilder.h"
|
#include "Luau/UnwindBuilder.h"
|
||||||
|
|
||||||
#include "BitUtils.h"
|
#include "BitUtils.h"
|
||||||
|
#include "CodeGenUtils.h"
|
||||||
#include "NativeState.h"
|
#include "NativeState.h"
|
||||||
#include "EmitCommonA64.h"
|
#include "EmitCommonA64.h"
|
||||||
|
|
||||||
|
@ -96,42 +97,27 @@ static void emitInterrupt(AssemblyBuilderA64& build)
|
||||||
build.br(x0);
|
build.br(x0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void emitReentry(AssemblyBuilderA64& build, ModuleHelpers& helpers)
|
static void emitContinueCall(AssemblyBuilderA64& build, ModuleHelpers& helpers)
|
||||||
{
|
{
|
||||||
// x0 = closure object to reentry (equal to clvalue(L->ci->func))
|
// x0 = closure object to reentry (equal to clvalue(L->ci->func))
|
||||||
|
|
||||||
// If the fallback requested an exit, we need to do this right away
|
// If the fallback yielded, we need to do this right away
|
||||||
build.cbz(x0, helpers.exitNoContinueVm);
|
// note: it's slightly cheaper to check x0 LSB; a valid Closure pointer must be aligned to 8 bytes
|
||||||
|
LUAU_ASSERT(CALL_FALLBACK_YIELD == 1);
|
||||||
emitUpdateBase(build);
|
build.tbnz(x0, 0, helpers.exitNoContinueVm);
|
||||||
|
|
||||||
// Need to update state of the current function before we jump away
|
// Need to update state of the current function before we jump away
|
||||||
build.ldr(x1, mem(x0, offsetof(Closure, l.p))); // cl->l.p aka proto
|
build.ldr(x1, mem(x0, offsetof(Closure, l.p))); // cl->l.p aka proto
|
||||||
|
|
||||||
build.ldr(x2, mem(rState, offsetof(lua_State, ci))); // L->ci
|
build.ldr(x2, mem(x1, offsetof(Proto, exectarget)));
|
||||||
|
build.cbz(x2, helpers.exitContinueVm);
|
||||||
// We need to check if the new frame can be executed natively
|
|
||||||
// TODO: .flags and .savedpc load below can be fused with ldp
|
|
||||||
build.ldr(w3, mem(x2, offsetof(CallInfo, flags)));
|
|
||||||
build.tbz(x3, countrz(LUA_CALLINFO_NATIVE), helpers.exitContinueVm);
|
|
||||||
|
|
||||||
build.mov(rClosure, x0);
|
build.mov(rClosure, x0);
|
||||||
|
|
||||||
LUAU_ASSERT(offsetof(Proto, code) == offsetof(Proto, k) + 8);
|
LUAU_ASSERT(offsetof(Proto, code) == offsetof(Proto, k) + 8);
|
||||||
build.ldp(rConstants, rCode, mem(x1, offsetof(Proto, k))); // proto->k, proto->code
|
build.ldp(rConstants, rCode, mem(x1, offsetof(Proto, k))); // proto->k, proto->code
|
||||||
|
|
||||||
// Get instruction index from instruction pointer
|
build.br(x2);
|
||||||
// To get instruction index from instruction pointer, we need to divide byte offset by 4
|
|
||||||
// But we will actually need to scale instruction index by 4 back to byte offset later so it cancels out
|
|
||||||
build.ldr(x2, mem(x2, offsetof(CallInfo, savedpc))); // L->ci->savedpc
|
|
||||||
build.sub(x2, x2, rCode);
|
|
||||||
|
|
||||||
// Get new instruction location and jump to it
|
|
||||||
LUAU_ASSERT(offsetof(Proto, exectarget) == offsetof(Proto, execdata) + 8);
|
|
||||||
build.ldp(x3, x4, mem(x1, offsetof(Proto, execdata)));
|
|
||||||
build.ldr(w2, mem(x3, x2));
|
|
||||||
build.add(x4, x4, x2);
|
|
||||||
build.br(x4);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void emitReturn(AssemblyBuilderA64& build, ModuleHelpers& helpers)
|
void emitReturn(AssemblyBuilderA64& build, ModuleHelpers& helpers)
|
||||||
|
@ -326,11 +312,6 @@ void assembleHelpers(AssemblyBuilderA64& build, ModuleHelpers& helpers)
|
||||||
build.setLabel(helpers.exitNoContinueVm);
|
build.setLabel(helpers.exitNoContinueVm);
|
||||||
emitExit(build, /* continueInVm */ false);
|
emitExit(build, /* continueInVm */ false);
|
||||||
|
|
||||||
if (build.logText)
|
|
||||||
build.logAppend("; reentry\n");
|
|
||||||
build.setLabel(helpers.reentry);
|
|
||||||
emitReentry(build, helpers);
|
|
||||||
|
|
||||||
if (build.logText)
|
if (build.logText)
|
||||||
build.logAppend("; interrupt\n");
|
build.logAppend("; interrupt\n");
|
||||||
build.setLabel(helpers.interrupt);
|
build.setLabel(helpers.interrupt);
|
||||||
|
@ -340,6 +321,11 @@ void assembleHelpers(AssemblyBuilderA64& build, ModuleHelpers& helpers)
|
||||||
build.logAppend("; return\n");
|
build.logAppend("; return\n");
|
||||||
build.setLabel(helpers.return_);
|
build.setLabel(helpers.return_);
|
||||||
emitReturn(build, helpers);
|
emitReturn(build, helpers);
|
||||||
|
|
||||||
|
if (build.logText)
|
||||||
|
build.logAppend("; continueCall\n");
|
||||||
|
build.setLabel(helpers.continueCall);
|
||||||
|
emitContinueCall(build, helpers);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace A64
|
} // namespace A64
|
||||||
|
|
|
@ -46,7 +46,15 @@ template<typename AssemblyBuilder>
|
||||||
static std::string getAssemblyImpl(AssemblyBuilder& build, const TValue* func, AssemblyOptions options, LoweringStats* stats)
|
static std::string getAssemblyImpl(AssemblyBuilder& build, const TValue* func, AssemblyOptions options, LoweringStats* stats)
|
||||||
{
|
{
|
||||||
std::vector<Proto*> protos;
|
std::vector<Proto*> protos;
|
||||||
gatherFunctions(protos, clvalue(func)->l.p);
|
gatherFunctions(protos, clvalue(func)->l.p, /* flags= */ 0);
|
||||||
|
|
||||||
|
protos.erase(std::remove_if(protos.begin(), protos.end(), [](Proto* p) { return p == nullptr; }), protos.end());
|
||||||
|
|
||||||
|
if (protos.empty())
|
||||||
|
{
|
||||||
|
build.finalize(); // to avoid assertion in AssemblyBuilder dtor
|
||||||
|
return std::string();
|
||||||
|
}
|
||||||
|
|
||||||
ModuleHelpers helpers;
|
ModuleHelpers helpers;
|
||||||
assembleHelpers(build, helpers);
|
assembleHelpers(build, helpers);
|
||||||
|
@ -58,7 +66,6 @@ static std::string getAssemblyImpl(AssemblyBuilder& build, const TValue* func, A
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Proto* p : protos)
|
for (Proto* p : protos)
|
||||||
if (p)
|
|
||||||
{
|
{
|
||||||
IrBuilder ir;
|
IrBuilder ir;
|
||||||
ir.buildFunctionIr(p);
|
ir.buildFunctionIr(p);
|
||||||
|
|
|
@ -29,7 +29,7 @@ namespace Luau
|
||||||
namespace CodeGen
|
namespace CodeGen
|
||||||
{
|
{
|
||||||
|
|
||||||
inline void gatherFunctions(std::vector<Proto*>& results, Proto* proto)
|
inline void gatherFunctions(std::vector<Proto*>& results, Proto* proto, unsigned int flags)
|
||||||
{
|
{
|
||||||
if (results.size() <= size_t(proto->bytecodeid))
|
if (results.size() <= size_t(proto->bytecodeid))
|
||||||
results.resize(proto->bytecodeid + 1);
|
results.resize(proto->bytecodeid + 1);
|
||||||
|
@ -38,10 +38,13 @@ inline void gatherFunctions(std::vector<Proto*>& results, Proto* proto)
|
||||||
if (results[proto->bytecodeid])
|
if (results[proto->bytecodeid])
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// Only compile cold functions if requested
|
||||||
|
if ((proto->flags & LPF_NATIVE_COLD) == 0 || (flags & CodeGen_ColdFunctions) != 0)
|
||||||
results[proto->bytecodeid] = proto;
|
results[proto->bytecodeid] = proto;
|
||||||
|
|
||||||
|
// Recursively traverse child protos even if we aren't compiling this one
|
||||||
for (int i = 0; i < proto->sizep; i++)
|
for (int i = 0; i < proto->sizep; i++)
|
||||||
gatherFunctions(results, proto->p[i]);
|
gatherFunctions(results, proto->p[i], flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename AssemblyBuilder, typename IrLowering>
|
template<typename AssemblyBuilder, typename IrLowering>
|
||||||
|
|
|
@ -275,7 +275,7 @@ Closure* callFallback(lua_State* L, StkId ra, StkId argtop, int nresults)
|
||||||
|
|
||||||
// yield
|
// yield
|
||||||
if (n < 0)
|
if (n < 0)
|
||||||
return NULL;
|
return (Closure*)CALL_FALLBACK_YIELD;
|
||||||
|
|
||||||
// ci is our callinfo, cip is our parent
|
// ci is our callinfo, cip is our parent
|
||||||
CallInfo* ci = L->ci;
|
CallInfo* ci = L->ci;
|
||||||
|
@ -299,8 +299,7 @@ Closure* callFallback(lua_State* L, StkId ra, StkId argtop, int nresults)
|
||||||
L->top = (nresults == LUA_MULTRET) ? res : cip->top;
|
L->top = (nresults == LUA_MULTRET) ? res : cip->top;
|
||||||
|
|
||||||
// keep executing current function
|
// keep executing current function
|
||||||
LUAU_ASSERT(isLua(cip));
|
return NULL;
|
||||||
return clvalue(cip->func);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,8 @@ void forgPrepXnextFallback(lua_State* L, TValue* ra, int pc);
|
||||||
Closure* callProlog(lua_State* L, TValue* ra, StkId argtop, int nresults);
|
Closure* callProlog(lua_State* L, TValue* ra, StkId argtop, int nresults);
|
||||||
void callEpilogC(lua_State* L, int nresults, int n);
|
void callEpilogC(lua_State* L, int nresults, int n);
|
||||||
|
|
||||||
|
#define CALL_FALLBACK_YIELD 1
|
||||||
|
|
||||||
Closure* callFallback(lua_State* L, StkId ra, StkId argtop, int nresults);
|
Closure* callFallback(lua_State* L, StkId ra, StkId argtop, int nresults);
|
||||||
|
|
||||||
const Instruction* executeGETGLOBAL(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
const Instruction* executeGETGLOBAL(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||||
|
|
|
@ -240,11 +240,6 @@ void assembleHelpers(X64::AssemblyBuilderX64& build, ModuleHelpers& helpers)
|
||||||
build.setLabel(helpers.exitNoContinueVm);
|
build.setLabel(helpers.exitNoContinueVm);
|
||||||
emitExit(build, /* continueInVm */ false);
|
emitExit(build, /* continueInVm */ false);
|
||||||
|
|
||||||
if (build.logText)
|
|
||||||
build.logAppend("; continueCallInVm\n");
|
|
||||||
build.setLabel(helpers.continueCallInVm);
|
|
||||||
emitContinueCallInVm(build);
|
|
||||||
|
|
||||||
if (build.logText)
|
if (build.logText)
|
||||||
build.logAppend("; interrupt\n");
|
build.logAppend("; interrupt\n");
|
||||||
build.setLabel(helpers.interrupt);
|
build.setLabel(helpers.interrupt);
|
||||||
|
|
|
@ -29,11 +29,8 @@ struct ModuleHelpers
|
||||||
Label return_;
|
Label return_;
|
||||||
Label interrupt;
|
Label interrupt;
|
||||||
|
|
||||||
// X64
|
|
||||||
Label continueCallInVm;
|
|
||||||
|
|
||||||
// A64
|
// A64
|
||||||
Label reentry; // x0: closure
|
Label continueCall; // x0: closure
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace CodeGen
|
} // namespace CodeGen
|
||||||
|
|
|
@ -378,17 +378,6 @@ void emitUpdatePcForExit(AssemblyBuilderX64& build)
|
||||||
build.mov(qword[rax + offsetof(CallInfo, savedpc)], rdx);
|
build.mov(qword[rax + offsetof(CallInfo, savedpc)], rdx);
|
||||||
}
|
}
|
||||||
|
|
||||||
void emitContinueCallInVm(AssemblyBuilderX64& build)
|
|
||||||
{
|
|
||||||
RegisterX64 proto = rcx; // Sync with emitInstCall
|
|
||||||
|
|
||||||
build.mov(rdx, qword[proto + offsetof(Proto, code)]);
|
|
||||||
build.mov(rax, qword[rState + offsetof(lua_State, ci)]);
|
|
||||||
build.mov(qword[rax + offsetof(CallInfo, savedpc)], rdx);
|
|
||||||
|
|
||||||
emitExit(build, /* continueInVm */ true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void emitReturn(AssemblyBuilderX64& build, ModuleHelpers& helpers)
|
void emitReturn(AssemblyBuilderX64& build, ModuleHelpers& helpers)
|
||||||
{
|
{
|
||||||
// input: res in rdi, number of written values in ecx
|
// input: res in rdi, number of written values in ecx
|
||||||
|
|
|
@ -43,7 +43,7 @@ constexpr RegisterX64 rNativeContext = r13; // NativeContext* context
|
||||||
constexpr RegisterX64 rConstants = r12; // TValue* k
|
constexpr RegisterX64 rConstants = r12; // TValue* k
|
||||||
|
|
||||||
constexpr unsigned kExtraLocals = 3; // Number of 8 byte slots available for specialized local variables specified below
|
constexpr unsigned kExtraLocals = 3; // Number of 8 byte slots available for specialized local variables specified below
|
||||||
constexpr unsigned kSpillSlots = 5; // Number of 8 byte slots available for register allocator to spill data into
|
constexpr unsigned kSpillSlots = 13; // Number of 8 byte slots available for register allocator to spill data into
|
||||||
static_assert((kExtraLocals + kSpillSlots) * 8 % 16 == 0, "locals have to preserve 16 byte alignment");
|
static_assert((kExtraLocals + kSpillSlots) * 8 % 16 == 0, "locals have to preserve 16 byte alignment");
|
||||||
|
|
||||||
constexpr uint8_t kWindowsFirstNonVolXmmReg = 6;
|
constexpr uint8_t kWindowsFirstNonVolXmmReg = 6;
|
||||||
|
@ -216,7 +216,6 @@ void emitInterrupt(AssemblyBuilderX64& build);
|
||||||
void emitFallback(IrRegAllocX64& regs, AssemblyBuilderX64& build, int offset, int pcpos);
|
void emitFallback(IrRegAllocX64& regs, AssemblyBuilderX64& build, int offset, int pcpos);
|
||||||
|
|
||||||
void emitUpdatePcForExit(AssemblyBuilderX64& build);
|
void emitUpdatePcForExit(AssemblyBuilderX64& build);
|
||||||
void emitContinueCallInVm(AssemblyBuilderX64& build);
|
|
||||||
|
|
||||||
void emitReturn(AssemblyBuilderX64& build, ModuleHelpers& helpers);
|
void emitReturn(AssemblyBuilderX64& build, ModuleHelpers& helpers);
|
||||||
|
|
||||||
|
|
|
@ -79,36 +79,34 @@ void emitInstCall(AssemblyBuilderX64& build, ModuleHelpers& helpers, int ra, int
|
||||||
|
|
||||||
// Set L->top to ci->top as most function expect (no vararg)
|
// Set L->top to ci->top as most function expect (no vararg)
|
||||||
build.mov(rax, qword[ci + offsetof(CallInfo, top)]);
|
build.mov(rax, qword[ci + offsetof(CallInfo, top)]);
|
||||||
build.mov(qword[rState + offsetof(lua_State, top)], rax);
|
|
||||||
|
|
||||||
// But if it is vararg, update it to 'argi'
|
// But if it is vararg, update it to 'argi'
|
||||||
Label skipVararg;
|
Label skipVararg;
|
||||||
|
|
||||||
build.test(byte[proto + offsetof(Proto, is_vararg)], 1);
|
build.test(byte[proto + offsetof(Proto, is_vararg)], 1);
|
||||||
build.jcc(ConditionX64::Zero, skipVararg);
|
build.jcc(ConditionX64::Zero, skipVararg);
|
||||||
|
build.mov(rax, argi);
|
||||||
|
|
||||||
build.mov(qword[rState + offsetof(lua_State, top)], argi);
|
|
||||||
build.setLabel(skipVararg);
|
build.setLabel(skipVararg);
|
||||||
|
|
||||||
// Keep executing new function
|
build.mov(qword[rState + offsetof(lua_State, top)], rax);
|
||||||
|
|
||||||
|
// Switch current code
|
||||||
// ci->savedpc = p->code;
|
// ci->savedpc = p->code;
|
||||||
build.mov(rax, qword[proto + offsetof(Proto, code)]);
|
build.mov(rax, qword[proto + offsetof(Proto, code)]);
|
||||||
|
build.mov(sCode, rax); // note: this needs to be before the next store for optimal performance
|
||||||
build.mov(qword[ci + offsetof(CallInfo, savedpc)], rax);
|
build.mov(qword[ci + offsetof(CallInfo, savedpc)], rax);
|
||||||
|
|
||||||
// Get native function entry
|
|
||||||
build.mov(rax, qword[proto + offsetof(Proto, exectarget)]);
|
|
||||||
build.test(rax, rax);
|
|
||||||
build.jcc(ConditionX64::Zero, helpers.continueCallInVm);
|
|
||||||
|
|
||||||
// Mark call frame as native
|
|
||||||
build.mov(dword[ci + offsetof(CallInfo, flags)], LUA_CALLINFO_NATIVE);
|
|
||||||
|
|
||||||
// Switch current constants
|
// Switch current constants
|
||||||
build.mov(rConstants, qword[proto + offsetof(Proto, k)]);
|
build.mov(rConstants, qword[proto + offsetof(Proto, k)]);
|
||||||
|
|
||||||
// Switch current code
|
// Get native function entry
|
||||||
build.mov(rdx, qword[proto + offsetof(Proto, code)]);
|
build.mov(rax, qword[proto + offsetof(Proto, exectarget)]);
|
||||||
build.mov(sCode, rdx);
|
build.test(rax, rax);
|
||||||
|
build.jcc(ConditionX64::Zero, helpers.exitContinueVm);
|
||||||
|
|
||||||
|
// Mark call frame as native
|
||||||
|
build.mov(dword[ci + offsetof(CallInfo, flags)], LUA_CALLINFO_NATIVE);
|
||||||
|
|
||||||
build.jmp(rax);
|
build.jmp(rax);
|
||||||
}
|
}
|
||||||
|
|
|
@ -146,6 +146,11 @@ void IrBuilder::buildFunctionIr(Proto* proto)
|
||||||
if (instIndexToBlock[i] != kNoAssociatedBlockIndex)
|
if (instIndexToBlock[i] != kNoAssociatedBlockIndex)
|
||||||
beginBlock(blockAtInst(i));
|
beginBlock(blockAtInst(i));
|
||||||
|
|
||||||
|
// Numeric for loops require additional processing to maintain loop stack
|
||||||
|
// Notably, this must be performed even when the block is dead so that we maintain the pairing FORNPREP-FORNLOOP
|
||||||
|
if (op == LOP_FORNPREP)
|
||||||
|
beforeInstForNPrep(*this, pc);
|
||||||
|
|
||||||
// We skip dead bytecode instructions when they appear after block was already terminated
|
// We skip dead bytecode instructions when they appear after block was already terminated
|
||||||
if (!inTerminatedBlock)
|
if (!inTerminatedBlock)
|
||||||
{
|
{
|
||||||
|
@ -164,6 +169,10 @@ void IrBuilder::buildFunctionIr(Proto* proto)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// See above for FORNPREP..FORNLOOP processing
|
||||||
|
if (op == LOP_FORNLOOP)
|
||||||
|
afterInstForNLoop(*this, pc);
|
||||||
|
|
||||||
i = nexti;
|
i = nexti;
|
||||||
LUAU_ASSERT(i <= proto->sizecode);
|
LUAU_ASSERT(i <= proto->sizecode);
|
||||||
|
|
||||||
|
|
|
@ -1515,8 +1515,10 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next)
|
||||||
build.ldr(x4, mem(rNativeContext, offsetof(NativeContext, callFallback)));
|
build.ldr(x4, mem(rNativeContext, offsetof(NativeContext, callFallback)));
|
||||||
build.blr(x4);
|
build.blr(x4);
|
||||||
|
|
||||||
// reentry with x0=closure (NULL will trigger exit)
|
emitUpdateBase(build);
|
||||||
build.b(helpers.reentry);
|
|
||||||
|
// reentry with x0=closure (NULL implies C function; CALL_FALLBACK_YIELD will trigger exit)
|
||||||
|
build.cbnz(x0, helpers.continueCall);
|
||||||
break;
|
break;
|
||||||
case IrCmd::RETURN:
|
case IrCmd::RETURN:
|
||||||
regs.spill(build, index);
|
regs.spill(build, index);
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(DebugLuauCodegenChaosA64, false)
|
LUAU_FASTFLAGVARIABLE(DebugCodegenChaosA64, false)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -146,7 +146,7 @@ RegisterA64 IrRegAllocA64::allocReg(KindA64 kind, uint32_t index)
|
||||||
|
|
||||||
int reg = 31 - countlz(set.free);
|
int reg = 31 - countlz(set.free);
|
||||||
|
|
||||||
if (FFlag::DebugLuauCodegenChaosA64)
|
if (FFlag::DebugCodegenChaosA64)
|
||||||
reg = countrz(set.free); // allocate from low end; this causes extra conflicts for calls
|
reg = countrz(set.free); // allocate from low end; this causes extra conflicts for calls
|
||||||
|
|
||||||
set.free &= ~(1u << reg);
|
set.free &= ~(1u << reg);
|
||||||
|
@ -167,7 +167,7 @@ RegisterA64 IrRegAllocA64::allocTemp(KindA64 kind)
|
||||||
|
|
||||||
int reg = 31 - countlz(set.free);
|
int reg = 31 - countlz(set.free);
|
||||||
|
|
||||||
if (FFlag::DebugLuauCodegenChaosA64)
|
if (FFlag::DebugCodegenChaosA64)
|
||||||
reg = countrz(set.free); // allocate from low end; this causes extra conflicts for calls
|
reg = countrz(set.free); // allocate from low end; this causes extra conflicts for calls
|
||||||
|
|
||||||
set.free &= ~(1u << reg);
|
set.free &= ~(1u << reg);
|
||||||
|
@ -278,7 +278,7 @@ size_t IrRegAllocA64::spill(AssemblyBuilderA64& build, uint32_t index, std::init
|
||||||
uint32_t poisongpr = 0;
|
uint32_t poisongpr = 0;
|
||||||
uint32_t poisonsimd = 0;
|
uint32_t poisonsimd = 0;
|
||||||
|
|
||||||
if (FFlag::DebugLuauCodegenChaosA64)
|
if (FFlag::DebugCodegenChaosA64)
|
||||||
{
|
{
|
||||||
poisongpr = gpr.base & ~gpr.free;
|
poisongpr = gpr.base & ~gpr.free;
|
||||||
poisonsimd = simd.base & ~simd.free;
|
poisonsimd = simd.base & ~simd.free;
|
||||||
|
@ -370,7 +370,7 @@ size_t IrRegAllocA64::spill(AssemblyBuilderA64& build, uint32_t index, std::init
|
||||||
LUAU_ASSERT(set.free == set.base);
|
LUAU_ASSERT(set.free == set.base);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FFlag::DebugLuauCodegenChaosA64)
|
if (FFlag::DebugCodegenChaosA64)
|
||||||
{
|
{
|
||||||
for (int reg = 0; reg < 32; ++reg)
|
for (int reg = 0; reg < 32; ++reg)
|
||||||
{
|
{
|
||||||
|
|
|
@ -83,6 +83,9 @@ static BuiltinImplResult translateBuiltin2NumberToNumberLibm(
|
||||||
IrOp va = builtinLoadDouble(build, build.vmReg(arg));
|
IrOp va = builtinLoadDouble(build, build.vmReg(arg));
|
||||||
IrOp vb = builtinLoadDouble(build, args);
|
IrOp vb = builtinLoadDouble(build, args);
|
||||||
|
|
||||||
|
if (bfid == LBF_MATH_LDEXP)
|
||||||
|
vb = build.inst(IrCmd::NUM_TO_INT, vb);
|
||||||
|
|
||||||
IrOp res = build.inst(IrCmd::INVOKE_LIBM, build.constUint(bfid), va, vb);
|
IrOp res = build.inst(IrCmd::INVOKE_LIBM, build.constUint(bfid), va, vb);
|
||||||
|
|
||||||
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(ra), res);
|
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(ra), res);
|
||||||
|
@ -93,30 +96,6 @@ static BuiltinImplResult translateBuiltin2NumberToNumberLibm(
|
||||||
return {BuiltinImplType::Full, 1};
|
return {BuiltinImplType::Full, 1};
|
||||||
}
|
}
|
||||||
|
|
||||||
static BuiltinImplResult translateBuiltinMathLdexp(
|
|
||||||
IrBuilder& build, LuauBuiltinFunction bfid, int nparams, int ra, int arg, IrOp args, int nresults, int pcpos)
|
|
||||||
{
|
|
||||||
if (nparams < 2 || nresults > 1)
|
|
||||||
return {BuiltinImplType::None, -1};
|
|
||||||
|
|
||||||
builtinCheckDouble(build, build.vmReg(arg), pcpos);
|
|
||||||
builtinCheckDouble(build, args, pcpos);
|
|
||||||
|
|
||||||
IrOp va = builtinLoadDouble(build, build.vmReg(arg));
|
|
||||||
IrOp vb = builtinLoadDouble(build, args);
|
|
||||||
|
|
||||||
IrOp vbi = build.inst(IrCmd::NUM_TO_INT, vb);
|
|
||||||
|
|
||||||
IrOp res = build.inst(IrCmd::INVOKE_LIBM, build.constUint(bfid), va, vbi);
|
|
||||||
|
|
||||||
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(ra), res);
|
|
||||||
|
|
||||||
if (ra != arg)
|
|
||||||
build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TNUMBER));
|
|
||||||
|
|
||||||
return {BuiltinImplType::Full, 1};
|
|
||||||
}
|
|
||||||
|
|
||||||
// (number, ...) -> (number, number)
|
// (number, ...) -> (number, number)
|
||||||
static BuiltinImplResult translateBuiltinNumberTo2Number(
|
static BuiltinImplResult translateBuiltinNumberTo2Number(
|
||||||
IrBuilder& build, LuauBuiltinFunction bfid, int nparams, int ra, int arg, IrOp args, int nresults, int pcpos)
|
IrBuilder& build, LuauBuiltinFunction bfid, int nparams, int ra, int arg, IrOp args, int nresults, int pcpos)
|
||||||
|
@ -152,7 +131,7 @@ static BuiltinImplResult translateBuiltinAssert(IrBuilder& build, int nparams, i
|
||||||
return {BuiltinImplType::UsesFallback, 0};
|
return {BuiltinImplType::UsesFallback, 0};
|
||||||
}
|
}
|
||||||
|
|
||||||
static BuiltinImplResult translateBuiltinMathDeg(IrBuilder& build, int nparams, int ra, int arg, IrOp args, int nresults, int pcpos)
|
static BuiltinImplResult translateBuiltinMathDegRad(IrBuilder& build, IrCmd cmd, int nparams, int ra, int arg, IrOp args, int nresults, int pcpos)
|
||||||
{
|
{
|
||||||
if (nparams < 1 || nresults > 1)
|
if (nparams < 1 || nresults > 1)
|
||||||
return {BuiltinImplType::None, -1};
|
return {BuiltinImplType::None, -1};
|
||||||
|
@ -162,26 +141,7 @@ static BuiltinImplResult translateBuiltinMathDeg(IrBuilder& build, int nparams,
|
||||||
const double rpd = (3.14159265358979323846 / 180.0);
|
const double rpd = (3.14159265358979323846 / 180.0);
|
||||||
|
|
||||||
IrOp varg = builtinLoadDouble(build, build.vmReg(arg));
|
IrOp varg = builtinLoadDouble(build, build.vmReg(arg));
|
||||||
IrOp value = build.inst(IrCmd::DIV_NUM, varg, build.constDouble(rpd));
|
IrOp value = build.inst(cmd, varg, build.constDouble(rpd));
|
||||||
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(ra), value);
|
|
||||||
|
|
||||||
if (ra != arg)
|
|
||||||
build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TNUMBER));
|
|
||||||
|
|
||||||
return {BuiltinImplType::Full, 1};
|
|
||||||
}
|
|
||||||
|
|
||||||
static BuiltinImplResult translateBuiltinMathRad(IrBuilder& build, int nparams, int ra, int arg, IrOp args, int nresults, int pcpos)
|
|
||||||
{
|
|
||||||
if (nparams < 1 || nresults > 1)
|
|
||||||
return {BuiltinImplType::None, -1};
|
|
||||||
|
|
||||||
builtinCheckDouble(build, build.vmReg(arg), pcpos);
|
|
||||||
|
|
||||||
const double rpd = (3.14159265358979323846 / 180.0);
|
|
||||||
|
|
||||||
IrOp varg = builtinLoadDouble(build, build.vmReg(arg));
|
|
||||||
IrOp value = build.inst(IrCmd::MUL_NUM, varg, build.constDouble(rpd));
|
|
||||||
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(ra), value);
|
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(ra), value);
|
||||||
|
|
||||||
if (ra != arg)
|
if (ra != arg)
|
||||||
|
@ -231,7 +191,7 @@ static BuiltinImplResult translateBuiltinMathLog(
|
||||||
return {BuiltinImplType::Full, 1};
|
return {BuiltinImplType::Full, 1};
|
||||||
}
|
}
|
||||||
|
|
||||||
static BuiltinImplResult translateBuiltinMathMin(IrBuilder& build, int nparams, int ra, int arg, IrOp args, int nresults, int pcpos)
|
static BuiltinImplResult translateBuiltinMathMinMax(IrBuilder& build, IrCmd cmd, int nparams, int ra, int arg, IrOp args, int nresults, int pcpos)
|
||||||
{
|
{
|
||||||
if (nparams < 2 || nparams > kMinMaxUnrolledParams || nresults > 1)
|
if (nparams < 2 || nparams > kMinMaxUnrolledParams || nresults > 1)
|
||||||
return {BuiltinImplType::None, -1};
|
return {BuiltinImplType::None, -1};
|
||||||
|
@ -245,42 +205,12 @@ static BuiltinImplResult translateBuiltinMathMin(IrBuilder& build, int nparams,
|
||||||
IrOp varg1 = builtinLoadDouble(build, build.vmReg(arg));
|
IrOp varg1 = builtinLoadDouble(build, build.vmReg(arg));
|
||||||
IrOp varg2 = builtinLoadDouble(build, args);
|
IrOp varg2 = builtinLoadDouble(build, args);
|
||||||
|
|
||||||
IrOp res = build.inst(IrCmd::MIN_NUM, varg2, varg1); // Swapped arguments are required for consistency with VM builtins
|
IrOp res = build.inst(cmd, varg2, varg1); // Swapped arguments are required for consistency with VM builtins
|
||||||
|
|
||||||
for (int i = 3; i <= nparams; ++i)
|
for (int i = 3; i <= nparams; ++i)
|
||||||
{
|
{
|
||||||
IrOp arg = builtinLoadDouble(build, build.vmReg(vmRegOp(args) + (i - 2)));
|
IrOp arg = builtinLoadDouble(build, build.vmReg(vmRegOp(args) + (i - 2)));
|
||||||
res = build.inst(IrCmd::MIN_NUM, arg, res);
|
res = build.inst(cmd, arg, res);
|
||||||
}
|
|
||||||
|
|
||||||
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(ra), res);
|
|
||||||
|
|
||||||
if (ra != arg)
|
|
||||||
build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TNUMBER));
|
|
||||||
|
|
||||||
return {BuiltinImplType::Full, 1};
|
|
||||||
}
|
|
||||||
|
|
||||||
static BuiltinImplResult translateBuiltinMathMax(IrBuilder& build, int nparams, int ra, int arg, IrOp args, int nresults, int pcpos)
|
|
||||||
{
|
|
||||||
if (nparams < 2 || nparams > kMinMaxUnrolledParams || nresults > 1)
|
|
||||||
return {BuiltinImplType::None, -1};
|
|
||||||
|
|
||||||
builtinCheckDouble(build, build.vmReg(arg), pcpos);
|
|
||||||
builtinCheckDouble(build, args, pcpos);
|
|
||||||
|
|
||||||
for (int i = 3; i <= nparams; ++i)
|
|
||||||
builtinCheckDouble(build, build.vmReg(vmRegOp(args) + (i - 2)), pcpos);
|
|
||||||
|
|
||||||
IrOp varg1 = builtinLoadDouble(build, build.vmReg(arg));
|
|
||||||
IrOp varg2 = builtinLoadDouble(build, args);
|
|
||||||
|
|
||||||
IrOp res = build.inst(IrCmd::MAX_NUM, varg2, varg1); // Swapped arguments are required for consistency with VM builtins
|
|
||||||
|
|
||||||
for (int i = 3; i <= nparams; ++i)
|
|
||||||
{
|
|
||||||
IrOp arg = builtinLoadDouble(build, build.vmReg(vmRegOp(args) + (i - 2)));
|
|
||||||
res = build.inst(IrCmd::MAX_NUM, arg, res);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(ra), res);
|
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(ra), res);
|
||||||
|
@ -823,15 +753,15 @@ BuiltinImplResult translateBuiltin(IrBuilder& build, int bfid, int ra, int arg,
|
||||||
case LBF_ASSERT:
|
case LBF_ASSERT:
|
||||||
return translateBuiltinAssert(build, nparams, ra, arg, args, nresults, pcpos);
|
return translateBuiltinAssert(build, nparams, ra, arg, args, nresults, pcpos);
|
||||||
case LBF_MATH_DEG:
|
case LBF_MATH_DEG:
|
||||||
return translateBuiltinMathDeg(build, nparams, ra, arg, args, nresults, pcpos);
|
return translateBuiltinMathDegRad(build, IrCmd::DIV_NUM, nparams, ra, arg, args, nresults, pcpos);
|
||||||
case LBF_MATH_RAD:
|
case LBF_MATH_RAD:
|
||||||
return translateBuiltinMathRad(build, nparams, ra, arg, args, nresults, pcpos);
|
return translateBuiltinMathDegRad(build, IrCmd::MUL_NUM, nparams, ra, arg, args, nresults, pcpos);
|
||||||
case LBF_MATH_LOG:
|
case LBF_MATH_LOG:
|
||||||
return translateBuiltinMathLog(build, LuauBuiltinFunction(bfid), nparams, ra, arg, args, nresults, pcpos);
|
return translateBuiltinMathLog(build, LuauBuiltinFunction(bfid), nparams, ra, arg, args, nresults, pcpos);
|
||||||
case LBF_MATH_MIN:
|
case LBF_MATH_MIN:
|
||||||
return translateBuiltinMathMin(build, nparams, ra, arg, args, nresults, pcpos);
|
return translateBuiltinMathMinMax(build, IrCmd::MIN_NUM, nparams, ra, arg, args, nresults, pcpos);
|
||||||
case LBF_MATH_MAX:
|
case LBF_MATH_MAX:
|
||||||
return translateBuiltinMathMax(build, nparams, ra, arg, args, nresults, pcpos);
|
return translateBuiltinMathMinMax(build, IrCmd::MAX_NUM, nparams, ra, arg, args, nresults, pcpos);
|
||||||
case LBF_MATH_CLAMP:
|
case LBF_MATH_CLAMP:
|
||||||
return translateBuiltinMathClamp(build, nparams, ra, arg, args, nresults, fallback, pcpos);
|
return translateBuiltinMathClamp(build, nparams, ra, arg, args, nresults, fallback, pcpos);
|
||||||
case LBF_MATH_FLOOR:
|
case LBF_MATH_FLOOR:
|
||||||
|
@ -861,9 +791,8 @@ BuiltinImplResult translateBuiltin(IrBuilder& build, int bfid, int ra, int arg,
|
||||||
case LBF_MATH_POW:
|
case LBF_MATH_POW:
|
||||||
case LBF_MATH_FMOD:
|
case LBF_MATH_FMOD:
|
||||||
case LBF_MATH_ATAN2:
|
case LBF_MATH_ATAN2:
|
||||||
return translateBuiltin2NumberToNumberLibm(build, LuauBuiltinFunction(bfid), nparams, ra, arg, args, nresults, pcpos);
|
|
||||||
case LBF_MATH_LDEXP:
|
case LBF_MATH_LDEXP:
|
||||||
return translateBuiltinMathLdexp(build, LuauBuiltinFunction(bfid), nparams, ra, arg, args, nresults, pcpos);
|
return translateBuiltin2NumberToNumberLibm(build, LuauBuiltinFunction(bfid), nparams, ra, arg, args, nresults, pcpos);
|
||||||
case LBF_MATH_FREXP:
|
case LBF_MATH_FREXP:
|
||||||
case LBF_MATH_MODF:
|
case LBF_MATH_MODF:
|
||||||
return translateBuiltinNumberTo2Number(build, LuauBuiltinFunction(bfid), nparams, ra, arg, args, nresults, pcpos);
|
return translateBuiltinNumberTo2Number(build, LuauBuiltinFunction(bfid), nparams, ra, arg, args, nresults, pcpos);
|
||||||
|
|
|
@ -12,8 +12,9 @@
|
||||||
#include "lstate.h"
|
#include "lstate.h"
|
||||||
#include "ltm.h"
|
#include "ltm.h"
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauImproveForN, false)
|
LUAU_FASTFLAGVARIABLE(LuauImproveForN2, false)
|
||||||
LUAU_FASTFLAG(LuauReduceStackSpills)
|
LUAU_FASTFLAG(LuauReduceStackSpills)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauInlineArrConstOffset, false)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -631,6 +632,26 @@ static IrOp getLoopStepK(IrBuilder& build, int ra)
|
||||||
return build.undef();
|
return build.undef();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void beforeInstForNPrep(IrBuilder& build, const Instruction* pc)
|
||||||
|
{
|
||||||
|
if (FFlag::LuauImproveForN2)
|
||||||
|
{
|
||||||
|
int ra = LUAU_INSN_A(*pc);
|
||||||
|
|
||||||
|
IrOp stepK = getLoopStepK(build, ra);
|
||||||
|
build.loopStepStack.push_back(stepK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void afterInstForNLoop(IrBuilder& build, const Instruction* pc)
|
||||||
|
{
|
||||||
|
if (FFlag::LuauImproveForN2)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(!build.loopStepStack.empty());
|
||||||
|
build.loopStepStack.pop_back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void translateInstForNPrep(IrBuilder& build, const Instruction* pc, int pcpos)
|
void translateInstForNPrep(IrBuilder& build, const Instruction* pc, int pcpos)
|
||||||
{
|
{
|
||||||
int ra = LUAU_INSN_A(*pc);
|
int ra = LUAU_INSN_A(*pc);
|
||||||
|
@ -638,10 +659,10 @@ void translateInstForNPrep(IrBuilder& build, const Instruction* pc, int pcpos)
|
||||||
IrOp loopStart = build.blockAtInst(pcpos + getOpLength(LuauOpcode(LUAU_INSN_OP(*pc))));
|
IrOp loopStart = build.blockAtInst(pcpos + getOpLength(LuauOpcode(LUAU_INSN_OP(*pc))));
|
||||||
IrOp loopExit = build.blockAtInst(getJumpTarget(*pc, pcpos));
|
IrOp loopExit = build.blockAtInst(getJumpTarget(*pc, pcpos));
|
||||||
|
|
||||||
if (FFlag::LuauImproveForN)
|
if (FFlag::LuauImproveForN2)
|
||||||
{
|
{
|
||||||
IrOp stepK = getLoopStepK(build, ra);
|
LUAU_ASSERT(!build.loopStepStack.empty());
|
||||||
build.loopStepStack.push_back(stepK);
|
IrOp stepK = build.loopStepStack.back();
|
||||||
|
|
||||||
// When loop parameters are not numbers, VM tries to perform type coercion from string and raises an exception if that fails
|
// When loop parameters are not numbers, VM tries to perform type coercion from string and raises an exception if that fails
|
||||||
// Performing that fallback in native code increases code size and complicates CFG, obscuring the values when they are constant
|
// Performing that fallback in native code increases code size and complicates CFG, obscuring the values when they are constant
|
||||||
|
@ -733,7 +754,7 @@ void translateInstForNPrep(IrBuilder& build, const Instruction* pc, int pcpos)
|
||||||
// VM places interrupt in FORNLOOP, but that creates a likely spill point for short loops that use loop index as INTERRUPT always spills
|
// VM places interrupt in FORNLOOP, but that creates a likely spill point for short loops that use loop index as INTERRUPT always spills
|
||||||
// We place the interrupt at the beginning of the loop body instead; VM uses FORNLOOP because it doesn't want to waste an extra instruction.
|
// We place the interrupt at the beginning of the loop body instead; VM uses FORNLOOP because it doesn't want to waste an extra instruction.
|
||||||
// Because loop block may not have been started yet (as it's started when lowering the first instruction!), we need to defer INTERRUPT placement.
|
// Because loop block may not have been started yet (as it's started when lowering the first instruction!), we need to defer INTERRUPT placement.
|
||||||
if (FFlag::LuauImproveForN)
|
if (FFlag::LuauImproveForN2)
|
||||||
build.interruptRequested = true;
|
build.interruptRequested = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -744,11 +765,10 @@ void translateInstForNLoop(IrBuilder& build, const Instruction* pc, int pcpos)
|
||||||
IrOp loopRepeat = build.blockAtInst(getJumpTarget(*pc, pcpos));
|
IrOp loopRepeat = build.blockAtInst(getJumpTarget(*pc, pcpos));
|
||||||
IrOp loopExit = build.blockAtInst(pcpos + getOpLength(LuauOpcode(LUAU_INSN_OP(*pc))));
|
IrOp loopExit = build.blockAtInst(pcpos + getOpLength(LuauOpcode(LUAU_INSN_OP(*pc))));
|
||||||
|
|
||||||
if (FFlag::LuauImproveForN)
|
if (FFlag::LuauImproveForN2)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(!build.loopStepStack.empty());
|
LUAU_ASSERT(!build.loopStepStack.empty());
|
||||||
IrOp stepK = build.loopStepStack.back();
|
IrOp stepK = build.loopStepStack.back();
|
||||||
build.loopStepStack.pop_back();
|
|
||||||
|
|
||||||
IrOp zero = build.constDouble(0.0);
|
IrOp zero = build.constDouble(0.0);
|
||||||
IrOp limit = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(ra + 0));
|
IrOp limit = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(ra + 0));
|
||||||
|
@ -950,10 +970,20 @@ void translateInstGetTableN(IrBuilder& build, const Instruction* pc, int pcpos)
|
||||||
build.inst(IrCmd::CHECK_ARRAY_SIZE, vb, build.constInt(c), fallback);
|
build.inst(IrCmd::CHECK_ARRAY_SIZE, vb, build.constInt(c), fallback);
|
||||||
build.inst(IrCmd::CHECK_NO_METATABLE, vb, fallback);
|
build.inst(IrCmd::CHECK_NO_METATABLE, vb, fallback);
|
||||||
|
|
||||||
|
if (FFlag::LuauInlineArrConstOffset)
|
||||||
|
{
|
||||||
|
IrOp arrEl = build.inst(IrCmd::GET_ARR_ADDR, vb, build.constInt(0));
|
||||||
|
|
||||||
|
IrOp arrElTval = build.inst(IrCmd::LOAD_TVALUE, arrEl, build.constInt(c * sizeof(TValue)));
|
||||||
|
build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), arrElTval);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
IrOp arrEl = build.inst(IrCmd::GET_ARR_ADDR, vb, build.constInt(c));
|
IrOp arrEl = build.inst(IrCmd::GET_ARR_ADDR, vb, build.constInt(c));
|
||||||
|
|
||||||
IrOp arrElTval = build.inst(IrCmd::LOAD_TVALUE, arrEl);
|
IrOp arrElTval = build.inst(IrCmd::LOAD_TVALUE, arrEl);
|
||||||
build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), arrElTval);
|
build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), arrElTval);
|
||||||
|
}
|
||||||
|
|
||||||
IrOp next = build.blockAtInst(pcpos + 1);
|
IrOp next = build.blockAtInst(pcpos + 1);
|
||||||
FallbackStreamScope scope(build, fallback, next);
|
FallbackStreamScope scope(build, fallback, next);
|
||||||
|
@ -980,10 +1010,20 @@ void translateInstSetTableN(IrBuilder& build, const Instruction* pc, int pcpos)
|
||||||
build.inst(IrCmd::CHECK_NO_METATABLE, vb, fallback);
|
build.inst(IrCmd::CHECK_NO_METATABLE, vb, fallback);
|
||||||
build.inst(IrCmd::CHECK_READONLY, vb, fallback);
|
build.inst(IrCmd::CHECK_READONLY, vb, fallback);
|
||||||
|
|
||||||
|
if (FFlag::LuauInlineArrConstOffset)
|
||||||
|
{
|
||||||
|
IrOp arrEl = build.inst(IrCmd::GET_ARR_ADDR, vb, build.constInt(0));
|
||||||
|
|
||||||
|
IrOp tva = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(ra));
|
||||||
|
build.inst(IrCmd::STORE_TVALUE, arrEl, tva, build.constInt(c * sizeof(TValue)));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
IrOp arrEl = build.inst(IrCmd::GET_ARR_ADDR, vb, build.constInt(c));
|
IrOp arrEl = build.inst(IrCmd::GET_ARR_ADDR, vb, build.constInt(c));
|
||||||
|
|
||||||
IrOp tva = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(ra));
|
IrOp tva = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(ra));
|
||||||
build.inst(IrCmd::STORE_TVALUE, arrEl, tva);
|
build.inst(IrCmd::STORE_TVALUE, arrEl, tva);
|
||||||
|
}
|
||||||
|
|
||||||
build.inst(IrCmd::BARRIER_TABLE_FORWARD, vb, build.vmReg(ra), build.undef());
|
build.inst(IrCmd::BARRIER_TABLE_FORWARD, vb, build.vmReg(ra), build.undef());
|
||||||
|
|
||||||
|
|
|
@ -65,5 +65,8 @@ void translateInstAndX(IrBuilder& build, const Instruction* pc, int pcpos, IrOp
|
||||||
void translateInstOrX(IrBuilder& build, const Instruction* pc, int pcpos, IrOp c);
|
void translateInstOrX(IrBuilder& build, const Instruction* pc, int pcpos, IrOp c);
|
||||||
void translateInstNewClosure(IrBuilder& build, const Instruction* pc, int pcpos);
|
void translateInstNewClosure(IrBuilder& build, const Instruction* pc, int pcpos);
|
||||||
|
|
||||||
|
void beforeInstForNPrep(IrBuilder& build, const Instruction* pc);
|
||||||
|
void afterInstForNLoop(IrBuilder& build, const Instruction* pc);
|
||||||
|
|
||||||
} // namespace CodeGen
|
} // namespace CodeGen
|
||||||
} // namespace Luau
|
} // namespace Luau
|
||||||
|
|
|
@ -427,7 +427,7 @@ enum LuauBytecodeTag
|
||||||
// Bytecode version; runtime supports [MIN, MAX], compiler emits TARGET by default but may emit a higher version when flags are enabled
|
// Bytecode version; runtime supports [MIN, MAX], compiler emits TARGET by default but may emit a higher version when flags are enabled
|
||||||
LBC_VERSION_MIN = 3,
|
LBC_VERSION_MIN = 3,
|
||||||
LBC_VERSION_MAX = 4,
|
LBC_VERSION_MAX = 4,
|
||||||
LBC_VERSION_TARGET = 3,
|
LBC_VERSION_TARGET = 4,
|
||||||
// Type encoding version
|
// Type encoding version
|
||||||
LBC_TYPE_VERSION = 1,
|
LBC_TYPE_VERSION = 1,
|
||||||
// Types of constant table entries
|
// Types of constant table entries
|
||||||
|
@ -575,4 +575,6 @@ enum LuauProtoFlag
|
||||||
{
|
{
|
||||||
// used to tag main proto for modules with --!native
|
// used to tag main proto for modules with --!native
|
||||||
LPF_NATIVE_MODULE = 1 << 0,
|
LPF_NATIVE_MODULE = 1 << 0,
|
||||||
|
// used to tag individual protos as not profitable to compile natively
|
||||||
|
LPF_NATIVE_COLD = 1 << 1,
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,8 +7,6 @@
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(BytecodeVersion4, false)
|
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauFloorDivision)
|
LUAU_FASTFLAG(LuauFloorDivision)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
|
@ -586,12 +584,9 @@ void BytecodeBuilder::finalize()
|
||||||
|
|
||||||
bytecode = char(version);
|
bytecode = char(version);
|
||||||
|
|
||||||
if (FFlag::BytecodeVersion4)
|
|
||||||
{
|
|
||||||
uint8_t typesversion = getTypeEncodingVersion();
|
uint8_t typesversion = getTypeEncodingVersion();
|
||||||
LUAU_ASSERT(typesversion == 1);
|
LUAU_ASSERT(typesversion == 1);
|
||||||
writeByte(bytecode, typesversion);
|
writeByte(bytecode, typesversion);
|
||||||
}
|
|
||||||
|
|
||||||
writeStringTable(bytecode);
|
writeStringTable(bytecode);
|
||||||
|
|
||||||
|
@ -615,13 +610,10 @@ void BytecodeBuilder::writeFunction(std::string& ss, uint32_t id, uint8_t flags)
|
||||||
writeByte(ss, func.numupvalues);
|
writeByte(ss, func.numupvalues);
|
||||||
writeByte(ss, func.isvararg);
|
writeByte(ss, func.isvararg);
|
||||||
|
|
||||||
if (FFlag::BytecodeVersion4)
|
|
||||||
{
|
|
||||||
writeByte(ss, flags);
|
writeByte(ss, flags);
|
||||||
|
|
||||||
writeVarInt(ss, uint32_t(func.typeinfo.size()));
|
writeVarInt(ss, uint32_t(func.typeinfo.size()));
|
||||||
ss.append(func.typeinfo);
|
ss.append(func.typeinfo);
|
||||||
}
|
|
||||||
|
|
||||||
// instructions
|
// instructions
|
||||||
writeVarInt(ss, uint32_t(insns.size()));
|
writeVarInt(ss, uint32_t(insns.size()));
|
||||||
|
@ -1074,10 +1066,6 @@ std::string BytecodeBuilder::getError(const std::string& message)
|
||||||
uint8_t BytecodeBuilder::getVersion()
|
uint8_t BytecodeBuilder::getVersion()
|
||||||
{
|
{
|
||||||
// This function usually returns LBC_VERSION_TARGET but may sometimes return a higher number (within LBC_VERSION_MIN/MAX) under fast flags
|
// This function usually returns LBC_VERSION_TARGET but may sometimes return a higher number (within LBC_VERSION_MIN/MAX) under fast flags
|
||||||
|
|
||||||
if (FFlag::BytecodeVersion4)
|
|
||||||
return 4;
|
|
||||||
|
|
||||||
return LBC_VERSION_TARGET;
|
return LBC_VERSION_TARGET;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,13 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineThreshold, 25)
|
||||||
LUAU_FASTINTVARIABLE(LuauCompileInlineThresholdMaxBoost, 300)
|
LUAU_FASTINTVARIABLE(LuauCompileInlineThresholdMaxBoost, 300)
|
||||||
LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5)
|
LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5)
|
||||||
|
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauCompileFenvNoBuiltinFold, false)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauCompileTopCold, false)
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauFloorDivision)
|
LUAU_FASTFLAG(LuauFloorDivision)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauCompileFixContinueValidation, false)
|
||||||
|
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauCompileContinueCloseUpvals, false)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -259,6 +265,10 @@ struct Compiler
|
||||||
if (bytecode.getInstructionCount() > kMaxInstructionCount)
|
if (bytecode.getInstructionCount() > kMaxInstructionCount)
|
||||||
CompileError::raise(func->location, "Exceeded function instruction limit; split the function into parts to compile");
|
CompileError::raise(func->location, "Exceeded function instruction limit; split the function into parts to compile");
|
||||||
|
|
||||||
|
// since top-level code only executes once, it can be marked as cold if it has no loops (top-level code with loops might be profitable to compile natively)
|
||||||
|
if (FFlag::LuauCompileTopCold && func->functionDepth == 0 && !hasLoops)
|
||||||
|
protoflags |= LPF_NATIVE_COLD;
|
||||||
|
|
||||||
bytecode.endFunction(uint8_t(stackSize), uint8_t(upvals.size()), protoflags);
|
bytecode.endFunction(uint8_t(stackSize), uint8_t(upvals.size()), protoflags);
|
||||||
|
|
||||||
Function& f = functions[func];
|
Function& f = functions[func];
|
||||||
|
@ -283,6 +293,7 @@ struct Compiler
|
||||||
|
|
||||||
upvals.clear(); // note: instead of std::move above, we copy & clear to preserve capacity for future pushes
|
upvals.clear(); // note: instead of std::move above, we copy & clear to preserve capacity for future pushes
|
||||||
stackSize = 0;
|
stackSize = 0;
|
||||||
|
hasLoops = false;
|
||||||
|
|
||||||
return fid;
|
return fid;
|
||||||
}
|
}
|
||||||
|
@ -646,11 +657,25 @@ struct Compiler
|
||||||
// apply all evaluated arguments to the compiler state
|
// apply all evaluated arguments to the compiler state
|
||||||
// note: locals use current startpc for debug info, although some of them have been computed earlier; this is similar to compileStatLocal
|
// note: locals use current startpc for debug info, although some of them have been computed earlier; this is similar to compileStatLocal
|
||||||
for (InlineArg& arg : args)
|
for (InlineArg& arg : args)
|
||||||
|
{
|
||||||
if (arg.value.type == Constant::Type_Unknown)
|
if (arg.value.type == Constant::Type_Unknown)
|
||||||
|
{
|
||||||
pushLocal(arg.local, arg.reg);
|
pushLocal(arg.local, arg.reg);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
locstants[arg.local] = arg.value;
|
locstants[arg.local] = arg.value;
|
||||||
|
|
||||||
|
if (FFlag::LuauCompileFixContinueValidation)
|
||||||
|
{
|
||||||
|
// Mark that optimization skipped allocation of this local
|
||||||
|
Local& l = locals[arg.local];
|
||||||
|
LUAU_ASSERT(!l.skipped);
|
||||||
|
l.skipped = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// the inline frame will be used to compile return statements as well as to reject recursive inlining attempts
|
// the inline frame will be used to compile return statements as well as to reject recursive inlining attempts
|
||||||
inlineFrames.push_back({func, oldLocals, target, targetCount});
|
inlineFrames.push_back({func, oldLocals, target, targetCount});
|
||||||
|
|
||||||
|
@ -693,9 +718,28 @@ struct Compiler
|
||||||
|
|
||||||
// clean up constant state for future inlining attempts
|
// clean up constant state for future inlining attempts
|
||||||
for (size_t i = 0; i < func->args.size; ++i)
|
for (size_t i = 0; i < func->args.size; ++i)
|
||||||
if (Constant* var = locstants.find(func->args.data[i]))
|
{
|
||||||
|
AstLocal* local = func->args.data[i];
|
||||||
|
|
||||||
|
if (FFlag::LuauCompileFixContinueValidation)
|
||||||
|
{
|
||||||
|
if (Constant* var = locstants.find(local); var && var->type != Constant::Type_Unknown)
|
||||||
|
{
|
||||||
var->type = Constant::Type_Unknown;
|
var->type = Constant::Type_Unknown;
|
||||||
|
|
||||||
|
// Restore local allocation skip flag as well
|
||||||
|
Local& l = locals[local];
|
||||||
|
LUAU_ASSERT(l.skipped);
|
||||||
|
l.skipped = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (Constant* var = locstants.find(local))
|
||||||
|
var->type = Constant::Type_Unknown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
foldConstants(constants, variables, locstants, builtinsFold, builtinsFoldMathK, func->body);
|
foldConstants(constants, variables, locstants, builtinsFold, builtinsFoldMathK, func->body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2469,7 +2513,7 @@ struct Compiler
|
||||||
AstStat* continueStatement = extractStatContinue(stat->thenbody);
|
AstStat* continueStatement = extractStatContinue(stat->thenbody);
|
||||||
|
|
||||||
// Optimization: body is a "continue" statement with no "else" => we can directly continue in "then" case
|
// Optimization: body is a "continue" statement with no "else" => we can directly continue in "then" case
|
||||||
if (!stat->elsebody && continueStatement != nullptr && !areLocalsCaptured(loops.back().localOffset))
|
if (!stat->elsebody && continueStatement != nullptr && !areLocalsCaptured(loops.back().localOffsetContinue))
|
||||||
{
|
{
|
||||||
if (loops.back().untilCondition)
|
if (loops.back().untilCondition)
|
||||||
validateContinueUntil(continueStatement, loops.back().untilCondition);
|
validateContinueUntil(continueStatement, loops.back().untilCondition);
|
||||||
|
@ -2533,7 +2577,8 @@ struct Compiler
|
||||||
size_t oldJumps = loopJumps.size();
|
size_t oldJumps = loopJumps.size();
|
||||||
size_t oldLocals = localStack.size();
|
size_t oldLocals = localStack.size();
|
||||||
|
|
||||||
loops.push_back({oldLocals, nullptr});
|
loops.push_back({oldLocals, oldLocals, nullptr});
|
||||||
|
hasLoops = true;
|
||||||
|
|
||||||
size_t loopLabel = bytecode.emitLabel();
|
size_t loopLabel = bytecode.emitLabel();
|
||||||
|
|
||||||
|
@ -2568,7 +2613,8 @@ struct Compiler
|
||||||
size_t oldJumps = loopJumps.size();
|
size_t oldJumps = loopJumps.size();
|
||||||
size_t oldLocals = localStack.size();
|
size_t oldLocals = localStack.size();
|
||||||
|
|
||||||
loops.push_back({oldLocals, stat->condition});
|
loops.push_back({oldLocals, oldLocals, stat->condition});
|
||||||
|
hasLoops = true;
|
||||||
|
|
||||||
size_t loopLabel = bytecode.emitLabel();
|
size_t loopLabel = bytecode.emitLabel();
|
||||||
|
|
||||||
|
@ -2579,8 +2625,17 @@ struct Compiler
|
||||||
RegScope rs(this);
|
RegScope rs(this);
|
||||||
|
|
||||||
for (size_t i = 0; i < body->body.size; ++i)
|
for (size_t i = 0; i < body->body.size; ++i)
|
||||||
|
{
|
||||||
compileStat(body->body.data[i]);
|
compileStat(body->body.data[i]);
|
||||||
|
|
||||||
|
// continue statement inside the repeat..until loop should not close upvalues defined directly in the loop body
|
||||||
|
// (but it must still close upvalues defined in more nested blocks)
|
||||||
|
// this is because the upvalues defined inside the loop body may be captured by a closure defined in the until
|
||||||
|
// expression that continue will jump to.
|
||||||
|
if (FFlag::LuauCompileContinueCloseUpvals)
|
||||||
|
loops.back().localOffsetContinue = localStack.size();
|
||||||
|
}
|
||||||
|
|
||||||
size_t contLabel = bytecode.emitLabel();
|
size_t contLabel = bytecode.emitLabel();
|
||||||
|
|
||||||
size_t endLabel;
|
size_t endLabel;
|
||||||
|
@ -2707,7 +2762,19 @@ struct Compiler
|
||||||
{
|
{
|
||||||
// Optimization: we don't need to allocate and assign const locals, since their uses will be constant-folded
|
// Optimization: we don't need to allocate and assign const locals, since their uses will be constant-folded
|
||||||
if (options.optimizationLevel >= 1 && options.debugLevel <= 1 && areLocalsRedundant(stat))
|
if (options.optimizationLevel >= 1 && options.debugLevel <= 1 && areLocalsRedundant(stat))
|
||||||
|
{
|
||||||
|
if (FFlag::LuauCompileFixContinueValidation)
|
||||||
|
{
|
||||||
|
// Mark that optimization skipped allocation of this local
|
||||||
|
for (AstLocal* local : stat->vars)
|
||||||
|
{
|
||||||
|
Local& l = locals[local];
|
||||||
|
l.skipped = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Optimization: for 1-1 local assignments, we can reuse the register *if* neither local is mutated
|
// Optimization: for 1-1 local assignments, we can reuse the register *if* neither local is mutated
|
||||||
if (options.optimizationLevel >= 1 && stat->vars.size == 1 && stat->values.size == 1)
|
if (options.optimizationLevel >= 1 && stat->vars.size == 1 && stat->values.size == 1)
|
||||||
|
@ -2796,7 +2863,7 @@ struct Compiler
|
||||||
size_t oldLocals = localStack.size();
|
size_t oldLocals = localStack.size();
|
||||||
size_t oldJumps = loopJumps.size();
|
size_t oldJumps = loopJumps.size();
|
||||||
|
|
||||||
loops.push_back({oldLocals, nullptr});
|
loops.push_back({oldLocals, oldLocals, nullptr});
|
||||||
|
|
||||||
for (int iv = 0; iv < tripCount; ++iv)
|
for (int iv = 0; iv < tripCount; ++iv)
|
||||||
{
|
{
|
||||||
|
@ -2847,7 +2914,8 @@ struct Compiler
|
||||||
size_t oldLocals = localStack.size();
|
size_t oldLocals = localStack.size();
|
||||||
size_t oldJumps = loopJumps.size();
|
size_t oldJumps = loopJumps.size();
|
||||||
|
|
||||||
loops.push_back({oldLocals, nullptr});
|
loops.push_back({oldLocals, oldLocals, nullptr});
|
||||||
|
hasLoops = true;
|
||||||
|
|
||||||
// register layout: limit, step, index
|
// register layout: limit, step, index
|
||||||
uint8_t regs = allocReg(stat, 3);
|
uint8_t regs = allocReg(stat, 3);
|
||||||
|
@ -2911,7 +2979,8 @@ struct Compiler
|
||||||
size_t oldLocals = localStack.size();
|
size_t oldLocals = localStack.size();
|
||||||
size_t oldJumps = loopJumps.size();
|
size_t oldJumps = loopJumps.size();
|
||||||
|
|
||||||
loops.push_back({oldLocals, nullptr});
|
loops.push_back({oldLocals, oldLocals, nullptr});
|
||||||
|
hasLoops = true;
|
||||||
|
|
||||||
// register layout: generator, state, index, variables...
|
// register layout: generator, state, index, variables...
|
||||||
uint8_t regs = allocReg(stat, 3);
|
uint8_t regs = allocReg(stat, 3);
|
||||||
|
@ -3327,7 +3396,7 @@ struct Compiler
|
||||||
|
|
||||||
// before continuing, we need to close all local variables that were captured in closures since loop start
|
// before continuing, we need to close all local variables that were captured in closures since loop start
|
||||||
// normally they are closed by the enclosing blocks, including the loop block, but we're skipping that here
|
// normally they are closed by the enclosing blocks, including the loop block, but we're skipping that here
|
||||||
closeLocals(loops.back().localOffset);
|
closeLocals(loops.back().localOffsetContinue);
|
||||||
|
|
||||||
size_t label = bytecode.emitLabel();
|
size_t label = bytecode.emitLabel();
|
||||||
|
|
||||||
|
@ -3640,6 +3709,9 @@ struct Compiler
|
||||||
{
|
{
|
||||||
Local& l = self->locals[local];
|
Local& l = self->locals[local];
|
||||||
|
|
||||||
|
if (FFlag::LuauCompileFixContinueValidation && l.skipped)
|
||||||
|
return;
|
||||||
|
|
||||||
if (!l.allocated && !undef)
|
if (!l.allocated && !undef)
|
||||||
undef = local;
|
undef = local;
|
||||||
}
|
}
|
||||||
|
@ -3765,6 +3837,7 @@ struct Compiler
|
||||||
uint8_t reg = 0;
|
uint8_t reg = 0;
|
||||||
bool allocated = false;
|
bool allocated = false;
|
||||||
bool captured = false;
|
bool captured = false;
|
||||||
|
bool skipped = false;
|
||||||
uint32_t debugpc = 0;
|
uint32_t debugpc = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -3783,6 +3856,7 @@ struct Compiler
|
||||||
struct Loop
|
struct Loop
|
||||||
{
|
{
|
||||||
size_t localOffset;
|
size_t localOffset;
|
||||||
|
size_t localOffsetContinue;
|
||||||
|
|
||||||
AstExpr* untilCondition;
|
AstExpr* untilCondition;
|
||||||
};
|
};
|
||||||
|
@ -3830,8 +3904,10 @@ struct Compiler
|
||||||
const DenseHashMap<AstExprCall*, int>* builtinsFold = nullptr;
|
const DenseHashMap<AstExprCall*, int>* builtinsFold = nullptr;
|
||||||
bool builtinsFoldMathK = false;
|
bool builtinsFoldMathK = false;
|
||||||
|
|
||||||
|
// compileFunction state, gets reset for every function
|
||||||
unsigned int regTop = 0;
|
unsigned int regTop = 0;
|
||||||
unsigned int stackSize = 0;
|
unsigned int stackSize = 0;
|
||||||
|
bool hasLoops = false;
|
||||||
|
|
||||||
bool getfenvUsed = false;
|
bool getfenvUsed = false;
|
||||||
bool setfenvUsed = false;
|
bool setfenvUsed = false;
|
||||||
|
@ -3877,8 +3953,15 @@ void compileOrThrow(BytecodeBuilder& bytecode, const ParseResult& parseResult, c
|
||||||
// this pass analyzes mutability of locals/globals and associates locals with their initial values
|
// this pass analyzes mutability of locals/globals and associates locals with their initial values
|
||||||
trackValues(compiler.globals, compiler.variables, root);
|
trackValues(compiler.globals, compiler.variables, root);
|
||||||
|
|
||||||
|
// this visitor tracks calls to getfenv/setfenv and disables some optimizations when they are found
|
||||||
|
if (options.optimizationLevel >= 1 && (names.get("getfenv").value || names.get("setfenv").value))
|
||||||
|
{
|
||||||
|
Compiler::FenvVisitor fenvVisitor(compiler.getfenvUsed, compiler.setfenvUsed);
|
||||||
|
root->visit(&fenvVisitor);
|
||||||
|
}
|
||||||
|
|
||||||
// builtin folding is enabled on optimization level 2 since we can't deoptimize folding at runtime
|
// builtin folding is enabled on optimization level 2 since we can't deoptimize folding at runtime
|
||||||
if (options.optimizationLevel >= 2)
|
if (options.optimizationLevel >= 2 && (!FFlag::LuauCompileFenvNoBuiltinFold || (!compiler.getfenvUsed && !compiler.setfenvUsed)))
|
||||||
{
|
{
|
||||||
compiler.builtinsFold = &compiler.builtins;
|
compiler.builtinsFold = &compiler.builtins;
|
||||||
|
|
||||||
|
@ -3898,13 +3981,6 @@ void compileOrThrow(BytecodeBuilder& bytecode, const ParseResult& parseResult, c
|
||||||
predictTableShapes(compiler.tableShapes, root);
|
predictTableShapes(compiler.tableShapes, root);
|
||||||
}
|
}
|
||||||
|
|
||||||
// this visitor tracks calls to getfenv/setfenv and disables some optimizations when they are found
|
|
||||||
if (options.optimizationLevel >= 1 && (names.get("getfenv").value || names.get("setfenv").value))
|
|
||||||
{
|
|
||||||
Compiler::FenvVisitor fenvVisitor(compiler.getfenvUsed, compiler.setfenvUsed);
|
|
||||||
root->visit(&fenvVisitor);
|
|
||||||
}
|
|
||||||
|
|
||||||
// gathers all functions with the invariant that all function references are to functions earlier in the list
|
// 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
|
// for example, function foo() return function() end end will result in two vector entries, [0] = anonymous and [1] = foo
|
||||||
std::vector<AstExprFunction*> functions;
|
std::vector<AstExprFunction*> functions;
|
||||||
|
|
|
@ -2,11 +2,15 @@
|
||||||
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
||||||
#include "lualib.h"
|
#include "lualib.h"
|
||||||
|
|
||||||
|
#include "lcommon.h"
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
|
||||||
#define LUA_STRFTIMEOPTIONS "aAbBcdHIjmMpSUwWxXyYzZ%"
|
#define LUA_STRFTIMEOPTIONS "aAbBcdHIjmMpSUwWxXyYzZ%"
|
||||||
|
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauOsTimegm, false)
|
||||||
|
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
static tm* gmtime_r(const time_t* timep, tm* result)
|
static tm* gmtime_r(const time_t* timep, tm* result)
|
||||||
{
|
{
|
||||||
|
@ -20,10 +24,49 @@ static tm* localtime_r(const time_t* timep, tm* result)
|
||||||
|
|
||||||
static time_t timegm(struct tm* timep)
|
static time_t timegm(struct tm* timep)
|
||||||
{
|
{
|
||||||
|
LUAU_ASSERT(!FFlag::LuauOsTimegm);
|
||||||
|
|
||||||
return _mkgmtime(timep);
|
return _mkgmtime(timep);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
static time_t os_timegm(struct tm* timep)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(FFlag::LuauOsTimegm);
|
||||||
|
|
||||||
|
// Julian day number calculation
|
||||||
|
int day = timep->tm_mday;
|
||||||
|
int month = timep->tm_mon + 1;
|
||||||
|
int year = timep->tm_year + 1900;
|
||||||
|
|
||||||
|
// year adjustment, pretend that it starts in March
|
||||||
|
int a = timep->tm_mon % 12 < 2 ? 1 : 0;
|
||||||
|
|
||||||
|
// also adjust for out-of-range month numbers in input
|
||||||
|
a -= timep->tm_mon / 12;
|
||||||
|
|
||||||
|
int y = year + 4800 - a;
|
||||||
|
int m = month + (12 * a) - 3;
|
||||||
|
|
||||||
|
int julianday = day + ((153 * m + 2) / 5) + (365 * y) + (y / 4) - (y / 100) + (y / 400) - 32045;
|
||||||
|
|
||||||
|
const int utcstartasjulianday = 2440588; // Jan 1st 1970 offset in Julian calendar
|
||||||
|
const int64_t utcstartasjuliansecond = utcstartasjulianday * 86400ll; // same in seconds
|
||||||
|
|
||||||
|
// fail the dates before UTC start
|
||||||
|
if (julianday < utcstartasjulianday)
|
||||||
|
return time_t(-1);
|
||||||
|
|
||||||
|
int64_t daysecond = timep->tm_hour * 3600ll + timep->tm_min * 60ll + timep->tm_sec;
|
||||||
|
int64_t julianseconds = int64_t(julianday) * 86400ull + daysecond;
|
||||||
|
|
||||||
|
if (julianseconds < utcstartasjuliansecond)
|
||||||
|
return time_t(-1);
|
||||||
|
|
||||||
|
int64_t utc = julianseconds - utcstartasjuliansecond;
|
||||||
|
return time_t(utc);
|
||||||
|
}
|
||||||
|
|
||||||
static int os_clock(lua_State* L)
|
static int os_clock(lua_State* L)
|
||||||
{
|
{
|
||||||
lua_pushnumber(L, lua_clock());
|
lua_pushnumber(L, lua_clock());
|
||||||
|
@ -163,6 +206,9 @@ static int os_time(lua_State* L)
|
||||||
ts.tm_isdst = getboolfield(L, "isdst");
|
ts.tm_isdst = getboolfield(L, "isdst");
|
||||||
|
|
||||||
// Note: upstream Lua uses mktime() here which assumes input is local time, but we prefer UTC for consistency
|
// Note: upstream Lua uses mktime() here which assumes input is local time, but we prefer UTC for consistency
|
||||||
|
if (FFlag::LuauOsTimegm)
|
||||||
|
t = os_timegm(&ts);
|
||||||
|
else
|
||||||
t = timegm(&ts);
|
t = timegm(&ts);
|
||||||
}
|
}
|
||||||
if (t == (time_t)(-1))
|
if (t == (time_t)(-1))
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
#ifndef NOMINMAX
|
#ifndef NOMINMAX
|
||||||
#define NOMINMAX
|
#define NOMINMAX
|
||||||
#endif
|
#endif
|
||||||
#include <Windows.h>
|
#include <windows.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
|
|
|
@ -2061,9 +2061,8 @@ function re.type(...)
|
||||||
return proxy[...] and proxy[...].name;
|
return proxy[...] and proxy[...].name;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
for k, f in pairs(re_m) do
|
-- TODO: table.foreach is currently used as top-level loops needlessly increase native code size for this module
|
||||||
re[k] = f;
|
table.foreach(re_m, function(k, f) re[k] = f end)
|
||||||
end;
|
|
||||||
|
|
||||||
re_m = { __index = re_m };
|
re_m = { __index = re_m };
|
||||||
|
|
||||||
|
|
|
@ -264,6 +264,14 @@ TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "FormsOfSetcc")
|
||||||
SINGLE_COMPARE(setcc(ConditionX64::BelowEqual, byte[rcx]), 0x0f, 0x96, 0x01);
|
SINGLE_COMPARE(setcc(ConditionX64::BelowEqual, byte[rcx]), 0x0f, 0x96, 0x01);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "FormsOfCmov")
|
||||||
|
{
|
||||||
|
SINGLE_COMPARE(cmov(ConditionX64::LessEqual, ebx, eax), 0x0f, 0x4e, 0xd8);
|
||||||
|
SINGLE_COMPARE(cmov(ConditionX64::NotZero, rbx, qword[rax]), 0x48, 0x0f, 0x45, 0x18);
|
||||||
|
SINGLE_COMPARE(cmov(ConditionX64::Zero, rbx, qword[rax + rcx]), 0x48, 0x0f, 0x44, 0x1c, 0x08);
|
||||||
|
SINGLE_COMPARE(cmov(ConditionX64::BelowEqual, r14d, r15d), 0x45, 0x0f, 0x46, 0xf7);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "FormsOfAbsoluteJumps")
|
TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "FormsOfAbsoluteJumps")
|
||||||
{
|
{
|
||||||
SINGLE_COMPARE(jmp(rax), 0xff, 0xe0);
|
SINGLE_COMPARE(jmp(rax), 0xff, 0xe0);
|
||||||
|
@ -590,6 +598,7 @@ TEST_CASE("LogTest")
|
||||||
build.vroundsd(xmm1, xmm2, xmm3, RoundingModeX64::RoundToNearestEven);
|
build.vroundsd(xmm1, xmm2, xmm3, RoundingModeX64::RoundToNearestEven);
|
||||||
build.add(rdx, qword[rcx - 12]);
|
build.add(rdx, qword[rcx - 12]);
|
||||||
build.pop(r12);
|
build.pop(r12);
|
||||||
|
build.cmov(ConditionX64::AboveEqual, rax, rbx);
|
||||||
build.ret();
|
build.ret();
|
||||||
build.int3();
|
build.int3();
|
||||||
|
|
||||||
|
@ -634,6 +643,7 @@ TEST_CASE("LogTest")
|
||||||
vroundsd xmm1,xmm2,xmm3,8
|
vroundsd xmm1,xmm2,xmm3,8
|
||||||
add rdx,qword ptr [rcx-0Ch]
|
add rdx,qword ptr [rcx-0Ch]
|
||||||
pop r12
|
pop r12
|
||||||
|
cmovae rax,rbx
|
||||||
ret
|
ret
|
||||||
int3
|
int3
|
||||||
nop
|
nop
|
||||||
|
|
|
@ -1772,6 +1772,8 @@ RETURN R0 0
|
||||||
|
|
||||||
TEST_CASE("LoopContinueUntil")
|
TEST_CASE("LoopContinueUntil")
|
||||||
{
|
{
|
||||||
|
ScopedFastFlag sff("LuauCompileContinueCloseUpvals", true);
|
||||||
|
|
||||||
// it's valid to use locals defined inside the loop in until expression if they're defined before continue
|
// it's valid to use locals defined inside the loop in until expression if they're defined before continue
|
||||||
CHECK_EQ("\n" + compileFunction0("repeat local r = math.random() if r > 0.5 then continue end r = r + 0.3 until r < 0.5"), R"(
|
CHECK_EQ("\n" + compileFunction0("repeat local r = math.random() if r > 0.5 then continue end r = r + 0.3 until r < 0.5"), R"(
|
||||||
L0: GETIMPORT R0 2 [math.random]
|
L0: GETIMPORT R0 2 [math.random]
|
||||||
|
@ -1833,17 +1835,15 @@ L2: RETURN R0 0
|
||||||
L0: GETIMPORT R0 2 [math.random]
|
L0: GETIMPORT R0 2 [math.random]
|
||||||
CALL R0 0 1
|
CALL R0 0 1
|
||||||
LOADK R1 K3 [0.5]
|
LOADK R1 K3 [0.5]
|
||||||
JUMPIFNOTLT R1 R0 L1
|
JUMPIFLT R1 R0 L1
|
||||||
CLOSEUPVALS R0
|
ADDK R0 R0 K4 [0.29999999999999999]
|
||||||
JUMP L2
|
L1: NEWCLOSURE R1 P0
|
||||||
L1: ADDK R0 R0 K4 [0.29999999999999999]
|
|
||||||
L2: NEWCLOSURE R1 P0
|
|
||||||
CAPTURE REF R0
|
CAPTURE REF R0
|
||||||
CALL R1 0 1
|
CALL R1 0 1
|
||||||
JUMPIF R1 L3
|
JUMPIF R1 L2
|
||||||
CLOSEUPVALS R0
|
CLOSEUPVALS R0
|
||||||
JUMPBACK L0
|
JUMPBACK L0
|
||||||
L3: CLOSEUPVALS R0
|
L2: CLOSEUPVALS R0
|
||||||
RETURN R0 0
|
RETURN R0 0
|
||||||
)");
|
)");
|
||||||
|
|
||||||
|
@ -1895,42 +1895,188 @@ L2: RETURN R0 0
|
||||||
L0: GETIMPORT R0 2 [math.random]
|
L0: GETIMPORT R0 2 [math.random]
|
||||||
CALL R0 0 1
|
CALL R0 0 1
|
||||||
LOADK R1 K3 [0.5]
|
LOADK R1 K3 [0.5]
|
||||||
JUMPIFNOTLT R1 R0 L1
|
JUMPIFLT R1 R0 L1
|
||||||
CLOSEUPVALS R0
|
ADDK R0 R0 K4 [0.29999999999999999]
|
||||||
JUMP L2
|
L1: NEWCLOSURE R1 P0
|
||||||
L1: ADDK R0 R0 K4 [0.29999999999999999]
|
|
||||||
L2: NEWCLOSURE R1 P0
|
|
||||||
CAPTURE UPVAL U0
|
CAPTURE UPVAL U0
|
||||||
CAPTURE REF R0
|
CAPTURE REF R0
|
||||||
CALL R1 0 1
|
CALL R1 0 1
|
||||||
JUMPIF R1 L3
|
JUMPIF R1 L2
|
||||||
CLOSEUPVALS R0
|
CLOSEUPVALS R0
|
||||||
JUMPBACK L0
|
JUMPBACK L0
|
||||||
L3: CLOSEUPVALS R0
|
L2: CLOSEUPVALS R0
|
||||||
RETURN R0 0
|
RETURN R0 0
|
||||||
)");
|
)");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("LoopContinueUntilOops")
|
TEST_CASE("LoopContinueIgnoresImplicitConstant")
|
||||||
{
|
{
|
||||||
|
ScopedFastFlag luauCompileFixContinueValidation{"LuauCompileFixContinueValidation", true};
|
||||||
|
|
||||||
// this used to crash the compiler :(
|
// this used to crash the compiler :(
|
||||||
try
|
CHECK_EQ("\n" + compileFunction0(R"(
|
||||||
{
|
|
||||||
Luau::BytecodeBuilder bcb;
|
|
||||||
Luau::compileOrThrow(bcb, R"(
|
|
||||||
local _
|
local _
|
||||||
repeat
|
repeat
|
||||||
continue
|
continue
|
||||||
until not _
|
until not _
|
||||||
|
)"),
|
||||||
|
R"(
|
||||||
|
RETURN R0 0
|
||||||
|
RETURN R0 0
|
||||||
)");
|
)");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("LoopContinueIgnoresExplicitConstant")
|
||||||
|
{
|
||||||
|
ScopedFastFlag luauCompileFixContinueValidation{"LuauCompileFixContinueValidation", true};
|
||||||
|
|
||||||
|
// Constants do not allocate locals and 'continue' validation should skip them if their lifetime already started
|
||||||
|
CHECK_EQ("\n" + compileFunction0(R"(
|
||||||
|
local c = true
|
||||||
|
repeat
|
||||||
|
continue
|
||||||
|
until c
|
||||||
|
)"),
|
||||||
|
R"(
|
||||||
|
RETURN R0 0
|
||||||
|
RETURN R0 0
|
||||||
|
)");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("LoopContinueRespectsExplicitConstant")
|
||||||
|
{
|
||||||
|
ScopedFastFlag luauCompileFixContinueValidation{"LuauCompileFixContinueValidation", true};
|
||||||
|
|
||||||
|
// If local lifetime hasn't started, even if it's a constant that will not receive an allocation, it cannot be jumped over
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Luau::BytecodeBuilder bcb;
|
||||||
|
Luau::compileOrThrow(bcb, R"(
|
||||||
|
repeat
|
||||||
|
do continue end
|
||||||
|
|
||||||
|
local c = true
|
||||||
|
until c
|
||||||
|
)");
|
||||||
|
|
||||||
|
CHECK(!"Expected CompileError");
|
||||||
}
|
}
|
||||||
catch (Luau::CompileError& e)
|
catch (Luau::CompileError& e)
|
||||||
{
|
{
|
||||||
|
CHECK_EQ(e.getLocation().begin.line + 1, 6);
|
||||||
CHECK_EQ(
|
CHECK_EQ(
|
||||||
std::string(e.what()), "Local _ used in the repeat..until condition is undefined because continue statement on line 4 jumps over it");
|
std::string(e.what()), "Local c used in the repeat..until condition is undefined because continue statement on line 3 jumps over it");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("LoopContinueIgnoresImplicitConstantAfterInline")
|
||||||
|
{
|
||||||
|
ScopedFastFlag luauCompileFixContinueValidation{"LuauCompileFixContinueValidation", true};
|
||||||
|
|
||||||
|
// Inlining might also replace some locals with constants instead of allocating them
|
||||||
|
CHECK_EQ("\n" + compileFunction(R"(
|
||||||
|
local function inline(f)
|
||||||
|
repeat
|
||||||
|
continue
|
||||||
|
until f
|
||||||
|
end
|
||||||
|
|
||||||
|
local function test(...)
|
||||||
|
inline(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
test()
|
||||||
|
)",
|
||||||
|
1, 2),
|
||||||
|
R"(
|
||||||
|
RETURN R0 0
|
||||||
|
RETURN R0 0
|
||||||
|
)");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("LoopContinueUntilCapture")
|
||||||
|
{
|
||||||
|
ScopedFastFlag sff("LuauCompileContinueCloseUpvals", true);
|
||||||
|
|
||||||
|
// validate continue upvalue closing behavior: continue must close locals defined in the nested scopes
|
||||||
|
// but can't close locals defined in the loop scope - these are visible to the condition and will be closed
|
||||||
|
// when evaluating the condition instead.
|
||||||
|
CHECK_EQ("\n" + compileFunction(R"(
|
||||||
|
local a a = 0
|
||||||
|
repeat
|
||||||
|
local b b = 0
|
||||||
|
if a then
|
||||||
|
local c
|
||||||
|
print(function() c = 0 end)
|
||||||
|
if a then
|
||||||
|
continue -- must close c but not a/b
|
||||||
|
end
|
||||||
|
-- must close c
|
||||||
|
end
|
||||||
|
-- must close b but not a
|
||||||
|
until function() a = 0 b = 0 end
|
||||||
|
-- must close b on loop exit
|
||||||
|
-- must close a
|
||||||
|
)",
|
||||||
|
2),
|
||||||
|
R"(
|
||||||
|
LOADNIL R0
|
||||||
|
LOADN R0 0
|
||||||
|
L0: LOADNIL R1
|
||||||
|
LOADN R1 0
|
||||||
|
JUMPIFNOT R0 L2
|
||||||
|
LOADNIL R2
|
||||||
|
GETIMPORT R3 1 [print]
|
||||||
|
NEWCLOSURE R4 P0
|
||||||
|
CAPTURE REF R2
|
||||||
|
CALL R3 1 0
|
||||||
|
JUMPIFNOT R0 L1
|
||||||
|
CLOSEUPVALS R2
|
||||||
|
JUMP L2
|
||||||
|
L1: CLOSEUPVALS R2
|
||||||
|
L2: NEWCLOSURE R2 P1
|
||||||
|
CAPTURE REF R0
|
||||||
|
CAPTURE REF R1
|
||||||
|
JUMPIF R2 L3
|
||||||
|
CLOSEUPVALS R1
|
||||||
|
JUMPBACK L0
|
||||||
|
L3: CLOSEUPVALS R1
|
||||||
|
CLOSEUPVALS R0
|
||||||
|
RETURN R0 0
|
||||||
|
)");
|
||||||
|
|
||||||
|
// a simpler version of the above test doesn't need to close anything when evaluating continue
|
||||||
|
CHECK_EQ("\n" + compileFunction(R"(
|
||||||
|
local a a = 0
|
||||||
|
repeat
|
||||||
|
local b b = 0
|
||||||
|
if a then
|
||||||
|
continue -- must not close a/b
|
||||||
|
end
|
||||||
|
-- must close b but not a
|
||||||
|
until function() a = 0 b = 0 end
|
||||||
|
-- must close b on loop exit
|
||||||
|
-- must close a
|
||||||
|
)",
|
||||||
|
1),
|
||||||
|
R"(
|
||||||
|
LOADNIL R0
|
||||||
|
LOADN R0 0
|
||||||
|
L0: LOADNIL R1
|
||||||
|
LOADN R1 0
|
||||||
|
JUMPIF R0 L1
|
||||||
|
L1: NEWCLOSURE R2 P0
|
||||||
|
CAPTURE REF R0
|
||||||
|
CAPTURE REF R1
|
||||||
|
JUMPIF R2 L2
|
||||||
|
CLOSEUPVALS R1
|
||||||
|
JUMPBACK L0
|
||||||
|
L2: CLOSEUPVALS R1
|
||||||
|
CLOSEUPVALS R0
|
||||||
|
RETURN R0 0
|
||||||
|
)");
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("AndOrOptimizations")
|
TEST_CASE("AndOrOptimizations")
|
||||||
{
|
{
|
||||||
// the OR/ORK optimization triggers for cutoff since lhs is simple
|
// the OR/ORK optimization triggers for cutoff since lhs is simple
|
||||||
|
@ -6368,7 +6514,7 @@ return
|
||||||
math.log10(100),
|
math.log10(100),
|
||||||
math.log(1),
|
math.log(1),
|
||||||
math.log(4, 2),
|
math.log(4, 2),
|
||||||
math.log(27, 3),
|
math.log(64, 4),
|
||||||
math.max(1, 2, 3),
|
math.max(1, 2, 3),
|
||||||
math.min(1, 2, 3),
|
math.min(1, 2, 3),
|
||||||
math.pow(3, 3),
|
math.pow(3, 3),
|
||||||
|
@ -7408,4 +7554,27 @@ RETURN R0 1
|
||||||
)");
|
)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("NoBuiltinFoldFenv")
|
||||||
|
{
|
||||||
|
ScopedFastFlag sff("LuauCompileFenvNoBuiltinFold", true);
|
||||||
|
|
||||||
|
// builtin folding is disabled when getfenv/setfenv is used in the module
|
||||||
|
CHECK_EQ("\n" + compileFunction(R"(
|
||||||
|
getfenv()
|
||||||
|
|
||||||
|
function test()
|
||||||
|
return math.pi, math.sin(0)
|
||||||
|
end
|
||||||
|
)",
|
||||||
|
0, 2),
|
||||||
|
R"(
|
||||||
|
GETIMPORT R0 2 [math.pi]
|
||||||
|
LOADN R2 0
|
||||||
|
FASTCALL1 24 R2 L0
|
||||||
|
GETIMPORT R1 4 [math.sin]
|
||||||
|
CALL R1 1 1
|
||||||
|
L0: RETURN R0 2
|
||||||
|
)");
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
#include "Luau/TypeInfer.h"
|
#include "Luau/TypeInfer.h"
|
||||||
#include "Luau/BytecodeBuilder.h"
|
#include "Luau/BytecodeBuilder.h"
|
||||||
#include "Luau/Frontend.h"
|
#include "Luau/Frontend.h"
|
||||||
|
#include "Luau/CodeGen.h"
|
||||||
|
|
||||||
#include "doctest.h"
|
#include "doctest.h"
|
||||||
#include "ScopedFlags.h"
|
#include "ScopedFlags.h"
|
||||||
|
@ -224,7 +225,7 @@ static StateRef runConformance(const char* name, void (*setup)(lua_State* L) = n
|
||||||
free(bytecode);
|
free(bytecode);
|
||||||
|
|
||||||
if (result == 0 && codegen && !skipCodegen && luau_codegen_supported())
|
if (result == 0 && codegen && !skipCodegen && luau_codegen_supported())
|
||||||
luau_codegen_compile(L, -1);
|
Luau::CodeGen::compile(L, -1, Luau::CodeGen::CodeGen_ColdFunctions);
|
||||||
|
|
||||||
int status = (result == 0) ? lua_resume(L, nullptr, 0) : LUA_ERRSYNTAX;
|
int status = (result == 0) ? lua_resume(L, nullptr, 0) : LUA_ERRSYNTAX;
|
||||||
|
|
||||||
|
@ -288,7 +289,7 @@ TEST_CASE("Assert")
|
||||||
TEST_CASE("Basic")
|
TEST_CASE("Basic")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sffs{"LuauFloorDivision", true};
|
ScopedFastFlag sffs{"LuauFloorDivision", true};
|
||||||
ScopedFastFlag sfff{"LuauImproveForN", true};
|
ScopedFastFlag sfff{"LuauImproveForN2", true};
|
||||||
|
|
||||||
runConformance("basic.lua");
|
runConformance("basic.lua");
|
||||||
}
|
}
|
||||||
|
@ -379,6 +380,8 @@ TEST_CASE("Events")
|
||||||
|
|
||||||
TEST_CASE("Constructs")
|
TEST_CASE("Constructs")
|
||||||
{
|
{
|
||||||
|
ScopedFastFlag sff("LuauCompileContinueCloseUpvals", true);
|
||||||
|
|
||||||
runConformance("constructs.lua");
|
runConformance("constructs.lua");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1809,8 +1812,6 @@ TEST_CASE("Native")
|
||||||
|
|
||||||
TEST_CASE("NativeTypeAnnotations")
|
TEST_CASE("NativeTypeAnnotations")
|
||||||
{
|
{
|
||||||
ScopedFastFlag bytecodeVersion4("BytecodeVersion4", true);
|
|
||||||
|
|
||||||
// This tests requires code to run natively, otherwise all 'is_native' checks will fail
|
// This tests requires code to run natively, otherwise all 'is_native' checks will fail
|
||||||
if (!codegen || !luau_codegen_supported())
|
if (!codegen || !luau_codegen_supported())
|
||||||
return;
|
return;
|
||||||
|
@ -1891,7 +1892,7 @@ TEST_CASE("HugeFunction")
|
||||||
REQUIRE(result == 0);
|
REQUIRE(result == 0);
|
||||||
|
|
||||||
if (codegen && luau_codegen_supported())
|
if (codegen && luau_codegen_supported())
|
||||||
luau_codegen_compile(L, -1);
|
Luau::CodeGen::compile(L, -1, Luau::CodeGen::CodeGen_ColdFunctions);
|
||||||
|
|
||||||
int status = lua_resume(L, nullptr, 0);
|
int status = lua_resume(L, nullptr, 0);
|
||||||
REQUIRE(status == 0);
|
REQUIRE(status == 0);
|
||||||
|
|
|
@ -3,10 +3,28 @@
|
||||||
|
|
||||||
#include "Fixture.h"
|
#include "Fixture.h"
|
||||||
|
|
||||||
|
#include "Luau/Common.h"
|
||||||
|
#include "Luau/Ast.h"
|
||||||
|
#include "Luau/ModuleResolver.h"
|
||||||
|
#include "ScopedFlags.h"
|
||||||
#include "doctest.h"
|
#include "doctest.h"
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
using namespace Luau;
|
using namespace Luau;
|
||||||
|
|
||||||
|
struct NonStrictTypeCheckerFixture : Fixture
|
||||||
|
{
|
||||||
|
|
||||||
|
ParseResult parse(std::string source)
|
||||||
|
{
|
||||||
|
ParseOptions opts;
|
||||||
|
opts.allowDeclarationSyntax = true;
|
||||||
|
ScopedFastFlag sff{"LuauCheckedFunctionSyntax", true};
|
||||||
|
return tryParse(source, opts);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
TEST_SUITE_BEGIN("NonStrictTypeCheckerTest");
|
TEST_SUITE_BEGIN("NonStrictTypeCheckerTest");
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "basic")
|
TEST_CASE_FIXTURE(Fixture, "basic")
|
||||||
|
@ -14,4 +32,85 @@ TEST_CASE_FIXTURE(Fixture, "basic")
|
||||||
Luau::checkNonStrict(builtinTypes, nullptr);
|
Luau::checkNonStrict(builtinTypes, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "parse_top_level_checked_fn")
|
||||||
|
{
|
||||||
|
std::string src = R"BUILTIN_SRC(
|
||||||
|
declare function @checked abs(n: number): number
|
||||||
|
)BUILTIN_SRC";
|
||||||
|
|
||||||
|
ParseResult pr = parse(src);
|
||||||
|
LUAU_ASSERT(pr.errors.size() == 0);
|
||||||
|
|
||||||
|
LUAU_ASSERT(pr.root->body.size == 1);
|
||||||
|
AstStat* root = *(pr.root->body.data);
|
||||||
|
auto func = root->as<AstStatDeclareFunction>();
|
||||||
|
LUAU_ASSERT(func);
|
||||||
|
LUAU_ASSERT(func->checkedFunction);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "parse_declared_table_checked_member")
|
||||||
|
{
|
||||||
|
std::string src = R"BUILTIN_SRC(
|
||||||
|
declare math : {
|
||||||
|
abs : @checked (number) -> number
|
||||||
|
}
|
||||||
|
)BUILTIN_SRC";
|
||||||
|
|
||||||
|
ParseResult pr = parse(src);
|
||||||
|
LUAU_ASSERT(pr.errors.size() == 0);
|
||||||
|
|
||||||
|
LUAU_ASSERT(pr.root->body.size == 1);
|
||||||
|
AstStat* root = *(pr.root->body.data);
|
||||||
|
auto glob = root->as<AstStatDeclareGlobal>();
|
||||||
|
LUAU_ASSERT(glob);
|
||||||
|
auto tbl = glob->type->as<AstTypeTable>();
|
||||||
|
LUAU_ASSERT(tbl);
|
||||||
|
LUAU_ASSERT(tbl->props.size == 1);
|
||||||
|
auto prop = *tbl->props.data;
|
||||||
|
auto func = prop.type->as<AstTypeFunction>();
|
||||||
|
LUAU_ASSERT(func);
|
||||||
|
LUAU_ASSERT(func->checkedFunction);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "parse_checked_outside_decl_fails")
|
||||||
|
{
|
||||||
|
auto src = R"(
|
||||||
|
local @checked = 3
|
||||||
|
)";
|
||||||
|
|
||||||
|
ParseResult pr = parse(src);
|
||||||
|
LUAU_ASSERT(pr.errors.size() > 0);
|
||||||
|
auto ts = pr.errors[1].getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "parse_checked_in_and_out_of_decl_fails")
|
||||||
|
{
|
||||||
|
auto src = R"(
|
||||||
|
local @checked = 3
|
||||||
|
declare function @checked abs(n: number): number
|
||||||
|
)";
|
||||||
|
auto pr = parse(src);
|
||||||
|
LUAU_ASSERT(pr.errors.size() == 2);
|
||||||
|
LUAU_ASSERT(pr.errors[0].getLocation().begin.line == 1);
|
||||||
|
LUAU_ASSERT(pr.errors[1].getLocation().begin.line == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "parse_checked_as_function_name_fails")
|
||||||
|
{
|
||||||
|
auto pr = parse(R"(
|
||||||
|
function @checked(x: number) : number
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
LUAU_ASSERT(pr.errors.size() > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "cannot_use_@_as_variable_name")
|
||||||
|
{
|
||||||
|
auto pr = parse(R"(
|
||||||
|
local @blah = 3
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_ASSERT(pr.errors.size() > 0);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -598,6 +598,20 @@ TEST_CASE_FIXTURE(SubtypeFixture, "(number) -> () <!: <T>(T) -> ()")
|
||||||
CHECK_IS_NOT_SUBTYPE(numberToNothingType, genericTToNothingType);
|
CHECK_IS_NOT_SUBTYPE(numberToNothingType, genericTToNothingType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(SubtypeFixture, "<T>() -> (T, T) <!: () -> (string, number)")
|
||||||
|
{
|
||||||
|
TypeId nothingToTwoTs = arena.addType(FunctionType{
|
||||||
|
{genericT},
|
||||||
|
{},
|
||||||
|
builtinTypes->emptyTypePack,
|
||||||
|
arena.addTypePack({genericT, genericT})
|
||||||
|
});
|
||||||
|
|
||||||
|
TypeId nothingToStringAndNumber = fn({}, {builtinTypes->stringType, builtinTypes->numberType});
|
||||||
|
|
||||||
|
CHECK_IS_NOT_SUBTYPE(nothingToTwoTs, nothingToStringAndNumber);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(SubtypeFixture, "<A...>(A...) -> A... <: (number) -> number")
|
TEST_CASE_FIXTURE(SubtypeFixture, "<A...>(A...) -> A... <: (number) -> number")
|
||||||
{
|
{
|
||||||
CHECK_IS_SUBTYPE(genericAsToAsType, numberToNumberType);
|
CHECK_IS_SUBTYPE(genericAsToAsType, numberToNumberType);
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
#include "Luau/TypeFamily.h"
|
#include "Luau/TypeFamily.h"
|
||||||
|
|
||||||
#include "Luau/ConstraintSolver.h"
|
#include "Luau/ConstraintSolver.h"
|
||||||
|
#include "Luau/NotNull.h"
|
||||||
#include "Luau/TxnLog.h"
|
#include "Luau/TxnLog.h"
|
||||||
#include "Luau/Type.h"
|
#include "Luau/Type.h"
|
||||||
|
|
||||||
|
@ -22,21 +23,20 @@ struct FamilyFixture : Fixture
|
||||||
{
|
{
|
||||||
swapFamily = TypeFamily{/* name */ "Swap",
|
swapFamily = TypeFamily{/* name */ "Swap",
|
||||||
/* reducer */
|
/* reducer */
|
||||||
[](std::vector<TypeId> tys, std::vector<TypePackId> tps, NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtins,
|
[](std::vector<TypeId> tys, std::vector<TypePackId> tps, NotNull<TypeFamilyContext> ctx) -> TypeFamilyReductionResult<TypeId> {
|
||||||
NotNull<const TxnLog> log, NotNull<Scope> scope, NotNull<Normalizer> normalizer, ConstraintSolver* solver) -> TypeFamilyReductionResult<TypeId> {
|
|
||||||
LUAU_ASSERT(tys.size() == 1);
|
LUAU_ASSERT(tys.size() == 1);
|
||||||
TypeId param = log->follow(tys.at(0));
|
TypeId param = follow(tys.at(0));
|
||||||
|
|
||||||
if (isString(param))
|
if (isString(param))
|
||||||
{
|
{
|
||||||
return TypeFamilyReductionResult<TypeId>{builtins->numberType, false, {}, {}};
|
return TypeFamilyReductionResult<TypeId>{ctx->builtins->numberType, false, {}, {}};
|
||||||
}
|
}
|
||||||
else if (isNumber(param))
|
else if (isNumber(param))
|
||||||
{
|
{
|
||||||
return TypeFamilyReductionResult<TypeId>{builtins->stringType, false, {}, {}};
|
return TypeFamilyReductionResult<TypeId>{ctx->builtins->stringType, false, {}, {}};
|
||||||
}
|
}
|
||||||
else if (log->get<BlockedType>(param) || log->get<PendingExpansionType>(param) ||
|
else if (is<BlockedType>(param) || is<PendingExpansionType>(param) || is<TypeFamilyInstanceType>(param) ||
|
||||||
log->get<TypeFamilyInstanceType>(param) || (solver && solver->hasUnresolvedConstraints(param)))
|
(ctx->solver && ctx->solver->hasUnresolvedConstraints(param)))
|
||||||
{
|
{
|
||||||
return TypeFamilyReductionResult<TypeId>{std::nullopt, false, {param}, {}};
|
return TypeFamilyReductionResult<TypeId>{std::nullopt, false, {param}, {}};
|
||||||
}
|
}
|
||||||
|
|
|
@ -198,10 +198,7 @@ TEST_CASE_FIXTURE(Fixture, "generic_aliases")
|
||||||
)");
|
)");
|
||||||
|
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
const std::string expected = R"(Type 'bad' could not be converted into 'T<number>'
|
const std::string expected = "Type 'bad' could not be converted into 'T<number>'";
|
||||||
caused by:
|
|
||||||
Property 'v' is not compatible.
|
|
||||||
Type 'string' could not be converted into 'number' in an invariant context)";
|
|
||||||
CHECK(result.errors[0].location == Location{{4, 31}, {4, 44}});
|
CHECK(result.errors[0].location == Location{{4, 31}, {4, 44}});
|
||||||
CHECK_EQ(expected, toString(result.errors[0]));
|
CHECK_EQ(expected, toString(result.errors[0]));
|
||||||
}
|
}
|
||||||
|
@ -220,13 +217,7 @@ TEST_CASE_FIXTURE(Fixture, "dependent_generic_aliases")
|
||||||
)");
|
)");
|
||||||
|
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
const std::string expected = R"(Type 'bad' could not be converted into 'U<number>'
|
const std::string expected = "Type 'bad' could not be converted into 'U<number>'";
|
||||||
caused by:
|
|
||||||
Property 't' is not compatible.
|
|
||||||
Type '{| v: string |}' could not be converted into 'T<number>'
|
|
||||||
caused by:
|
|
||||||
Property 'v' is not compatible.
|
|
||||||
Type 'string' could not be converted into 'number' in an invariant context)";
|
|
||||||
|
|
||||||
CHECK(result.errors[0].location == Location{{4, 31}, {4, 52}});
|
CHECK(result.errors[0].location == Location{{4, 31}, {4, 52}});
|
||||||
CHECK_EQ(expected, toString(result.errors[0]));
|
CHECK_EQ(expected, toString(result.errors[0]));
|
||||||
|
|
|
@ -997,7 +997,7 @@ TEST_CASE_FIXTURE(Fixture, "cli_80596_simplify_degenerate_intersections")
|
||||||
local x: number = obj.x or 3
|
local x: number = obj.x or 3
|
||||||
)");
|
)");
|
||||||
|
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_ERRORS(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "cli_80596_simplify_more_realistic_intersections")
|
TEST_CASE_FIXTURE(Fixture, "cli_80596_simplify_more_realistic_intersections")
|
||||||
|
@ -1023,7 +1023,7 @@ TEST_CASE_FIXTURE(Fixture, "cli_80596_simplify_more_realistic_intersections")
|
||||||
local x: number = obj.x or 3
|
local x: number = obj.x or 3
|
||||||
)");
|
)");
|
||||||
|
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_ERRORS(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -936,6 +936,128 @@ TEST_CASE_FIXTURE(Fixture, "infer_any_in_all_modes_when_lhs_is_unknown")
|
||||||
// the case right now, though.
|
// the case right now, though.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "infer_type_for_generic_subtraction")
|
||||||
|
{
|
||||||
|
CheckResult result = check(Mode::Strict, R"(
|
||||||
|
local function f(x, y)
|
||||||
|
return x - y
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
{
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
CHECK(toString(requireType("f")) == "<a, b>(a, b) -> Sub<a, b>");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
CHECK_EQ(toString(result.errors[0]), "Unknown type used in - operation; consider adding a type annotation to 'x'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "infer_type_for_generic_multiplication")
|
||||||
|
{
|
||||||
|
CheckResult result = check(Mode::Strict, R"(
|
||||||
|
local function f(x, y)
|
||||||
|
return x * y
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
{
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
CHECK(toString(requireType("f")) == "<a, b>(a, b) -> Mul<a, b>");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
CHECK_EQ(toString(result.errors[0]), "Unknown type used in * operation; consider adding a type annotation to 'x'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "infer_type_for_generic_division")
|
||||||
|
{
|
||||||
|
CheckResult result = check(Mode::Strict, R"(
|
||||||
|
local function f(x, y)
|
||||||
|
return x / y
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
{
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
CHECK(toString(requireType("f")) == "<a, b>(a, b) -> Div<a, b>");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
CHECK_EQ(toString(result.errors[0]), "Unknown type used in / operation; consider adding a type annotation to 'x'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "infer_type_for_generic_floor_division")
|
||||||
|
{
|
||||||
|
ScopedFastFlag floorDiv{"LuauFloorDivision", true};
|
||||||
|
|
||||||
|
CheckResult result = check(Mode::Strict, R"(
|
||||||
|
local function f(x, y)
|
||||||
|
return x // y
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
{
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
CHECK(toString(requireType("f")) == "<a, b>(a, b) -> FloorDiv<a, b>");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
CHECK_EQ(toString(result.errors[0]), "Unknown type used in // operation; consider adding a type annotation to 'x'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "infer_type_for_generic_exponentiation")
|
||||||
|
{
|
||||||
|
CheckResult result = check(Mode::Strict, R"(
|
||||||
|
local function f(x, y)
|
||||||
|
return x ^ y
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
{
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
CHECK(toString(requireType("f")) == "<a, b>(a, b) -> Exp<a, b>");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
CHECK_EQ(toString(result.errors[0]), "Unknown type used in ^ operation; consider adding a type annotation to 'x'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "infer_type_for_generic_modulo")
|
||||||
|
{
|
||||||
|
CheckResult result = check(Mode::Strict, R"(
|
||||||
|
local function f(x, y)
|
||||||
|
return x % y
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
{
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
CHECK(toString(requireType("f")) == "<a, b>(a, b) -> Mod<a, b>");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
CHECK_EQ(toString(result.errors[0]), "Unknown type used in % operation; consider adding a type annotation to 'x'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "equality_operations_succeed_if_any_union_branch_succeeds")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "equality_operations_succeed_if_any_union_branch_succeeds")
|
||||||
{
|
{
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
|
|
|
@ -1942,4 +1942,54 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "conditional_refinement_should_stay_error_sup
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "globals_can_be_narrowed_too")
|
||||||
|
{
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
if typeof(string) == 'string' then
|
||||||
|
local foo = string
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
CHECK("never" == toString(requireTypeAtPosition(Position{2, 24})));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "luau_polyfill_isindexkey_refine_conjunction")
|
||||||
|
{
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local function isIndexKey(k, contiguousLength)
|
||||||
|
return type(k) == "number"
|
||||||
|
and k <= contiguousLength -- nothing out of bounds
|
||||||
|
and 1 <= k -- nothing illegal for array indices
|
||||||
|
and math.floor(k) == k -- no float keys
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "luau_polyfill_isindexkey_refine_conjunction_variant")
|
||||||
|
{
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local function isIndexKey(k, contiguousLength: number)
|
||||||
|
return type(k) == "number"
|
||||||
|
and k <= contiguousLength -- nothing out of bounds
|
||||||
|
and 1 <= k -- nothing illegal for array indices
|
||||||
|
and math.floor(k) == k -- no float keys
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "globals_can_be_narrowed_too")
|
||||||
|
{
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
if typeof(string) == 'string' then
|
||||||
|
local foo = string
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
CHECK("never" == toString(requireTypeAtPosition(Position{2, 24})));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -366,10 +366,7 @@ TEST_CASE_FIXTURE(Fixture, "parametric_tagged_union_alias")
|
||||||
|
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
|
||||||
const std::string expectedError = R"(Type 'a' could not be converted into 'Err<number> | Ok<string>'
|
const std::string expectedError = "Type 'a' could not be converted into 'Err<number> | Ok<string>'";
|
||||||
caused by:
|
|
||||||
None of the union options are compatible. For example:
|
|
||||||
Table type 'a' not compatible with type 'Err<number>' because the former is missing field 'error')";
|
|
||||||
|
|
||||||
CHECK(toString(result.errors[0]) == expectedError);
|
CHECK(toString(result.errors[0]) == expectedError);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2672,7 +2672,7 @@ TEST_CASE_FIXTURE(Fixture, "generalize_table_argument")
|
||||||
std::optional<TypeId> fooArg1 = first(fooType->argTypes);
|
std::optional<TypeId> fooArg1 = first(fooType->argTypes);
|
||||||
REQUIRE(fooArg1);
|
REQUIRE(fooArg1);
|
||||||
|
|
||||||
const TableType* fooArg1Table = get<TableType>(*fooArg1);
|
const TableType* fooArg1Table = get<TableType>(follow(*fooArg1));
|
||||||
REQUIRE(fooArg1Table);
|
REQUIRE(fooArg1Table);
|
||||||
|
|
||||||
CHECK_EQ(fooArg1Table->state, TableState::Generic);
|
CHECK_EQ(fooArg1Table->state, TableState::Generic);
|
||||||
|
|
|
@ -237,4 +237,19 @@ repeat
|
||||||
i = i+1
|
i = i+1
|
||||||
until i==c
|
until i==c
|
||||||
|
|
||||||
|
-- validate continue upvalue close behavior
|
||||||
|
local function check_connected(writer, reader)
|
||||||
|
writer(1)
|
||||||
|
assert(reader() == 1)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
repeat
|
||||||
|
local value = nil
|
||||||
|
local function write(n)
|
||||||
|
value = n
|
||||||
|
end
|
||||||
|
continue
|
||||||
|
until check_connected(write, function() return value end)
|
||||||
|
|
||||||
return 'OK'
|
return 'OK'
|
||||||
|
|
|
@ -23,6 +23,29 @@ assert(os.time({ year = 1970, month = 1, day = 1, hour = 0, min = 0, sec = 0}) =
|
||||||
assert(os.time({ year = 3000, month = 12, day = 31, hour = 23, min = 59, sec = 59}) == 32535215999) -- just before Windows max range
|
assert(os.time({ year = 3000, month = 12, day = 31, hour = 23, min = 59, sec = 59}) == 32535215999) -- just before Windows max range
|
||||||
assert(os.time({ year = 1970, month = 1, day = 1, hour = 0, min = 0, sec = -1}) == nil) -- going before using time fields
|
assert(os.time({ year = 1970, month = 1, day = 1, hour = 0, min = 0, sec = -1}) == nil) -- going before using time fields
|
||||||
|
|
||||||
|
assert(os.time({ year = 2000, month = 6, day = 10, hour = 0, min = 0, sec = 0}) == 960595200)
|
||||||
|
assert(os.time({ year = 2000, month = 6, day = 10, hour = 0, min = 0, sec = -86400}) == 960508800)
|
||||||
|
assert(os.time({ year = 2000, month = 6, day = 10, hour = 0, min = 0, sec = 86400}) == 960681600)
|
||||||
|
assert(os.time({ year = 2000, month = 6, day = 10, hour = 0, min = -600, sec = 0}) == 960559200)
|
||||||
|
assert(os.time({ year = 2000, month = 6, day = 10, hour = 0, min = 600, sec = 0}) == 960631200)
|
||||||
|
assert(os.time({ year = 2000, month = 6, day = 10, hour = -600, min = 0, sec = 0}) == 958435200)
|
||||||
|
assert(os.time({ year = 2000, month = 6, day = 10, hour = 600, min = 0, sec = 0}) == 962755200)
|
||||||
|
assert(os.time({ year = 2000, month = 6, day = -100, hour = 0, min = 0, sec = 0}) == 951091200)
|
||||||
|
assert(os.time({ year = 2000, month = 6, day = 1000, hour = 0, min = 0, sec = 0}) == 1046131200)
|
||||||
|
assert(os.time({ year = 2000, month = -60, day = 10, hour = 0, min = 0, sec = 0}) == 787017600)
|
||||||
|
assert(os.time({ year = 2000, month = 60, day = 10, hour = 0, min = 0, sec = 0}) == 1102636800)
|
||||||
|
|
||||||
|
assert(os.time({ year = 2000, month = 6, day = 10, hour = 0, min = 0, sec = -86400000}) == 874195200)
|
||||||
|
assert(os.time({ year = 2000, month = 6, day = 10, hour = 0, min = 0, sec = 86400000}) == 1046995200)
|
||||||
|
assert(os.time({ year = 2000, month = 6, day = 10, hour = 0, min = -600000, sec = 0}) == 924595200)
|
||||||
|
assert(os.time({ year = 2000, month = 6, day = 10, hour = 0, min = 600000, sec = 0}) == 996595200)
|
||||||
|
assert(os.time({ year = 2100, month = 6, day = 10, hour = -600000, min = 0, sec = 0}) == 1956268800)
|
||||||
|
assert(os.time({ year = 2100, month = 6, day = 10, hour = 600000, min = 0, sec = 0}) == 6276268800)
|
||||||
|
assert(os.time({ year = 2100, month = 6, day = -10000, hour = 0, min = 0, sec = 0}) == 3251404800)
|
||||||
|
assert(os.time({ year = 2100, month = 6, day = 100000, hour = 0, min = 0, sec = 0}) == 12755404800)
|
||||||
|
assert(os.time({ year = 2100, month = -600, day = 10, hour = 0, min = 0, sec = 0}) == 2522707200)
|
||||||
|
assert(os.time({ year = 2100, month = 600, day = 10, hour = 0, min = 0, sec = 0}) == 5678380800)
|
||||||
|
|
||||||
local function checkDateTable (t)
|
local function checkDateTable (t)
|
||||||
local D = os.date("!*t", t)
|
local D = os.date("!*t", t)
|
||||||
assert(os.time(D) == t)
|
assert(os.time(D) == t)
|
||||||
|
|
|
@ -346,7 +346,7 @@ assert(math.log10("10") == 1)
|
||||||
assert(math.log("0") == -inf)
|
assert(math.log("0") == -inf)
|
||||||
assert(math.log("8", 2) == 3)
|
assert(math.log("8", 2) == 3)
|
||||||
assert(math.log("10", 10) == 1)
|
assert(math.log("10", 10) == 1)
|
||||||
assert(math.log("9", 3) == 2)
|
assert(math.log("16", 4) == 2)
|
||||||
assert(math.max("1", 2) == 2)
|
assert(math.max("1", 2) == 2)
|
||||||
assert(math.max(2, "1") == 2)
|
assert(math.max(2, "1") == 2)
|
||||||
assert(math.max(1, 2, "3") == 3)
|
assert(math.max(1, 2, "3") == 3)
|
||||||
|
|
|
@ -203,6 +203,33 @@ local function arraySizeOpt2(a, i)
|
||||||
return a[i] + a[5]
|
return a[i] + a[5]
|
||||||
end
|
end
|
||||||
|
|
||||||
assert(arraySizeOpt1({1}, 1) == 71)
|
assert(arraySizeOpt2({1}, 1) == 71)
|
||||||
|
|
||||||
|
function deadLoopBody(n)
|
||||||
|
local r = 0
|
||||||
|
if n and false then
|
||||||
|
for i = 1, n do
|
||||||
|
r += 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return r
|
||||||
|
end
|
||||||
|
|
||||||
|
assert(deadLoopBody(5) == 0)
|
||||||
|
|
||||||
|
function arrayIndexingSpecialNumbers1(a, b, c)
|
||||||
|
local arr = table.create(100000)
|
||||||
|
arr[a] = 9
|
||||||
|
arr[b-1] = 80
|
||||||
|
arr[b] = 700
|
||||||
|
arr[b+1] = 6000
|
||||||
|
arr[c-1] = 50000
|
||||||
|
arr[c] = 400000
|
||||||
|
arr[c+1] = 3000000
|
||||||
|
|
||||||
|
return arr[1] + arr[255] + arr[256] + arr[257] + arr[65535] + arr[65536] + arr[65537]
|
||||||
|
end
|
||||||
|
|
||||||
|
assert(arrayIndexingSpecialNumbers1(1, 256, 65536) == 3456789)
|
||||||
|
|
||||||
return('OK')
|
return('OK')
|
||||||
|
|
|
@ -24,6 +24,8 @@ assert(tostring(-0.17) == "-0.17")
|
||||||
assert(tostring(math.pi) == "3.141592653589793")
|
assert(tostring(math.pi) == "3.141592653589793")
|
||||||
|
|
||||||
-- fuzzing corpus
|
-- fuzzing corpus
|
||||||
|
-- Note: If the assert below fires it may indicate floating point denormalized values
|
||||||
|
-- are not handled as expected.
|
||||||
assert(tostring(5.4536123983019448e-311) == "5.453612398302e-311")
|
assert(tostring(5.4536123983019448e-311) == "5.453612398302e-311")
|
||||||
assert(tostring(5.4834368411298348e-311) == "5.48343684113e-311")
|
assert(tostring(5.4834368411298348e-311) == "5.48343684113e-311")
|
||||||
assert(tostring(4.4154895841930002e-305) == "4.415489584193e-305")
|
assert(tostring(4.4154895841930002e-305) == "4.415489584193e-305")
|
||||||
|
|
|
@ -15,7 +15,11 @@
|
||||||
#ifndef NOMINMAX
|
#ifndef NOMINMAX
|
||||||
#define NOMINMAX
|
#define NOMINMAX
|
||||||
#endif
|
#endif
|
||||||
#include <Windows.h> // IsDebuggerPresent
|
#include <windows.h> // IsDebuggerPresent
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__x86_64__) || defined(_M_X64)
|
||||||
|
#include <immintrin.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
|
@ -253,8 +257,21 @@ static void setFastFlags(const std::vector<doctest::String>& flags)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This function performs system/architecture specific initialization prior to running tests.
|
||||||
|
static void initSystem()
|
||||||
|
{
|
||||||
|
#if defined(__x86_64__) || defined(_M_X64)
|
||||||
|
// Some unit tests make use of denormalized numbers. So flags to flush to zero or treat denormals as zero
|
||||||
|
// must be disabled for expected behavior.
|
||||||
|
_MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_OFF);
|
||||||
|
_MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_OFF);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char** argv)
|
int main(int argc, char** argv)
|
||||||
{
|
{
|
||||||
|
initSystem();
|
||||||
|
|
||||||
Luau::assertHandler() = testAssertionHandler;
|
Luau::assertHandler() = testAssertionHandler;
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
AnnotationTests.infer_type_of_value_a_via_typeof_with_assignment
|
AnnotationTests.infer_type_of_value_a_via_typeof_with_assignment
|
||||||
|
AnnotationTests.two_type_params
|
||||||
|
AnnotationTests.use_generic_type_alias
|
||||||
AstQuery.last_argument_function_call_type
|
AstQuery.last_argument_function_call_type
|
||||||
AstQuery::getDocumentationSymbolAtPosition.table_overloaded_function_prop
|
AstQuery::getDocumentationSymbolAtPosition.table_overloaded_function_prop
|
||||||
AutocompleteTest.anonymous_autofilled_generic_on_argument_type_pack_vararg
|
AutocompleteTest.anonymous_autofilled_generic_on_argument_type_pack_vararg
|
||||||
|
@ -24,6 +26,7 @@ AutocompleteTest.type_correct_suggestion_in_argument
|
||||||
AutocompleteTest.unsealed_table_2
|
AutocompleteTest.unsealed_table_2
|
||||||
BuiltinTests.aliased_string_format
|
BuiltinTests.aliased_string_format
|
||||||
BuiltinTests.assert_removes_falsy_types
|
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_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.assert_returns_false_and_string_iff_it_knows_the_first_argument_cannot_be_truthy
|
||||||
BuiltinTests.bad_select_should_not_crash
|
BuiltinTests.bad_select_should_not_crash
|
||||||
|
@ -39,6 +42,7 @@ BuiltinTests.gmatch_capture_types_set_containing_lbracket
|
||||||
BuiltinTests.gmatch_definition
|
BuiltinTests.gmatch_definition
|
||||||
BuiltinTests.ipairs_iterator_should_infer_types_and_type_check
|
BuiltinTests.ipairs_iterator_should_infer_types_and_type_check
|
||||||
BuiltinTests.next_iterator_should_infer_types_and_type_check
|
BuiltinTests.next_iterator_should_infer_types_and_type_check
|
||||||
|
BuiltinTests.os_time_takes_optional_date_table
|
||||||
BuiltinTests.pairs_iterator_should_infer_types_and_type_check
|
BuiltinTests.pairs_iterator_should_infer_types_and_type_check
|
||||||
BuiltinTests.see_thru_select
|
BuiltinTests.see_thru_select
|
||||||
BuiltinTests.see_thru_select_count
|
BuiltinTests.see_thru_select_count
|
||||||
|
@ -47,6 +51,7 @@ BuiltinTests.select_way_out_of_range
|
||||||
BuiltinTests.select_with_decimal_argument_is_rounded_down
|
BuiltinTests.select_with_decimal_argument_is_rounded_down
|
||||||
BuiltinTests.set_metatable_needs_arguments
|
BuiltinTests.set_metatable_needs_arguments
|
||||||
BuiltinTests.setmetatable_should_not_mutate_persisted_types
|
BuiltinTests.setmetatable_should_not_mutate_persisted_types
|
||||||
|
BuiltinTests.sort
|
||||||
BuiltinTests.sort_with_bad_predicate
|
BuiltinTests.sort_with_bad_predicate
|
||||||
BuiltinTests.sort_with_predicate
|
BuiltinTests.sort_with_predicate
|
||||||
BuiltinTests.string_format_arg_count_mismatch
|
BuiltinTests.string_format_arg_count_mismatch
|
||||||
|
@ -54,6 +59,7 @@ BuiltinTests.string_format_as_method
|
||||||
BuiltinTests.string_format_correctly_ordered_types
|
BuiltinTests.string_format_correctly_ordered_types
|
||||||
BuiltinTests.string_format_report_all_type_errors_at_correct_positions
|
BuiltinTests.string_format_report_all_type_errors_at_correct_positions
|
||||||
BuiltinTests.string_format_use_correct_argument2
|
BuiltinTests.string_format_use_correct_argument2
|
||||||
|
BuiltinTests.table_concat_returns_string
|
||||||
BuiltinTests.table_dot_remove_optionally_returns_generic
|
BuiltinTests.table_dot_remove_optionally_returns_generic
|
||||||
BuiltinTests.table_freeze_is_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_2_args_overload
|
||||||
|
@ -90,92 +96,181 @@ ControlFlowAnalysis.type_alias_does_not_leak_out_continuing
|
||||||
DefinitionTests.class_definition_indexer
|
DefinitionTests.class_definition_indexer
|
||||||
DefinitionTests.class_definition_overload_metamethods
|
DefinitionTests.class_definition_overload_metamethods
|
||||||
DefinitionTests.class_definition_string_props
|
DefinitionTests.class_definition_string_props
|
||||||
|
DefinitionTests.declaring_generic_functions
|
||||||
DefinitionTests.definition_file_classes
|
DefinitionTests.definition_file_classes
|
||||||
Differ.equal_generictp_cyclic
|
Differ.equal_generictp_cyclic
|
||||||
|
Differ.equal_table_A_B_C
|
||||||
Differ.equal_table_cyclic_diamonds_unraveled
|
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_cyclic_tables_are_not_different
|
||||||
Differ.equal_table_two_shifted_circles_are_not_different
|
Differ.equal_table_two_shifted_circles_are_not_different
|
||||||
Differ.generictp_normal
|
Differ.generictp_normal
|
||||||
Differ.generictp_normal_2
|
Differ.generictp_normal_2
|
||||||
Differ.left_cyclic_table_right_table_missing_property
|
Differ.left_cyclic_table_right_table_missing_property
|
||||||
Differ.left_cyclic_table_right_table_property_wrong
|
Differ.left_cyclic_table_right_table_property_wrong
|
||||||
|
Differ.negation
|
||||||
|
Differ.right_cyclic_table_left_table_missing_property
|
||||||
Differ.right_cyclic_table_left_table_property_wrong
|
Differ.right_cyclic_table_left_table_property_wrong
|
||||||
Differ.table_left_circle_right_measuring_tape
|
Differ.table_left_circle_right_measuring_tape
|
||||||
|
FrontendTest.accumulate_cached_errors_in_consistent_order
|
||||||
|
FrontendTest.it_should_be_safe_to_stringify_errors_when_full_type_graph_is_discarded
|
||||||
|
GenericsTests.apply_type_function_nested_generics1
|
||||||
|
GenericsTests.apply_type_function_nested_generics3
|
||||||
GenericsTests.better_mismatch_error_messages
|
GenericsTests.better_mismatch_error_messages
|
||||||
|
GenericsTests.bound_tables_do_not_clone_original_fields
|
||||||
|
GenericsTests.check_generic_function
|
||||||
|
GenericsTests.check_generic_local_function
|
||||||
GenericsTests.check_generic_typepack_function
|
GenericsTests.check_generic_typepack_function
|
||||||
GenericsTests.check_mutual_generic_functions
|
GenericsTests.check_mutual_generic_functions
|
||||||
|
GenericsTests.check_nested_generic_function
|
||||||
|
GenericsTests.check_recursive_generic_function
|
||||||
GenericsTests.correctly_instantiate_polymorphic_member_functions
|
GenericsTests.correctly_instantiate_polymorphic_member_functions
|
||||||
|
GenericsTests.do_not_always_instantiate_generic_intersection_types
|
||||||
GenericsTests.do_not_infer_generic_functions
|
GenericsTests.do_not_infer_generic_functions
|
||||||
GenericsTests.dont_substitute_bound_types
|
GenericsTests.dont_substitute_bound_types
|
||||||
|
GenericsTests.error_detailed_function_mismatch_generic_pack
|
||||||
|
GenericsTests.error_detailed_function_mismatch_generic_types
|
||||||
|
GenericsTests.factories_of_generics
|
||||||
|
GenericsTests.function_arguments_can_be_polytypes
|
||||||
GenericsTests.generic_argument_count_too_few
|
GenericsTests.generic_argument_count_too_few
|
||||||
GenericsTests.generic_argument_count_too_many
|
GenericsTests.generic_argument_count_too_many
|
||||||
|
GenericsTests.generic_factories
|
||||||
GenericsTests.generic_functions_dont_cache_type_parameters
|
GenericsTests.generic_functions_dont_cache_type_parameters
|
||||||
|
GenericsTests.generic_functions_in_types
|
||||||
GenericsTests.generic_functions_should_be_memory_safe
|
GenericsTests.generic_functions_should_be_memory_safe
|
||||||
GenericsTests.generic_type_pack_parentheses
|
GenericsTests.generic_type_pack_parentheses
|
||||||
|
GenericsTests.generic_type_pack_unification1
|
||||||
|
GenericsTests.generic_type_pack_unification2
|
||||||
|
GenericsTests.generic_type_pack_unification3
|
||||||
GenericsTests.higher_rank_polymorphism_should_not_accept_instantiated_arguments
|
GenericsTests.higher_rank_polymorphism_should_not_accept_instantiated_arguments
|
||||||
GenericsTests.hof_subtype_instantiation_regression
|
GenericsTests.hof_subtype_instantiation_regression
|
||||||
|
GenericsTests.infer_generic_function
|
||||||
GenericsTests.infer_generic_function_function_argument
|
GenericsTests.infer_generic_function_function_argument
|
||||||
GenericsTests.infer_generic_function_function_argument_2
|
GenericsTests.infer_generic_function_function_argument_2
|
||||||
GenericsTests.infer_generic_function_function_argument_3
|
GenericsTests.infer_generic_function_function_argument_3
|
||||||
GenericsTests.infer_generic_function_function_argument_overloaded
|
GenericsTests.infer_generic_function_function_argument_overloaded
|
||||||
GenericsTests.infer_generic_lib_function_function_argument
|
GenericsTests.infer_generic_lib_function_function_argument
|
||||||
|
GenericsTests.infer_generic_local_function
|
||||||
GenericsTests.infer_generic_property
|
GenericsTests.infer_generic_property
|
||||||
|
GenericsTests.infer_nested_generic_function
|
||||||
|
GenericsTests.inferred_local_vars_can_be_polytypes
|
||||||
GenericsTests.instantiated_function_argument_names
|
GenericsTests.instantiated_function_argument_names
|
||||||
GenericsTests.mutable_state_polymorphism
|
GenericsTests.instantiation_sharing_types
|
||||||
|
GenericsTests.local_vars_can_be_polytypes
|
||||||
GenericsTests.no_stack_overflow_from_quantifying
|
GenericsTests.no_stack_overflow_from_quantifying
|
||||||
|
GenericsTests.properties_can_be_instantiated_polytypes
|
||||||
GenericsTests.properties_can_be_polytypes
|
GenericsTests.properties_can_be_polytypes
|
||||||
GenericsTests.quantify_functions_even_if_they_have_an_explicit_generic
|
GenericsTests.quantify_functions_even_if_they_have_an_explicit_generic
|
||||||
|
GenericsTests.rank_N_types_via_typeof
|
||||||
GenericsTests.self_recursive_instantiated_param
|
GenericsTests.self_recursive_instantiated_param
|
||||||
|
GenericsTests.type_parameters_can_be_polytypes
|
||||||
|
GenericsTests.typefuns_sharing_types
|
||||||
|
IntersectionTypes.error_detailed_intersection_all
|
||||||
|
IntersectionTypes.error_detailed_intersection_part
|
||||||
|
IntersectionTypes.intersect_bool_and_false
|
||||||
|
IntersectionTypes.intersect_false_and_bool_and_false
|
||||||
|
IntersectionTypes.intersect_metatables
|
||||||
|
IntersectionTypes.intersect_saturate_overloaded_functions
|
||||||
|
IntersectionTypes.intersection_of_tables
|
||||||
|
IntersectionTypes.intersection_of_tables_with_never_properties
|
||||||
IntersectionTypes.intersection_of_tables_with_top_properties
|
IntersectionTypes.intersection_of_tables_with_top_properties
|
||||||
IntersectionTypes.less_greedy_unification_with_intersection_types
|
IntersectionTypes.less_greedy_unification_with_intersection_types
|
||||||
|
IntersectionTypes.overloaded_functions_mentioning_generic
|
||||||
|
IntersectionTypes.overloaded_functions_mentioning_generic_packs
|
||||||
|
IntersectionTypes.overloaded_functions_mentioning_generics
|
||||||
|
IntersectionTypes.overloaded_functions_returning_intersections
|
||||||
|
IntersectionTypes.overloadeded_functions_with_never_arguments
|
||||||
|
IntersectionTypes.overloadeded_functions_with_never_result
|
||||||
|
IntersectionTypes.overloadeded_functions_with_overlapping_results_and_variadics
|
||||||
|
IntersectionTypes.overloadeded_functions_with_unknown_arguments
|
||||||
|
IntersectionTypes.overloadeded_functions_with_unknown_result
|
||||||
|
IntersectionTypes.overloadeded_functions_with_weird_typepacks_1
|
||||||
|
IntersectionTypes.overloadeded_functions_with_weird_typepacks_2
|
||||||
|
IntersectionTypes.overloadeded_functions_with_weird_typepacks_3
|
||||||
|
IntersectionTypes.overloadeded_functions_with_weird_typepacks_4
|
||||||
IntersectionTypes.select_correct_union_fn
|
IntersectionTypes.select_correct_union_fn
|
||||||
IntersectionTypes.should_still_pick_an_overload_whose_arguments_are_unions
|
IntersectionTypes.should_still_pick_an_overload_whose_arguments_are_unions
|
||||||
IntersectionTypes.table_intersection_write_sealed_indirect
|
IntersectionTypes.table_intersection_write_sealed_indirect
|
||||||
IntersectionTypes.table_write_sealed_indirect
|
IntersectionTypes.table_write_sealed_indirect
|
||||||
|
IntersectionTypes.union_saturate_overloaded_functions
|
||||||
|
Negations.cofinite_strings_can_be_compared_for_equality
|
||||||
Normalize.higher_order_function_with_annotation
|
Normalize.higher_order_function_with_annotation
|
||||||
Normalize.negations_of_tables
|
Normalize.negations_of_tables
|
||||||
Normalize.specific_functions_cannot_be_negated
|
Normalize.specific_functions_cannot_be_negated
|
||||||
ProvisionalTests.assign_table_with_refined_property_with_a_similar_type_is_illegal
|
ProvisionalTests.assign_table_with_refined_property_with_a_similar_type_is_illegal
|
||||||
ProvisionalTests.bail_early_if_unification_is_too_complicated
|
|
||||||
ProvisionalTests.choose_the_right_overload_for_pcall
|
ProvisionalTests.choose_the_right_overload_for_pcall
|
||||||
ProvisionalTests.do_not_ice_when_trying_to_pick_first_of_generic_type_pack
|
ProvisionalTests.do_not_ice_when_trying_to_pick_first_of_generic_type_pack
|
||||||
ProvisionalTests.error_on_eq_metamethod_returning_a_type_other_than_boolean
|
ProvisionalTests.error_on_eq_metamethod_returning_a_type_other_than_boolean
|
||||||
|
ProvisionalTests.expected_type_should_be_a_helpful_deduction_guide_for_function_calls
|
||||||
|
ProvisionalTests.floating_generics_should_not_be_allowed
|
||||||
ProvisionalTests.free_is_not_bound_to_any
|
ProvisionalTests.free_is_not_bound_to_any
|
||||||
ProvisionalTests.free_options_can_be_unified_together
|
ProvisionalTests.free_options_can_be_unified_together
|
||||||
ProvisionalTests.free_options_cannot_be_unified_together
|
ProvisionalTests.free_options_cannot_be_unified_together
|
||||||
ProvisionalTests.function_returns_many_things_but_first_of_it_is_forgotten
|
ProvisionalTests.function_returns_many_things_but_first_of_it_is_forgotten
|
||||||
ProvisionalTests.generic_type_leak_to_module_interface
|
ProvisionalTests.generic_type_leak_to_module_interface
|
||||||
ProvisionalTests.greedy_inference_with_shared_self_triggers_function_with_no_returns
|
ProvisionalTests.greedy_inference_with_shared_self_triggers_function_with_no_returns
|
||||||
|
ProvisionalTests.it_should_be_agnostic_of_actual_size
|
||||||
|
ProvisionalTests.luau-polyfill.Array.filter
|
||||||
|
ProvisionalTests.luau-polyfill.Map.entries
|
||||||
|
ProvisionalTests.optional_class_instances_are_invariant
|
||||||
ProvisionalTests.pcall_returns_at_least_two_value_but_function_returns_nothing
|
ProvisionalTests.pcall_returns_at_least_two_value_but_function_returns_nothing
|
||||||
ProvisionalTests.setmetatable_constrains_free_type_into_free_table
|
ProvisionalTests.setmetatable_constrains_free_type_into_free_table
|
||||||
ProvisionalTests.specialization_binds_with_prototypes_too_early
|
ProvisionalTests.specialization_binds_with_prototypes_too_early
|
||||||
ProvisionalTests.table_insert_with_a_singleton_argument
|
ProvisionalTests.table_insert_with_a_singleton_argument
|
||||||
ProvisionalTests.table_unification_infinite_recursion
|
ProvisionalTests.table_unification_infinite_recursion
|
||||||
ProvisionalTests.typeguard_inference_incomplete
|
ProvisionalTests.typeguard_inference_incomplete
|
||||||
|
ProvisionalTests.while_body_are_also_refined
|
||||||
ProvisionalTests.xpcall_returns_what_f_returns
|
ProvisionalTests.xpcall_returns_what_f_returns
|
||||||
RefinementTest.call_an_incompatible_function_after_using_typeguard
|
RefinementTest.apply_refinements_on_astexprindexexpr_whose_subscript_expr_is_constant_string
|
||||||
|
RefinementTest.correctly_lookup_property_whose_base_was_previously_refined
|
||||||
RefinementTest.dataflow_analysis_can_tell_refinements_when_its_appropriate_to_refine_into_nil_or_never
|
RefinementTest.dataflow_analysis_can_tell_refinements_when_its_appropriate_to_refine_into_nil_or_never
|
||||||
RefinementTest.discriminate_from_truthiness_of_x
|
RefinementTest.discriminate_from_truthiness_of_x
|
||||||
RefinementTest.fail_to_refine_a_property_of_subscript_expression
|
RefinementTest.fail_to_refine_a_property_of_subscript_expression
|
||||||
RefinementTest.falsiness_of_TruthyPredicate_narrows_into_nil
|
RefinementTest.falsiness_of_TruthyPredicate_narrows_into_nil
|
||||||
|
RefinementTest.impossible_type_narrow_is_not_an_error
|
||||||
|
RefinementTest.index_on_a_refined_property
|
||||||
RefinementTest.isa_type_refinement_must_be_known_ahead_of_time
|
RefinementTest.isa_type_refinement_must_be_known_ahead_of_time
|
||||||
|
RefinementTest.luau_polyfill_isindexkey_refine_conjunction
|
||||||
|
RefinementTest.luau_polyfill_isindexkey_refine_conjunction_variant
|
||||||
RefinementTest.narrow_property_of_a_bounded_variable
|
RefinementTest.narrow_property_of_a_bounded_variable
|
||||||
RefinementTest.nonoptional_type_can_narrow_to_nil_if_sense_is_true
|
RefinementTest.nonoptional_type_can_narrow_to_nil_if_sense_is_true
|
||||||
RefinementTest.not_t_or_some_prop_of_t
|
RefinementTest.not_t_or_some_prop_of_t
|
||||||
RefinementTest.refine_a_property_of_some_global
|
RefinementTest.refine_a_property_of_some_global
|
||||||
|
RefinementTest.refine_unknown_to_table_then_clone_it
|
||||||
|
RefinementTest.string_not_equal_to_string_or_nil
|
||||||
RefinementTest.truthy_constraint_on_properties
|
RefinementTest.truthy_constraint_on_properties
|
||||||
|
RefinementTest.type_annotations_arent_relevant_when_doing_dataflow_analysis
|
||||||
RefinementTest.type_narrow_to_vector
|
RefinementTest.type_narrow_to_vector
|
||||||
RefinementTest.typeguard_cast_free_table_to_vector
|
RefinementTest.typeguard_cast_free_table_to_vector
|
||||||
RefinementTest.typeguard_in_assert_position
|
RefinementTest.typeguard_in_assert_position
|
||||||
RefinementTest.x_as_any_if_x_is_instance_elseif_x_is_table
|
RefinementTest.x_as_any_if_x_is_instance_elseif_x_is_table
|
||||||
|
TableTests.a_free_shape_can_turn_into_a_scalar_directly
|
||||||
TableTests.a_free_shape_can_turn_into_a_scalar_if_it_is_compatible
|
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.a_free_shape_cannot_turn_into_a_scalar_if_it_is_not_compatible
|
||||||
|
TableTests.accidentally_checked_prop_in_opposite_branch
|
||||||
|
TableTests.array_factory_function
|
||||||
TableTests.call_method
|
TableTests.call_method
|
||||||
TableTests.cannot_change_type_of_unsealed_table_prop
|
TableTests.cannot_change_type_of_unsealed_table_prop
|
||||||
|
TableTests.casting_tables_with_props_into_table_with_indexer2
|
||||||
TableTests.casting_tables_with_props_into_table_with_indexer3
|
TableTests.casting_tables_with_props_into_table_with_indexer3
|
||||||
TableTests.casting_tables_with_props_into_table_with_indexer4
|
TableTests.casting_tables_with_props_into_table_with_indexer4
|
||||||
TableTests.casting_unsealed_tables_with_props_into_table_with_indexer
|
TableTests.casting_unsealed_tables_with_props_into_table_with_indexer
|
||||||
|
TableTests.certain_properties_of_table_literal_arguments_can_be_covariant
|
||||||
TableTests.checked_prop_too_early
|
TableTests.checked_prop_too_early
|
||||||
TableTests.cli_84607_missing_prop_in_array_or_dict
|
TableTests.cli_84607_missing_prop_in_array_or_dict
|
||||||
|
TableTests.common_table_element_general
|
||||||
|
TableTests.common_table_element_inner_index
|
||||||
|
TableTests.common_table_element_inner_prop
|
||||||
|
TableTests.common_table_element_list
|
||||||
|
TableTests.common_table_element_union_assignment
|
||||||
|
TableTests.common_table_element_union_in_call
|
||||||
|
TableTests.common_table_element_union_in_call_tail
|
||||||
|
TableTests.common_table_element_union_in_prop
|
||||||
|
TableTests.confusing_indexing
|
||||||
TableTests.cyclic_shifted_tables
|
TableTests.cyclic_shifted_tables
|
||||||
TableTests.disallow_indexing_into_an_unsealed_table_with_no_indexer_in_strict_mode
|
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_crash_when_setmetatable_does_not_produce_a_metatabletypevar
|
||||||
|
@ -183,29 +278,54 @@ TableTests.dont_extend_unsealed_tables_in_rvalue_position
|
||||||
TableTests.dont_hang_when_trying_to_look_up_in_cyclic_metatable_index
|
TableTests.dont_hang_when_trying_to_look_up_in_cyclic_metatable_index
|
||||||
TableTests.dont_leak_free_table_props
|
TableTests.dont_leak_free_table_props
|
||||||
TableTests.dont_quantify_table_that_belongs_to_outer_scope
|
TableTests.dont_quantify_table_that_belongs_to_outer_scope
|
||||||
|
TableTests.dont_seal_an_unsealed_table_by_passing_it_to_a_function_that_takes_a_sealed_table
|
||||||
TableTests.dont_suggest_exact_match_keys
|
TableTests.dont_suggest_exact_match_keys
|
||||||
|
TableTests.error_detailed_indexer_key
|
||||||
|
TableTests.error_detailed_indexer_value
|
||||||
TableTests.error_detailed_metatable_prop
|
TableTests.error_detailed_metatable_prop
|
||||||
|
TableTests.error_detailed_prop
|
||||||
|
TableTests.error_detailed_prop_nested
|
||||||
|
TableTests.expected_indexer_from_table_union
|
||||||
|
TableTests.expected_indexer_value_type_extra
|
||||||
|
TableTests.expected_indexer_value_type_extra_2
|
||||||
TableTests.explicitly_typed_table
|
TableTests.explicitly_typed_table
|
||||||
|
TableTests.explicitly_typed_table_error
|
||||||
TableTests.explicitly_typed_table_with_indexer
|
TableTests.explicitly_typed_table_with_indexer
|
||||||
|
TableTests.extend_unsealed_table_with_metatable
|
||||||
TableTests.generalize_table_argument
|
TableTests.generalize_table_argument
|
||||||
TableTests.generic_table_instantiation_potential_regression
|
TableTests.generic_table_instantiation_potential_regression
|
||||||
TableTests.give_up_after_one_metatable_index_look_up
|
TableTests.give_up_after_one_metatable_index_look_up
|
||||||
|
TableTests.indexer_mismatch
|
||||||
|
TableTests.indexer_on_sealed_table_must_unify_with_free_table
|
||||||
TableTests.indexers_get_quantified_too
|
TableTests.indexers_get_quantified_too
|
||||||
TableTests.inequality_operators_imply_exactly_matching_types
|
TableTests.inequality_operators_imply_exactly_matching_types
|
||||||
TableTests.infer_array_2
|
TableTests.infer_array_2
|
||||||
|
TableTests.infer_indexer_for_left_unsealed_table_from_right_hand_table_with_indexer
|
||||||
|
TableTests.infer_indexer_from_its_function_return_type
|
||||||
|
TableTests.infer_indexer_from_value_property_in_literal
|
||||||
TableTests.inferred_return_type_of_free_table
|
TableTests.inferred_return_type_of_free_table
|
||||||
TableTests.instantiate_table_cloning_3
|
TableTests.instantiate_table_cloning_3
|
||||||
|
TableTests.instantiate_tables_at_scope_level
|
||||||
|
TableTests.invariant_table_properties_means_instantiating_tables_in_assignment_is_unsound
|
||||||
|
TableTests.invariant_table_properties_means_instantiating_tables_in_call_is_unsound
|
||||||
TableTests.leaking_bad_metatable_errors
|
TableTests.leaking_bad_metatable_errors
|
||||||
TableTests.less_exponential_blowup_please
|
TableTests.less_exponential_blowup_please
|
||||||
|
TableTests.metatable_mismatch_should_fail
|
||||||
TableTests.missing_metatable_for_sealed_tables_do_not_get_inferred
|
TableTests.missing_metatable_for_sealed_tables_do_not_get_inferred
|
||||||
TableTests.mixed_tables_with_implicit_numbered_keys
|
TableTests.mixed_tables_with_implicit_numbered_keys
|
||||||
TableTests.ok_to_provide_a_subtype_during_construction
|
TableTests.ok_to_provide_a_subtype_during_construction
|
||||||
|
TableTests.ok_to_set_nil_even_on_non_lvalue_base_expr
|
||||||
TableTests.okay_to_add_property_to_unsealed_tables_by_assignment
|
TableTests.okay_to_add_property_to_unsealed_tables_by_assignment
|
||||||
TableTests.okay_to_add_property_to_unsealed_tables_by_function_call
|
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_indexer_works
|
||||||
TableTests.oop_polymorphic
|
TableTests.oop_polymorphic
|
||||||
|
TableTests.open_table_unification_2
|
||||||
TableTests.pass_a_union_of_tables_to_a_function_that_requires_a_table
|
TableTests.pass_a_union_of_tables_to_a_function_that_requires_a_table
|
||||||
TableTests.pass_a_union_of_tables_to_a_function_that_requires_a_table_2
|
TableTests.pass_a_union_of_tables_to_a_function_that_requires_a_table_2
|
||||||
|
TableTests.passing_compatible_unions_to_a_generic_table_without_crashing
|
||||||
|
TableTests.prop_access_on_key_whose_types_mismatches
|
||||||
|
TableTests.prop_access_on_unions_of_indexers_where_key_whose_types_mismatches
|
||||||
TableTests.quantify_even_that_table_was_never_exported_at_all
|
TableTests.quantify_even_that_table_was_never_exported_at_all
|
||||||
TableTests.quantify_metatables_of_metatables_of_table
|
TableTests.quantify_metatables_of_metatables_of_table
|
||||||
TableTests.reasonable_error_when_adding_a_nonexistent_property_to_an_array_like_table
|
TableTests.reasonable_error_when_adding_a_nonexistent_property_to_an_array_like_table
|
||||||
|
@ -214,18 +334,25 @@ TableTests.right_table_missing_key2
|
||||||
TableTests.scalar_is_a_subtype_of_a_compatible_polymorphic_shape_type
|
TableTests.scalar_is_a_subtype_of_a_compatible_polymorphic_shape_type
|
||||||
TableTests.scalar_is_not_a_subtype_of_a_compatible_polymorphic_shape_type
|
TableTests.scalar_is_not_a_subtype_of_a_compatible_polymorphic_shape_type
|
||||||
TableTests.sealed_table_indexers_must_unify
|
TableTests.sealed_table_indexers_must_unify
|
||||||
|
TableTests.sealed_table_value_can_infer_an_indexer
|
||||||
TableTests.shared_selfs
|
TableTests.shared_selfs
|
||||||
TableTests.shared_selfs_from_free_param
|
TableTests.shared_selfs_from_free_param
|
||||||
TableTests.shared_selfs_through_metatables
|
TableTests.shared_selfs_through_metatables
|
||||||
|
TableTests.subproperties_can_also_be_covariantly_tested
|
||||||
TableTests.table_call_metamethod_basic
|
TableTests.table_call_metamethod_basic
|
||||||
TableTests.table_call_metamethod_generic
|
TableTests.table_call_metamethod_generic
|
||||||
|
TableTests.table_function_check_use_after_free
|
||||||
|
TableTests.table_insert_should_cope_with_optional_properties_in_strict
|
||||||
TableTests.table_param_width_subtyping_1
|
TableTests.table_param_width_subtyping_1
|
||||||
TableTests.table_param_width_subtyping_2
|
TableTests.table_param_width_subtyping_2
|
||||||
|
TableTests.table_param_width_subtyping_3
|
||||||
TableTests.table_simple_call
|
TableTests.table_simple_call
|
||||||
TableTests.table_subtyping_with_extra_props_dont_report_multiple_errors
|
TableTests.table_subtyping_with_extra_props_dont_report_multiple_errors
|
||||||
TableTests.table_subtyping_with_missing_props_dont_report_multiple_errors
|
TableTests.table_subtyping_with_missing_props_dont_report_multiple_errors
|
||||||
TableTests.table_subtyping_with_missing_props_dont_report_multiple_errors2
|
TableTests.table_subtyping_with_missing_props_dont_report_multiple_errors2
|
||||||
|
TableTests.table_unification_4
|
||||||
TableTests.table_unifies_into_map
|
TableTests.table_unifies_into_map
|
||||||
|
TableTests.top_table_type
|
||||||
TableTests.type_mismatch_on_massive_table_is_cut_short
|
TableTests.type_mismatch_on_massive_table_is_cut_short
|
||||||
TableTests.unifying_tables_shouldnt_uaf1
|
TableTests.unifying_tables_shouldnt_uaf1
|
||||||
TableTests.used_colon_instead_of_dot
|
TableTests.used_colon_instead_of_dot
|
||||||
|
@ -236,31 +363,41 @@ ToString.exhaustive_toString_of_cyclic_table
|
||||||
ToString.free_types
|
ToString.free_types
|
||||||
ToString.named_metatable_toStringNamedFunction
|
ToString.named_metatable_toStringNamedFunction
|
||||||
ToString.pick_distinct_names_for_mixed_explicit_and_implicit_generics
|
ToString.pick_distinct_names_for_mixed_explicit_and_implicit_generics
|
||||||
|
ToString.tostring_error_mismatch
|
||||||
ToString.toStringDetailed2
|
ToString.toStringDetailed2
|
||||||
ToString.toStringErrorPack
|
ToString.toStringErrorPack
|
||||||
ToString.toStringNamedFunction_generic_pack
|
ToString.toStringNamedFunction_generic_pack
|
||||||
TryUnifyTests.members_of_failed_typepack_unification_are_unified_with_errorType
|
TryUnifyTests.members_of_failed_typepack_unification_are_unified_with_errorType
|
||||||
TryUnifyTests.result_of_failed_typepack_unification_is_constrained
|
TryUnifyTests.result_of_failed_typepack_unification_is_constrained
|
||||||
TryUnifyTests.typepack_unification_should_trim_free_tails
|
TryUnifyTests.typepack_unification_should_trim_free_tails
|
||||||
|
TryUnifyTests.uninhabited_table_sub_anything
|
||||||
|
TryUnifyTests.uninhabited_table_sub_never
|
||||||
TryUnifyTests.variadics_should_use_reversed_properly
|
TryUnifyTests.variadics_should_use_reversed_properly
|
||||||
|
TypeAliases.alias_expands_to_bare_reference_to_imported_type
|
||||||
TypeAliases.dont_lose_track_of_PendingExpansionTypes_after_substitution
|
TypeAliases.dont_lose_track_of_PendingExpansionTypes_after_substitution
|
||||||
|
TypeAliases.free_variables_from_typeof_in_aliases
|
||||||
TypeAliases.generic_param_remap
|
TypeAliases.generic_param_remap
|
||||||
TypeAliases.mismatched_generic_type_param
|
TypeAliases.mismatched_generic_type_param
|
||||||
|
TypeAliases.mutually_recursive_aliases
|
||||||
|
TypeAliases.mutually_recursive_generic_aliases
|
||||||
TypeAliases.mutually_recursive_types_restriction_not_ok_1
|
TypeAliases.mutually_recursive_types_restriction_not_ok_1
|
||||||
TypeAliases.mutually_recursive_types_restriction_not_ok_2
|
TypeAliases.mutually_recursive_types_restriction_not_ok_2
|
||||||
TypeAliases.mutually_recursive_types_swapsies_not_ok
|
TypeAliases.mutually_recursive_types_swapsies_not_ok
|
||||||
TypeAliases.recursive_types_restriction_not_ok
|
TypeAliases.recursive_types_restriction_not_ok
|
||||||
TypeAliases.report_shadowed_aliases
|
TypeAliases.report_shadowed_aliases
|
||||||
|
TypeAliases.stringify_optional_parameterized_alias
|
||||||
TypeAliases.type_alias_local_mutation
|
TypeAliases.type_alias_local_mutation
|
||||||
TypeAliases.type_alias_local_rename
|
TypeAliases.type_alias_local_rename
|
||||||
TypeAliases.type_alias_locations
|
TypeAliases.type_alias_locations
|
||||||
TypeAliases.type_alias_of_an_imported_recursive_generic_type
|
TypeAliases.type_alias_of_an_imported_recursive_generic_type
|
||||||
|
TypeAliases.use_table_name_and_generic_params_in_errors
|
||||||
TypeFamilyTests.add_family_at_work
|
TypeFamilyTests.add_family_at_work
|
||||||
TypeFamilyTests.family_as_fn_arg
|
TypeFamilyTests.family_as_fn_arg
|
||||||
TypeFamilyTests.family_as_fn_ret
|
TypeFamilyTests.family_as_fn_ret
|
||||||
TypeFamilyTests.function_internal_families
|
TypeFamilyTests.function_internal_families
|
||||||
TypeFamilyTests.internal_families_raise_errors
|
TypeFamilyTests.internal_families_raise_errors
|
||||||
TypeFamilyTests.table_internal_families
|
TypeFamilyTests.table_internal_families
|
||||||
|
TypeFamilyTests.type_families_inhabited_with_normalization
|
||||||
TypeFamilyTests.unsolvable_family
|
TypeFamilyTests.unsolvable_family
|
||||||
TypeInfer.bidirectional_checking_of_callback_property
|
TypeInfer.bidirectional_checking_of_callback_property
|
||||||
TypeInfer.check_expr_recursion_limit
|
TypeInfer.check_expr_recursion_limit
|
||||||
|
@ -269,13 +406,22 @@ TypeInfer.cli_39932_use_unifier_in_ensure_methods
|
||||||
TypeInfer.cli_50041_committing_txnlog_in_apollo_client_error
|
TypeInfer.cli_50041_committing_txnlog_in_apollo_client_error
|
||||||
TypeInfer.dont_report_type_errors_within_an_AstExprError
|
TypeInfer.dont_report_type_errors_within_an_AstExprError
|
||||||
TypeInfer.dont_report_type_errors_within_an_AstStatError
|
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.fuzz_free_table_type_change_during_index_check
|
||||||
TypeInfer.infer_assignment_value_types_mutable_lval
|
TypeInfer.globals_are_banned_in_strict_mode
|
||||||
|
TypeInfer.if_statement
|
||||||
|
TypeInfer.infer_locals_via_assignment_from_its_call_site
|
||||||
|
TypeInfer.infer_locals_with_nil_value
|
||||||
|
TypeInfer.infer_through_group_expr
|
||||||
|
TypeInfer.infer_type_assertion_value_type
|
||||||
|
TypeInfer.interesting_local_type_inference_case
|
||||||
TypeInfer.no_stack_overflow_from_isoptional
|
TypeInfer.no_stack_overflow_from_isoptional
|
||||||
TypeInfer.promote_tail_type_packs
|
TypeInfer.promote_tail_type_packs
|
||||||
TypeInfer.recursive_function_that_invokes_itself_with_a_refinement_of_its_parameter
|
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.recursive_function_that_invokes_itself_with_a_refinement_of_its_parameter_2
|
||||||
TypeInfer.tc_after_error_recovery_no_replacement_name_in_error
|
TypeInfer.tc_after_error_recovery_no_replacement_name_in_error
|
||||||
|
TypeInfer.tc_error
|
||||||
|
TypeInfer.tc_error_2
|
||||||
TypeInfer.tc_if_else_expressions_expected_type_3
|
TypeInfer.tc_if_else_expressions_expected_type_3
|
||||||
TypeInfer.type_infer_recursion_limit_no_ice
|
TypeInfer.type_infer_recursion_limit_no_ice
|
||||||
TypeInfer.type_infer_recursion_limit_normalizer
|
TypeInfer.type_infer_recursion_limit_normalizer
|
||||||
|
@ -288,22 +434,36 @@ TypeInferAnyError.for_in_loop_iterator_returns_any
|
||||||
TypeInferAnyError.for_in_loop_iterator_returns_any2
|
TypeInferAnyError.for_in_loop_iterator_returns_any2
|
||||||
TypeInferAnyError.intersection_of_any_can_have_props
|
TypeInferAnyError.intersection_of_any_can_have_props
|
||||||
TypeInferAnyError.replace_every_free_type_when_unifying_a_complex_function_with_any
|
TypeInferAnyError.replace_every_free_type_when_unifying_a_complex_function_with_any
|
||||||
TypeInferAnyError.union_of_types_regression_test
|
TypeInferAnyError.type_error_addition
|
||||||
TypeInferClasses.callable_classes
|
TypeInferClasses.callable_classes
|
||||||
|
TypeInferClasses.can_read_prop_of_base_class_using_string
|
||||||
|
TypeInferClasses.cannot_unify_class_instance_with_primitive
|
||||||
TypeInferClasses.class_type_mismatch_with_name_conflict
|
TypeInferClasses.class_type_mismatch_with_name_conflict
|
||||||
TypeInferClasses.detailed_class_unification_error
|
TypeInferClasses.detailed_class_unification_error
|
||||||
TypeInferClasses.index_instance_property
|
TypeInferClasses.index_instance_property
|
||||||
|
TypeInferClasses.indexable_classes
|
||||||
TypeInferClasses.table_class_unification_reports_sane_errors_for_missing_properties
|
TypeInferClasses.table_class_unification_reports_sane_errors_for_missing_properties
|
||||||
|
TypeInferClasses.table_indexers_are_invariant
|
||||||
|
TypeInferClasses.type_mismatch_invariance_required_for_error
|
||||||
|
TypeInferClasses.we_can_report_when_someone_is_trying_to_use_a_table_rather_than_a_class
|
||||||
TypeInferFunctions.apply_of_lambda_with_inferred_and_explicit_types
|
TypeInferFunctions.apply_of_lambda_with_inferred_and_explicit_types
|
||||||
TypeInferFunctions.cannot_hoist_interior_defns_into_signature
|
TypeInferFunctions.cannot_hoist_interior_defns_into_signature
|
||||||
|
TypeInferFunctions.check_function_bodies
|
||||||
|
TypeInferFunctions.concrete_functions_are_not_supertypes_of_function
|
||||||
TypeInferFunctions.dont_assert_when_the_tarjan_limit_is_exceeded_during_generalization
|
TypeInferFunctions.dont_assert_when_the_tarjan_limit_is_exceeded_during_generalization
|
||||||
TypeInferFunctions.dont_give_other_overloads_message_if_only_one_argument_matching_overload_exists
|
TypeInferFunctions.dont_give_other_overloads_message_if_only_one_argument_matching_overload_exists
|
||||||
TypeInferFunctions.dont_infer_parameter_types_for_functions_from_their_call_site
|
TypeInferFunctions.dont_infer_parameter_types_for_functions_from_their_call_site
|
||||||
|
TypeInferFunctions.error_detailed_function_mismatch_arg
|
||||||
|
TypeInferFunctions.error_detailed_function_mismatch_arg_count
|
||||||
|
TypeInferFunctions.error_detailed_function_mismatch_ret
|
||||||
|
TypeInferFunctions.error_detailed_function_mismatch_ret_count
|
||||||
|
TypeInferFunctions.error_detailed_function_mismatch_ret_mult
|
||||||
TypeInferFunctions.function_cast_error_uses_correct_language
|
TypeInferFunctions.function_cast_error_uses_correct_language
|
||||||
TypeInferFunctions.function_decl_non_self_sealed_overwrite_2
|
TypeInferFunctions.function_decl_non_self_sealed_overwrite_2
|
||||||
TypeInferFunctions.function_decl_non_self_unsealed_overwrite
|
TypeInferFunctions.function_decl_non_self_unsealed_overwrite
|
||||||
TypeInferFunctions.function_does_not_return_enough_values
|
TypeInferFunctions.function_does_not_return_enough_values
|
||||||
TypeInferFunctions.function_exprs_are_generalized_at_signature_scope_not_enclosing
|
TypeInferFunctions.function_exprs_are_generalized_at_signature_scope_not_enclosing
|
||||||
|
TypeInferFunctions.function_is_supertype_of_concrete_functions
|
||||||
TypeInferFunctions.function_statement_sealed_table_assignment_through_indexer
|
TypeInferFunctions.function_statement_sealed_table_assignment_through_indexer
|
||||||
TypeInferFunctions.generic_packs_are_not_variadic
|
TypeInferFunctions.generic_packs_are_not_variadic
|
||||||
TypeInferFunctions.higher_order_function_2
|
TypeInferFunctions.higher_order_function_2
|
||||||
|
@ -311,14 +471,22 @@ TypeInferFunctions.higher_order_function_3
|
||||||
TypeInferFunctions.higher_order_function_4
|
TypeInferFunctions.higher_order_function_4
|
||||||
TypeInferFunctions.improved_function_arg_mismatch_errors
|
TypeInferFunctions.improved_function_arg_mismatch_errors
|
||||||
TypeInferFunctions.infer_anonymous_function_arguments
|
TypeInferFunctions.infer_anonymous_function_arguments
|
||||||
|
TypeInferFunctions.infer_anonymous_function_arguments_outside_call
|
||||||
TypeInferFunctions.infer_generic_function_function_argument
|
TypeInferFunctions.infer_generic_function_function_argument
|
||||||
TypeInferFunctions.infer_generic_function_function_argument_overloaded
|
TypeInferFunctions.infer_generic_function_function_argument_overloaded
|
||||||
TypeInferFunctions.infer_generic_lib_function_function_argument
|
TypeInferFunctions.infer_generic_lib_function_function_argument
|
||||||
TypeInferFunctions.infer_return_type_from_selected_overload
|
TypeInferFunctions.infer_return_type_from_selected_overload
|
||||||
|
TypeInferFunctions.infer_return_value_type
|
||||||
TypeInferFunctions.infer_that_function_does_not_return_a_table
|
TypeInferFunctions.infer_that_function_does_not_return_a_table
|
||||||
|
TypeInferFunctions.inferred_higher_order_functions_are_quantified_at_the_right_time3
|
||||||
|
TypeInferFunctions.instantiated_type_packs_must_have_a_non_null_scope
|
||||||
TypeInferFunctions.it_is_ok_to_oversaturate_a_higher_order_function_argument
|
TypeInferFunctions.it_is_ok_to_oversaturate_a_higher_order_function_argument
|
||||||
TypeInferFunctions.luau_subtyping_is_np_hard
|
TypeInferFunctions.luau_subtyping_is_np_hard
|
||||||
TypeInferFunctions.no_lossy_function_type
|
TypeInferFunctions.no_lossy_function_type
|
||||||
|
TypeInferFunctions.num_is_solved_after_num_or_str
|
||||||
|
TypeInferFunctions.num_is_solved_before_num_or_str
|
||||||
|
TypeInferFunctions.occurs_check_failure_in_function_return_type
|
||||||
|
TypeInferFunctions.other_things_are_not_related_to_function
|
||||||
TypeInferFunctions.param_1_and_2_both_takes_the_same_generic_but_their_arguments_are_incompatible
|
TypeInferFunctions.param_1_and_2_both_takes_the_same_generic_but_their_arguments_are_incompatible
|
||||||
TypeInferFunctions.param_1_and_2_both_takes_the_same_generic_but_their_arguments_are_incompatible_2
|
TypeInferFunctions.param_1_and_2_both_takes_the_same_generic_but_their_arguments_are_incompatible_2
|
||||||
TypeInferFunctions.record_matching_overload
|
TypeInferFunctions.record_matching_overload
|
||||||
|
@ -330,7 +498,9 @@ TypeInferFunctions.too_few_arguments_variadic_generic2
|
||||||
TypeInferFunctions.too_many_arguments_error_location
|
TypeInferFunctions.too_many_arguments_error_location
|
||||||
TypeInferFunctions.too_many_return_values_in_parentheses
|
TypeInferFunctions.too_many_return_values_in_parentheses
|
||||||
TypeInferFunctions.too_many_return_values_no_function
|
TypeInferFunctions.too_many_return_values_no_function
|
||||||
|
TypeInferFunctions.vararg_function_is_quantified
|
||||||
TypeInferLoops.cli_68448_iterators_need_not_accept_nil
|
TypeInferLoops.cli_68448_iterators_need_not_accept_nil
|
||||||
|
TypeInferLoops.dcr_iteration_explore_raycast_minimization
|
||||||
TypeInferLoops.dcr_iteration_on_never_gives_never
|
TypeInferLoops.dcr_iteration_on_never_gives_never
|
||||||
TypeInferLoops.dcr_xpath_candidates
|
TypeInferLoops.dcr_xpath_candidates
|
||||||
TypeInferLoops.for_in_loop
|
TypeInferLoops.for_in_loop
|
||||||
|
@ -341,8 +511,12 @@ TypeInferLoops.for_in_loop_on_non_function
|
||||||
TypeInferLoops.for_in_loop_with_custom_iterator
|
TypeInferLoops.for_in_loop_with_custom_iterator
|
||||||
TypeInferLoops.for_in_loop_with_incompatible_args_to_iterator
|
TypeInferLoops.for_in_loop_with_incompatible_args_to_iterator
|
||||||
TypeInferLoops.for_in_loop_with_next
|
TypeInferLoops.for_in_loop_with_next
|
||||||
|
TypeInferLoops.for_in_with_generic_next
|
||||||
TypeInferLoops.for_in_with_just_one_iterator_is_ok
|
TypeInferLoops.for_in_with_just_one_iterator_is_ok
|
||||||
|
TypeInferLoops.for_loop
|
||||||
TypeInferLoops.ipairs_produces_integral_indices
|
TypeInferLoops.ipairs_produces_integral_indices
|
||||||
|
TypeInferLoops.iteration_no_table_passed
|
||||||
|
TypeInferLoops.iteration_regression_issue_69967
|
||||||
TypeInferLoops.iteration_regression_issue_69967_alt
|
TypeInferLoops.iteration_regression_issue_69967_alt
|
||||||
TypeInferLoops.loop_iter_basic
|
TypeInferLoops.loop_iter_basic
|
||||||
TypeInferLoops.loop_iter_metamethod_nil
|
TypeInferLoops.loop_iter_metamethod_nil
|
||||||
|
@ -351,11 +525,14 @@ TypeInferLoops.loop_iter_metamethod_ok_with_inference
|
||||||
TypeInferLoops.loop_iter_trailing_nil
|
TypeInferLoops.loop_iter_trailing_nil
|
||||||
TypeInferLoops.loop_typecheck_crash_on_empty_optional
|
TypeInferLoops.loop_typecheck_crash_on_empty_optional
|
||||||
TypeInferLoops.properly_infer_iteratee_is_a_free_table
|
TypeInferLoops.properly_infer_iteratee_is_a_free_table
|
||||||
|
TypeInferLoops.repeat_loop
|
||||||
TypeInferLoops.unreachable_code_after_infinite_loop
|
TypeInferLoops.unreachable_code_after_infinite_loop
|
||||||
TypeInferLoops.varlist_declared_by_for_in_loop_should_be_free
|
TypeInferLoops.varlist_declared_by_for_in_loop_should_be_free
|
||||||
|
TypeInferLoops.while_loop
|
||||||
TypeInferModules.bound_free_table_export_is_ok
|
TypeInferModules.bound_free_table_export_is_ok
|
||||||
TypeInferModules.do_not_modify_imported_types_4
|
TypeInferModules.do_not_modify_imported_types_4
|
||||||
TypeInferModules.do_not_modify_imported_types_5
|
TypeInferModules.do_not_modify_imported_types_5
|
||||||
|
TypeInferModules.general_require_call_expression
|
||||||
TypeInferModules.module_type_conflict
|
TypeInferModules.module_type_conflict
|
||||||
TypeInferModules.module_type_conflict_instantiated
|
TypeInferModules.module_type_conflict_instantiated
|
||||||
TypeInferModules.require_failed_module
|
TypeInferModules.require_failed_module
|
||||||
|
@ -371,13 +548,20 @@ TypeInferOOP.table_oop
|
||||||
TypeInferOperators.add_type_family_works
|
TypeInferOperators.add_type_family_works
|
||||||
TypeInferOperators.and_binexps_dont_unify
|
TypeInferOperators.and_binexps_dont_unify
|
||||||
TypeInferOperators.cli_38355_recursive_union
|
TypeInferOperators.cli_38355_recursive_union
|
||||||
|
TypeInferOperators.compound_assign_mismatch_metatable
|
||||||
|
TypeInferOperators.compound_assign_mismatch_op
|
||||||
|
TypeInferOperators.compound_assign_mismatch_result
|
||||||
TypeInferOperators.compound_assign_result_must_be_compatible_with_var
|
TypeInferOperators.compound_assign_result_must_be_compatible_with_var
|
||||||
TypeInferOperators.concat_op_on_free_lhs_and_string_rhs
|
TypeInferOperators.concat_op_on_free_lhs_and_string_rhs
|
||||||
TypeInferOperators.concat_op_on_string_lhs_and_free_rhs
|
TypeInferOperators.concat_op_on_string_lhs_and_free_rhs
|
||||||
TypeInferOperators.disallow_string_and_types_without_metatables_from_arithmetic_binary_ops
|
TypeInferOperators.disallow_string_and_types_without_metatables_from_arithmetic_binary_ops
|
||||||
|
TypeInferOperators.luau-polyfill.Array.startswith
|
||||||
TypeInferOperators.luau_polyfill_is_array
|
TypeInferOperators.luau_polyfill_is_array
|
||||||
|
TypeInferOperators.normalize_strings_comparison
|
||||||
TypeInferOperators.operator_eq_completely_incompatible
|
TypeInferOperators.operator_eq_completely_incompatible
|
||||||
|
TypeInferOperators.operator_eq_verifies_types_do_intersect
|
||||||
TypeInferOperators.reducing_and
|
TypeInferOperators.reducing_and
|
||||||
|
TypeInferOperators.refine_and_or
|
||||||
TypeInferOperators.strict_binary_op_where_lhs_unknown
|
TypeInferOperators.strict_binary_op_where_lhs_unknown
|
||||||
TypeInferOperators.typecheck_overloaded_multiply_that_is_an_intersection
|
TypeInferOperators.typecheck_overloaded_multiply_that_is_an_intersection
|
||||||
TypeInferOperators.typecheck_overloaded_multiply_that_is_an_intersection_on_rhs
|
TypeInferOperators.typecheck_overloaded_multiply_that_is_an_intersection_on_rhs
|
||||||
|
@ -388,22 +572,51 @@ TypeInferOperators.unrelated_classes_cannot_be_compared
|
||||||
TypeInferOperators.unrelated_primitives_cannot_be_compared
|
TypeInferOperators.unrelated_primitives_cannot_be_compared
|
||||||
TypeInferPrimitives.CheckMethodsOfNumber
|
TypeInferPrimitives.CheckMethodsOfNumber
|
||||||
TypeInferPrimitives.string_index
|
TypeInferPrimitives.string_index
|
||||||
|
TypeInferUnknownNever.array_like_table_of_never_is_inhabitable
|
||||||
|
TypeInferUnknownNever.index_on_union_of_tables_for_properties_that_is_never
|
||||||
|
TypeInferUnknownNever.index_on_union_of_tables_for_properties_that_is_sorta_never
|
||||||
TypeInferUnknownNever.length_of_never
|
TypeInferUnknownNever.length_of_never
|
||||||
TypeInferUnknownNever.math_operators_and_never
|
TypeInferUnknownNever.math_operators_and_never
|
||||||
TypePackTests.fuzz_typepack_iter_follow_2
|
TypePackTests.fuzz_typepack_iter_follow_2
|
||||||
TypePackTests.pack_tail_unification_check
|
TypePackTests.pack_tail_unification_check
|
||||||
|
TypePackTests.parenthesized_varargs_returns_any
|
||||||
TypePackTests.type_alias_backwards_compatible
|
TypePackTests.type_alias_backwards_compatible
|
||||||
TypePackTests.type_alias_default_type_errors
|
TypePackTests.type_alias_default_type_errors
|
||||||
TypePackTests.type_packs_with_tails_in_vararg_adjustment
|
TypePackTests.type_packs_with_tails_in_vararg_adjustment
|
||||||
|
TypePackTests.unify_variadic_tails_in_arguments
|
||||||
|
TypePackTests.unify_variadic_tails_in_arguments_free
|
||||||
|
TypePackTests.variadic_argument_tail
|
||||||
|
TypeSingletons.enums_using_singletons_mismatch
|
||||||
|
TypeSingletons.error_detailed_tagged_union_mismatch_bool
|
||||||
|
TypeSingletons.error_detailed_tagged_union_mismatch_string
|
||||||
TypeSingletons.function_args_infer_singletons
|
TypeSingletons.function_args_infer_singletons
|
||||||
TypeSingletons.function_call_with_singletons
|
TypeSingletons.function_call_with_singletons
|
||||||
TypeSingletons.function_call_with_singletons_mismatch
|
TypeSingletons.function_call_with_singletons_mismatch
|
||||||
TypeSingletons.overloaded_function_call_with_singletons
|
TypeSingletons.overloaded_function_call_with_singletons
|
||||||
TypeSingletons.return_type_of_f_is_not_widened
|
TypeSingletons.return_type_of_f_is_not_widened
|
||||||
|
TypeSingletons.table_properties_singleton_strings
|
||||||
TypeSingletons.table_properties_type_error_escapes
|
TypeSingletons.table_properties_type_error_escapes
|
||||||
TypeSingletons.widen_the_supertype_if_it_is_free_and_subtype_has_singleton
|
TypeSingletons.widen_the_supertype_if_it_is_free_and_subtype_has_singleton
|
||||||
TypeSingletons.widening_happens_almost_everywhere
|
TypeSingletons.widening_happens_almost_everywhere
|
||||||
|
UnionTypes.disallow_less_specific_assign
|
||||||
|
UnionTypes.disallow_less_specific_assign2
|
||||||
|
UnionTypes.error_detailed_optional
|
||||||
|
UnionTypes.error_detailed_union_all
|
||||||
|
UnionTypes.error_detailed_union_part
|
||||||
|
UnionTypes.generic_function_with_optional_arg
|
||||||
UnionTypes.index_on_a_union_type_with_missing_property
|
UnionTypes.index_on_a_union_type_with_missing_property
|
||||||
UnionTypes.less_greedy_unification_with_union_types
|
UnionTypes.less_greedy_unification_with_union_types
|
||||||
|
UnionTypes.optional_arguments_table2
|
||||||
|
UnionTypes.optional_index_error
|
||||||
|
UnionTypes.optional_length_error
|
||||||
UnionTypes.table_union_write_indirect
|
UnionTypes.table_union_write_indirect
|
||||||
UnionTypes.unify_unsealed_table_union_check
|
UnionTypes.unify_sealed_table_union_check
|
||||||
|
UnionTypes.union_of_functions
|
||||||
|
UnionTypes.union_of_functions_mentioning_generic_typepacks
|
||||||
|
UnionTypes.union_of_functions_mentioning_generics
|
||||||
|
UnionTypes.union_of_functions_with_mismatching_arg_arities
|
||||||
|
UnionTypes.union_of_functions_with_mismatching_arg_variadics
|
||||||
|
UnionTypes.union_of_functions_with_mismatching_result_arities
|
||||||
|
UnionTypes.union_of_functions_with_mismatching_result_variadics
|
||||||
|
UnionTypes.union_of_functions_with_variadics
|
||||||
|
UnionTypes.union_true_and_false
|
||||||
|
|
Loading…
Reference in a new issue