mirror of
https://github.com/luau-lang/luau.git
synced 2024-11-15 14:25:44 +08:00
Sync to upstream/release/602 (#1089)
# What's changed? * Fixed a bug in type cloning by maintaining persistent types. * We now parse imprecise integer literals to report the imprecision as a warning to developers. * Add a compiler flag to specify the name of the statistics output file. ### New type solver * Renamed `ConstraintGraphBuilder` to `ConstraintGenerator` * LValues now take into account the type being assigned during constraint generation. * Normalization performance has been improved by 33% by replacing the an internal usage of `std::unordered_set` with `DenseHashMap`. * Normalization now has a helper to identify types that are equivalent to `unknown`, which is being used to fix some bugs in subtyping. * Uses of the old unifier in the new type solver have been eliminated. * Improved error explanations for subtyping errors in `TypeChecker2`. ### Native code generation * Expanded some of the statistics recorded during compilation to include the number of instructions and blocks. * Introduce instruction and block count limiters for controlling what bytecode is translated into native code. * Implement code generation for byteswap instruction. ### Internal Contributors Co-authored-by: Aaron Weiss <aaronweiss@roblox.com> Co-authored-by: Alexander McCord <amccord@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Aviral Goel <agoel@roblox.com> Co-authored-by: Lily Brown <lbrown@roblox.com>
This commit is contained in:
parent
1a9159daff
commit
7105c81579
@ -57,7 +57,7 @@ struct InferencePack
|
||||
}
|
||||
};
|
||||
|
||||
struct ConstraintGraphBuilder
|
||||
struct ConstraintGenerator
|
||||
{
|
||||
// A list of all the scopes in the module. This vector holds ownership of the
|
||||
// scope pointers; the scopes themselves borrow pointers to other scopes to
|
||||
@ -68,7 +68,7 @@ struct ConstraintGraphBuilder
|
||||
NotNull<BuiltinTypes> builtinTypes;
|
||||
const NotNull<TypeArena> arena;
|
||||
// The root scope of the module we're generating constraints for.
|
||||
// This is null when the CGB is initially constructed.
|
||||
// This is null when the CG is initially constructed.
|
||||
Scope* rootScope;
|
||||
|
||||
struct InferredBinding
|
||||
@ -116,13 +116,13 @@ struct ConstraintGraphBuilder
|
||||
|
||||
DcrLogger* logger;
|
||||
|
||||
ConstraintGraphBuilder(ModulePtr module, NotNull<Normalizer> normalizer, NotNull<ModuleResolver> moduleResolver,
|
||||
ConstraintGenerator(ModulePtr module, NotNull<Normalizer> normalizer, NotNull<ModuleResolver> moduleResolver,
|
||||
NotNull<BuiltinTypes> builtinTypes, NotNull<InternalErrorReporter> ice, const ScopePtr& globalScope,
|
||||
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope, DcrLogger* logger, NotNull<DataFlowGraph> dfg,
|
||||
std::vector<RequireCycle> requireCycles);
|
||||
|
||||
/**
|
||||
* The entry point to the ConstraintGraphBuilder. This will construct a set
|
||||
* The entry point to the ConstraintGenerator. This will construct a set
|
||||
* of scopes, constraints, and free types that can be solved later.
|
||||
* @param block the root block to generate constraints for.
|
||||
*/
|
||||
@ -232,12 +232,16 @@ private:
|
||||
Inference check(const ScopePtr& scope, AstExprTable* expr, std::optional<TypeId> expectedType);
|
||||
std::tuple<TypeId, TypeId, RefinementId> checkBinary(const ScopePtr& scope, AstExprBinary* binary, std::optional<TypeId> expectedType);
|
||||
|
||||
std::optional<TypeId> checkLValue(const ScopePtr& scope, AstExpr* expr);
|
||||
std::optional<TypeId> checkLValue(const ScopePtr& scope, AstExprLocal* local);
|
||||
std::optional<TypeId> checkLValue(const ScopePtr& scope, AstExprGlobal* global);
|
||||
std::optional<TypeId> checkLValue(const ScopePtr& scope, AstExprIndexName* indexName);
|
||||
std::optional<TypeId> checkLValue(const ScopePtr& scope, AstExprIndexExpr* indexExpr);
|
||||
TypeId updateProperty(const ScopePtr& scope, AstExpr* expr);
|
||||
/**
|
||||
* Generate constraints to assign assignedTy to the expression expr
|
||||
* @returns the type of the expression. This may or may not be assignedTy itself.
|
||||
*/
|
||||
std::optional<TypeId> checkLValue(const ScopePtr& scope, AstExpr* expr, TypeId assignedTy);
|
||||
std::optional<TypeId> checkLValue(const ScopePtr& scope, AstExprLocal* local, TypeId assignedTy);
|
||||
std::optional<TypeId> checkLValue(const ScopePtr& scope, AstExprGlobal* global, TypeId assignedTy);
|
||||
std::optional<TypeId> checkLValue(const ScopePtr& scope, AstExprIndexName* indexName, TypeId assignedTy);
|
||||
std::optional<TypeId> checkLValue(const ScopePtr& scope, AstExprIndexExpr* indexExpr, TypeId assignedTy);
|
||||
TypeId updateProperty(const ScopePtr& scope, AstExpr* expr, TypeId assignedTy);
|
||||
|
||||
void updateLValueType(AstExpr* lvalue, TypeId ty);
|
||||
|
||||
@ -324,7 +328,7 @@ private:
|
||||
|
||||
/** Scan the program for global definitions.
|
||||
*
|
||||
* ConstraintGraphBuilder needs to differentiate between globals and accesses to undefined symbols. Doing this "for
|
||||
* ConstraintGenerator needs to differentiate between globals and accesses to undefined symbols. Doing this "for
|
||||
* real" in a general way is going to be pretty hard, so we are choosing not to tackle that yet. For now, we do an
|
||||
* initial scan of the AST and note what globals are defined.
|
||||
*/
|
@ -34,7 +34,7 @@ struct DataFlowGraph
|
||||
DataFlowGraph& operator=(DataFlowGraph&&) = default;
|
||||
|
||||
DefId getDef(const AstExpr* expr) const;
|
||||
// Look up for the rvalue breadcrumb for a compound assignment.
|
||||
// Look up for the rvalue def for a compound assignment.
|
||||
std::optional<DefId> getRValueDefForCompoundAssign(const AstExpr* expr) const;
|
||||
|
||||
DefId getDef(const AstLocal* local) const;
|
||||
@ -64,7 +64,7 @@ private:
|
||||
|
||||
// Compound assignments are in a weird situation where the local being assigned to is also being used at its
|
||||
// previous type implicitly in an rvalue position. This map provides the previous binding.
|
||||
DenseHashMap<const AstExpr*, const Def*> compoundAssignBreadcrumbs{nullptr};
|
||||
DenseHashMap<const AstExpr*, const Def*> compoundAssignDefs{nullptr};
|
||||
|
||||
DenseHashMap<const AstExpr*, const RefinementKey*> astRefinementKeys{nullptr};
|
||||
|
||||
|
@ -29,7 +29,7 @@ bool isConsistentSubtype(TypePackId subTy, TypePackId superTy, NotNull<Scope> sc
|
||||
class TypeIds
|
||||
{
|
||||
private:
|
||||
std::unordered_set<TypeId> types;
|
||||
DenseHashMap<TypeId, bool> types{nullptr};
|
||||
std::vector<TypeId> order;
|
||||
std::size_t hash = 0;
|
||||
|
||||
@ -277,6 +277,7 @@ struct NormalizedType
|
||||
NormalizedType& operator=(NormalizedType&&) = default;
|
||||
|
||||
// IsType functions
|
||||
bool isUnknown() const;
|
||||
/// Returns true if the type is exactly a number. Behaves like Type::isNumber()
|
||||
bool isExactlyNumber() const;
|
||||
|
||||
|
@ -2,8 +2,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Ast.h"
|
||||
#include "Luau/Module.h"
|
||||
#include "Luau/NotNull.h"
|
||||
|
||||
namespace Luau
|
||||
@ -13,6 +11,8 @@ struct BuiltinTypes;
|
||||
struct DcrLogger;
|
||||
struct TypeCheckLimits;
|
||||
struct UnifierSharedState;
|
||||
struct SourceModule;
|
||||
struct Module;
|
||||
|
||||
void check(NotNull<BuiltinTypes> builtinTypes, NotNull<UnifierSharedState> sharedState, NotNull<TypeCheckLimits> limits, DcrLogger* logger,
|
||||
const SourceModule& sourceModule, Module* module);
|
||||
|
@ -12,32 +12,28 @@ namespace Luau
|
||||
|
||||
const void* ptr(TypeOrPack ty);
|
||||
|
||||
template<typename T>
|
||||
const T* get(TypeOrPack ty)
|
||||
template<typename T, typename std::enable_if_t<TypeOrPack::is_part_of_v<T>, bool> = true>
|
||||
const T* get(const TypeOrPack& tyOrTp)
|
||||
{
|
||||
if constexpr (std::is_same_v<T, TypeId>)
|
||||
return ty.get_if<TypeId>();
|
||||
else if constexpr (std::is_same_v<T, TypePackId>)
|
||||
return ty.get_if<TypePackId>();
|
||||
else if constexpr (TypeVariant::is_part_of_v<T>)
|
||||
{
|
||||
if (auto innerTy = ty.get_if<TypeId>())
|
||||
return get<T>(*innerTy);
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
else if constexpr (TypePackVariant::is_part_of_v<T>)
|
||||
{
|
||||
if (auto innerTp = ty.get_if<TypePackId>())
|
||||
return get<T>(*innerTp);
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
return tyOrTp.get_if<T>();
|
||||
}
|
||||
|
||||
template<typename T, typename std::enable_if_t<TypeVariant::is_part_of_v<T>, bool> = true>
|
||||
const T* get(const TypeOrPack& tyOrTp)
|
||||
{
|
||||
if (const TypeId* ty = get<TypeId>(tyOrTp))
|
||||
return get<T>(*ty);
|
||||
else
|
||||
{
|
||||
static_assert(always_false_v<T>, "invalid T to get from TypeOrPack");
|
||||
LUAU_UNREACHABLE();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template<typename T, typename std::enable_if_t<TypePackVariant::is_part_of_v<T>, bool> = true>
|
||||
const T* get(const TypeOrPack& tyOrTp)
|
||||
{
|
||||
if (const TypePackId* tp = get<TypePackId>(tyOrTp))
|
||||
return get<T>(*tp);
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
TypeOrPack follow(TypeOrPack ty);
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include "Luau/BuiltinDefinitions.h"
|
||||
#include "Luau/Frontend.h"
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/Subtyping.h"
|
||||
#include "Luau/TypeInfer.h"
|
||||
#include "Luau/TypePack.h"
|
||||
|
||||
@ -12,6 +13,7 @@
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
LUAU_FASTFLAG(DebugLuauReadWriteProperties);
|
||||
LUAU_FASTFLAG(LuauClipExtraHasEndProps);
|
||||
LUAU_FASTFLAGVARIABLE(LuauAutocompleteDoEnd, false);
|
||||
@ -143,13 +145,24 @@ static bool checkTypeMatch(TypeId subTy, TypeId superTy, NotNull<Scope> scope, T
|
||||
InternalErrorReporter iceReporter;
|
||||
UnifierSharedState unifierState(&iceReporter);
|
||||
Normalizer normalizer{typeArena, builtinTypes, NotNull{&unifierState}};
|
||||
Unifier unifier(NotNull<Normalizer>{&normalizer}, scope, Location(), Variance::Covariant);
|
||||
|
||||
// Cost of normalization can be too high for autocomplete response time requirements
|
||||
unifier.normalize = false;
|
||||
unifier.checkInhabited = false;
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
Subtyping subtyping{builtinTypes, NotNull{typeArena}, NotNull{&normalizer}, NotNull{&iceReporter}, scope};
|
||||
|
||||
return subtyping.isSubtype(subTy, superTy).isSubtype;
|
||||
}
|
||||
else
|
||||
{
|
||||
Unifier unifier(NotNull<Normalizer>{&normalizer}, scope, Location(), Variance::Covariant);
|
||||
|
||||
// Cost of normalization can be too high for autocomplete response time requirements
|
||||
unifier.normalize = false;
|
||||
unifier.checkInhabited = false;
|
||||
|
||||
return unifier.canUnify(subTy, superTy).empty();
|
||||
}
|
||||
|
||||
return unifier.canUnify(subTy, superTy).empty();
|
||||
}
|
||||
|
||||
static TypeCorrectKind checkTypeCorrectKind(
|
||||
|
@ -7,7 +7,7 @@
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/ConstraintSolver.h"
|
||||
#include "Luau/ConstraintGraphBuilder.h"
|
||||
#include "Luau/ConstraintGenerator.h"
|
||||
#include "Luau/NotNull.h"
|
||||
#include "Luau/TypeInfer.h"
|
||||
#include "Luau/TypeFamily.h"
|
||||
|
@ -14,7 +14,7 @@ LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
LUAU_FASTINTVARIABLE(LuauTypeCloneRecursionLimit, 300)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCloneCyclicUnions, false)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauStacklessTypeClone2, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauStacklessTypeClone3, false)
|
||||
LUAU_FASTINTVARIABLE(LuauTypeCloneIterationLimit, 100'000)
|
||||
|
||||
namespace Luau
|
||||
@ -118,6 +118,8 @@ private:
|
||||
ty = follow(ty, FollowOption::DisableLazyTypeThunks);
|
||||
if (auto it = types->find(ty); it != types->end())
|
||||
return it->second;
|
||||
else if (ty->persistent)
|
||||
return ty;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
@ -126,6 +128,8 @@ private:
|
||||
tp = follow(tp);
|
||||
if (auto it = packs->find(tp); it != packs->end())
|
||||
return it->second;
|
||||
else if (tp->persistent)
|
||||
return tp;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
@ -879,7 +883,7 @@ TypePackId clone(TypePackId tp, TypeArena& dest, CloneState& cloneState)
|
||||
if (tp->persistent)
|
||||
return tp;
|
||||
|
||||
if (FFlag::LuauStacklessTypeClone2)
|
||||
if (FFlag::LuauStacklessTypeClone3)
|
||||
{
|
||||
TypeCloner2 cloner{NotNull{&dest}, cloneState.builtinTypes, NotNull{&cloneState.seenTypes}, NotNull{&cloneState.seenTypePacks}};
|
||||
return cloner.clone(tp);
|
||||
@ -905,7 +909,7 @@ TypeId clone(TypeId typeId, TypeArena& dest, CloneState& cloneState)
|
||||
if (typeId->persistent)
|
||||
return typeId;
|
||||
|
||||
if (FFlag::LuauStacklessTypeClone2)
|
||||
if (FFlag::LuauStacklessTypeClone3)
|
||||
{
|
||||
TypeCloner2 cloner{NotNull{&dest}, cloneState.builtinTypes, NotNull{&cloneState.seenTypes}, NotNull{&cloneState.seenTypePacks}};
|
||||
return cloner.clone(typeId);
|
||||
@ -934,7 +938,7 @@ TypeId clone(TypeId typeId, TypeArena& dest, CloneState& cloneState)
|
||||
|
||||
TypeFun clone(const TypeFun& typeFun, TypeArena& dest, CloneState& cloneState)
|
||||
{
|
||||
if (FFlag::LuauStacklessTypeClone2)
|
||||
if (FFlag::LuauStacklessTypeClone3)
|
||||
{
|
||||
TypeCloner2 cloner{NotNull{&dest}, cloneState.builtinTypes, NotNull{&cloneState.seenTypes}, NotNull{&cloneState.seenTypePacks}};
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/ConstraintGraphBuilder.h"
|
||||
#include "Luau/ConstraintGenerator.h"
|
||||
|
||||
#include "Luau/Ast.h"
|
||||
#include "Luau/Def.h"
|
||||
@ -126,21 +126,21 @@ struct Checkpoint
|
||||
size_t offset;
|
||||
};
|
||||
|
||||
Checkpoint checkpoint(const ConstraintGraphBuilder* cgb)
|
||||
Checkpoint checkpoint(const ConstraintGenerator* cg)
|
||||
{
|
||||
return Checkpoint{cgb->constraints.size()};
|
||||
return Checkpoint{cg->constraints.size()};
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
void forEachConstraint(const Checkpoint& start, const Checkpoint& end, const ConstraintGraphBuilder* cgb, F f)
|
||||
void forEachConstraint(const Checkpoint& start, const Checkpoint& end, const ConstraintGenerator* cg, F f)
|
||||
{
|
||||
for (size_t i = start.offset; i < end.offset; ++i)
|
||||
f(cgb->constraints[i]);
|
||||
f(cg->constraints[i]);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ConstraintGraphBuilder::ConstraintGraphBuilder(ModulePtr module, NotNull<Normalizer> normalizer, NotNull<ModuleResolver> moduleResolver,
|
||||
ConstraintGenerator::ConstraintGenerator(ModulePtr module, NotNull<Normalizer> normalizer, NotNull<ModuleResolver> moduleResolver,
|
||||
NotNull<BuiltinTypes> builtinTypes, NotNull<InternalErrorReporter> ice, const ScopePtr& globalScope,
|
||||
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope, DcrLogger* logger, NotNull<DataFlowGraph> dfg,
|
||||
std::vector<RequireCycle> requireCycles)
|
||||
@ -160,7 +160,7 @@ ConstraintGraphBuilder::ConstraintGraphBuilder(ModulePtr module, NotNull<Normali
|
||||
LUAU_ASSERT(module);
|
||||
}
|
||||
|
||||
void ConstraintGraphBuilder::visitModuleRoot(AstStatBlock* block)
|
||||
void ConstraintGenerator::visitModuleRoot(AstStatBlock* block)
|
||||
{
|
||||
LUAU_ASSERT(scopes.empty());
|
||||
LUAU_ASSERT(rootScope == nullptr);
|
||||
@ -181,18 +181,18 @@ void ConstraintGraphBuilder::visitModuleRoot(AstStatBlock* block)
|
||||
logger->captureGenerationModule(module);
|
||||
}
|
||||
|
||||
TypeId ConstraintGraphBuilder::freshType(const ScopePtr& scope)
|
||||
TypeId ConstraintGenerator::freshType(const ScopePtr& scope)
|
||||
{
|
||||
return Luau::freshType(arena, builtinTypes, scope.get());
|
||||
}
|
||||
|
||||
TypePackId ConstraintGraphBuilder::freshTypePack(const ScopePtr& scope)
|
||||
TypePackId ConstraintGenerator::freshTypePack(const ScopePtr& scope)
|
||||
{
|
||||
FreeTypePack f{scope.get()};
|
||||
return arena->addTypePack(TypePackVar{std::move(f)});
|
||||
}
|
||||
|
||||
ScopePtr ConstraintGraphBuilder::childScope(AstNode* node, const ScopePtr& parent)
|
||||
ScopePtr ConstraintGenerator::childScope(AstNode* node, const ScopePtr& parent)
|
||||
{
|
||||
auto scope = std::make_shared<Scope>(parent);
|
||||
scopes.emplace_back(node->location, scope);
|
||||
@ -206,17 +206,17 @@ ScopePtr ConstraintGraphBuilder::childScope(AstNode* node, const ScopePtr& paren
|
||||
return scope;
|
||||
}
|
||||
|
||||
NotNull<Constraint> ConstraintGraphBuilder::addConstraint(const ScopePtr& scope, const Location& location, ConstraintV cv)
|
||||
NotNull<Constraint> ConstraintGenerator::addConstraint(const ScopePtr& scope, const Location& location, ConstraintV cv)
|
||||
{
|
||||
return NotNull{constraints.emplace_back(new Constraint{NotNull{scope.get()}, location, std::move(cv)}).get()};
|
||||
}
|
||||
|
||||
NotNull<Constraint> ConstraintGraphBuilder::addConstraint(const ScopePtr& scope, std::unique_ptr<Constraint> c)
|
||||
NotNull<Constraint> ConstraintGenerator::addConstraint(const ScopePtr& scope, std::unique_ptr<Constraint> c)
|
||||
{
|
||||
return NotNull{constraints.emplace_back(std::move(c)).get()};
|
||||
}
|
||||
|
||||
void ConstraintGraphBuilder::unionRefinements(const RefinementContext& lhs, const RefinementContext& rhs, RefinementContext& dest, std::vector<ConstraintV>* constraints)
|
||||
void ConstraintGenerator::unionRefinements(const RefinementContext& lhs, const RefinementContext& rhs, RefinementContext& dest, std::vector<ConstraintV>* constraints)
|
||||
{
|
||||
const auto intersect = [&](const std::vector<TypeId>& types) {
|
||||
if (1 == types.size())
|
||||
@ -252,7 +252,7 @@ void ConstraintGraphBuilder::unionRefinements(const RefinementContext& lhs, cons
|
||||
}
|
||||
}
|
||||
|
||||
void ConstraintGraphBuilder::computeRefinement(const ScopePtr& scope, RefinementId refinement, RefinementContext* refis, bool sense, bool eq, std::vector<ConstraintV>* constraints)
|
||||
void ConstraintGenerator::computeRefinement(const ScopePtr& scope, RefinementId refinement, RefinementContext* refis, bool sense, bool eq, std::vector<ConstraintV>* constraints)
|
||||
{
|
||||
if (!refinement)
|
||||
return;
|
||||
@ -382,7 +382,7 @@ bool mustDeferIntersection(TypeId ty)
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void ConstraintGraphBuilder::applyRefinements(const ScopePtr& scope, Location location, RefinementId refinement)
|
||||
void ConstraintGenerator::applyRefinements(const ScopePtr& scope, Location location, RefinementId refinement)
|
||||
{
|
||||
if (!refinement)
|
||||
return;
|
||||
@ -439,7 +439,7 @@ void ConstraintGraphBuilder::applyRefinements(const ScopePtr& scope, Location lo
|
||||
addConstraint(scope, location, c);
|
||||
}
|
||||
|
||||
ControlFlow ConstraintGraphBuilder::visitBlockWithoutChildScope(const ScopePtr& scope, AstStatBlock* block)
|
||||
ControlFlow ConstraintGenerator::visitBlockWithoutChildScope(const ScopePtr& scope, AstStatBlock* block)
|
||||
{
|
||||
RecursionCounter counter{&recursionCount};
|
||||
|
||||
@ -502,7 +502,7 @@ ControlFlow ConstraintGraphBuilder::visitBlockWithoutChildScope(const ScopePtr&
|
||||
return firstControlFlow.value_or(ControlFlow::None);
|
||||
}
|
||||
|
||||
ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStat* stat)
|
||||
ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStat* stat)
|
||||
{
|
||||
RecursionLimiter limiter{&recursionCount, FInt::LuauCheckRecursionLimit};
|
||||
|
||||
@ -560,7 +560,7 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStat* stat)
|
||||
}
|
||||
}
|
||||
|
||||
ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocal* statLocal)
|
||||
ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocal* statLocal)
|
||||
{
|
||||
std::vector<std::optional<TypeId>> varTypes;
|
||||
varTypes.reserve(statLocal->vars.size);
|
||||
@ -663,7 +663,7 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocal* s
|
||||
return ControlFlow::None;
|
||||
}
|
||||
|
||||
ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFor* for_)
|
||||
ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFor* for_)
|
||||
{
|
||||
TypeId annotationTy = builtinTypes->numberType;
|
||||
if (for_->var->annotation)
|
||||
@ -693,7 +693,7 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFor* for
|
||||
return ControlFlow::None;
|
||||
}
|
||||
|
||||
ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatForIn* forIn)
|
||||
ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatForIn* forIn)
|
||||
{
|
||||
ScopePtr loopScope = childScope(forIn, scope);
|
||||
|
||||
@ -728,7 +728,7 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatForIn* f
|
||||
return ControlFlow::None;
|
||||
}
|
||||
|
||||
ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatWhile* while_)
|
||||
ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatWhile* while_)
|
||||
{
|
||||
RefinementId refinement = check(scope, while_->condition).refinement;
|
||||
|
||||
@ -740,7 +740,7 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatWhile* w
|
||||
return ControlFlow::None;
|
||||
}
|
||||
|
||||
ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatRepeat* repeat)
|
||||
ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatRepeat* repeat)
|
||||
{
|
||||
ScopePtr repeatScope = childScope(repeat, scope);
|
||||
|
||||
@ -751,7 +751,7 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatRepeat*
|
||||
return ControlFlow::None;
|
||||
}
|
||||
|
||||
ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocalFunction* function)
|
||||
ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocalFunction* function)
|
||||
{
|
||||
// Local
|
||||
// Global
|
||||
@ -801,7 +801,7 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocalFun
|
||||
return ControlFlow::None;
|
||||
}
|
||||
|
||||
ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFunction* function)
|
||||
ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* function)
|
||||
{
|
||||
// Name could be AstStatLocal, AstStatGlobal, AstStatIndexName.
|
||||
// With or without self
|
||||
@ -846,7 +846,7 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFunction
|
||||
else if (AstExprIndexName* indexName = function->name->as<AstExprIndexName>())
|
||||
{
|
||||
Checkpoint check1 = checkpoint(this);
|
||||
std::optional<TypeId> lvalueType = checkLValue(scope, indexName);
|
||||
std::optional<TypeId> lvalueType = checkLValue(scope, indexName, generalizedType);
|
||||
LUAU_ASSERT(lvalueType);
|
||||
Checkpoint check2 = checkpoint(this);
|
||||
|
||||
@ -856,12 +856,9 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFunction
|
||||
|
||||
// TODO figure out how to populate the location field of the table Property.
|
||||
|
||||
if (lvalueType)
|
||||
if (lvalueType && *lvalueType != generalizedType)
|
||||
{
|
||||
if (get<FreeType>(*lvalueType))
|
||||
asMutable(*lvalueType)->ty.emplace<BoundType>(generalizedType);
|
||||
else
|
||||
addConstraint(scope, indexName->location, SubtypeConstraint{*lvalueType, generalizedType});
|
||||
addConstraint(scope, indexName->location, SubtypeConstraint{*lvalueType, generalizedType});
|
||||
}
|
||||
}
|
||||
else if (AstExprError* err = function->name->as<AstExprError>())
|
||||
@ -900,7 +897,7 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFunction
|
||||
return ControlFlow::None;
|
||||
}
|
||||
|
||||
ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatReturn* ret)
|
||||
ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatReturn* ret)
|
||||
{
|
||||
// At this point, the only way scope->returnType should have anything
|
||||
// interesting in it is if the function has an explicit return annotation.
|
||||
@ -916,7 +913,7 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatReturn*
|
||||
return ControlFlow::Returns;
|
||||
}
|
||||
|
||||
ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatBlock* block)
|
||||
ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatBlock* block)
|
||||
{
|
||||
ScopePtr innerScope = childScope(block, scope);
|
||||
|
||||
@ -944,7 +941,7 @@ static void bindFreeType(TypeId a, TypeId b)
|
||||
asMutable(b)->ty.emplace<BoundType>(a);
|
||||
}
|
||||
|
||||
ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatAssign* assign)
|
||||
ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatAssign* assign)
|
||||
{
|
||||
std::vector<std::optional<TypeId>> expectedTypes;
|
||||
expectedTypes.reserve(assign->vars.size);
|
||||
@ -957,16 +954,7 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatAssign*
|
||||
TypeId assignee = arena->addType(BlockedType{});
|
||||
assignees.push_back(assignee);
|
||||
|
||||
std::optional<TypeId> upperBound = follow(checkLValue(scope, lvalue));
|
||||
if (upperBound)
|
||||
{
|
||||
if (get<FreeType>(*upperBound))
|
||||
expectedTypes.push_back(std::nullopt);
|
||||
else
|
||||
expectedTypes.push_back(*upperBound);
|
||||
|
||||
addConstraint(scope, lvalue->location, SubtypeConstraint{assignee, *upperBound});
|
||||
}
|
||||
checkLValue(scope, lvalue, assignee);
|
||||
|
||||
DefId def = dfg->getDef(lvalue);
|
||||
scope->lvalueTypes[def] = assignee;
|
||||
@ -979,14 +967,12 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatAssign*
|
||||
return ControlFlow::None;
|
||||
}
|
||||
|
||||
ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatCompoundAssign* assign)
|
||||
ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatCompoundAssign* assign)
|
||||
{
|
||||
std::optional<TypeId> varTy = checkLValue(scope, assign->var);
|
||||
|
||||
AstExprBinary binop = AstExprBinary{assign->location, assign->op, assign->var, assign->value};
|
||||
TypeId resultTy = check(scope, &binop).ty;
|
||||
if (varTy)
|
||||
addConstraint(scope, assign->location, SubtypeConstraint{resultTy, *varTy});
|
||||
|
||||
checkLValue(scope, assign->var, resultTy);
|
||||
|
||||
DefId def = dfg->getDef(assign->var);
|
||||
scope->lvalueTypes[def] = resultTy;
|
||||
@ -994,7 +980,7 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatCompound
|
||||
return ControlFlow::None;
|
||||
}
|
||||
|
||||
ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatIf* ifStatement)
|
||||
ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatIf* ifStatement)
|
||||
{
|
||||
RefinementId refinement = check(scope, ifStatement->condition, std::nullopt).refinement;
|
||||
|
||||
@ -1041,7 +1027,7 @@ static bool occursCheck(TypeId needle, TypeId haystack)
|
||||
return false;
|
||||
}
|
||||
|
||||
ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatTypeAlias* alias)
|
||||
ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeAlias* alias)
|
||||
{
|
||||
ScopePtr* defnScope = astTypeAliasDefiningScopes.find(alias);
|
||||
|
||||
@ -1090,7 +1076,7 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatTypeAlia
|
||||
return ControlFlow::None;
|
||||
}
|
||||
|
||||
ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatDeclareGlobal* global)
|
||||
ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareGlobal* global)
|
||||
{
|
||||
LUAU_ASSERT(global->type);
|
||||
|
||||
@ -1115,7 +1101,7 @@ static bool isMetamethod(const Name& name)
|
||||
(FFlag::LuauFloorDivision && name == "__idiv");
|
||||
}
|
||||
|
||||
ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatDeclareClass* declaredClass)
|
||||
ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareClass* declaredClass)
|
||||
{
|
||||
std::optional<TypeId> superTy = std::make_optional(builtinTypes->classType);
|
||||
if (declaredClass->superName)
|
||||
@ -1234,7 +1220,7 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatDeclareC
|
||||
return ControlFlow::None;
|
||||
}
|
||||
|
||||
ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatDeclareFunction* global)
|
||||
ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareFunction* global)
|
||||
{
|
||||
std::vector<std::pair<Name, GenericTypeDefinition>> generics = createGenerics(scope, global->generics);
|
||||
std::vector<std::pair<Name, GenericTypePackDefinition>> genericPacks = createGenericPacks(scope, global->genericPacks);
|
||||
@ -1279,7 +1265,7 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatDeclareF
|
||||
return ControlFlow::None;
|
||||
}
|
||||
|
||||
ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatError* error)
|
||||
ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatError* error)
|
||||
{
|
||||
for (AstStat* stat : error->statements)
|
||||
visit(scope, stat);
|
||||
@ -1289,7 +1275,7 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatError* e
|
||||
return ControlFlow::None;
|
||||
}
|
||||
|
||||
InferencePack ConstraintGraphBuilder::checkPack(
|
||||
InferencePack ConstraintGenerator::checkPack(
|
||||
const ScopePtr& scope, AstArray<AstExpr*> exprs, const std::vector<std::optional<TypeId>>& expectedTypes)
|
||||
{
|
||||
std::vector<TypeId> head;
|
||||
@ -1320,7 +1306,7 @@ InferencePack ConstraintGraphBuilder::checkPack(
|
||||
return InferencePack{arena->addTypePack(TypePack{std::move(head), tail})};
|
||||
}
|
||||
|
||||
InferencePack ConstraintGraphBuilder::checkPack(
|
||||
InferencePack ConstraintGenerator::checkPack(
|
||||
const ScopePtr& scope, AstExpr* expr, const std::vector<std::optional<TypeId>>& expectedTypes, bool generalize)
|
||||
{
|
||||
RecursionCounter counter{&recursionCount};
|
||||
@ -1356,7 +1342,7 @@ InferencePack ConstraintGraphBuilder::checkPack(
|
||||
return result;
|
||||
}
|
||||
|
||||
InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCall* call)
|
||||
InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall* call)
|
||||
{
|
||||
std::vector<AstExpr*> exprArgs;
|
||||
|
||||
@ -1530,7 +1516,7 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCa
|
||||
}
|
||||
}
|
||||
|
||||
Inference ConstraintGraphBuilder::check(
|
||||
Inference ConstraintGenerator::check(
|
||||
const ScopePtr& scope, AstExpr* expr, std::optional<TypeId> expectedType, bool forceSingleton, bool generalize)
|
||||
{
|
||||
RecursionCounter counter{&recursionCount};
|
||||
@ -1600,7 +1586,7 @@ Inference ConstraintGraphBuilder::check(
|
||||
return result;
|
||||
}
|
||||
|
||||
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprConstantString* string, std::optional<TypeId> expectedType, bool forceSingleton)
|
||||
Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprConstantString* string, std::optional<TypeId> expectedType, bool forceSingleton)
|
||||
{
|
||||
if (forceSingleton)
|
||||
return Inference{arena->addType(SingletonType{StringSingleton{std::string{string->value.data, string->value.size}}})};
|
||||
@ -1624,7 +1610,7 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprConstantSt
|
||||
return Inference{builtinTypes->stringType};
|
||||
}
|
||||
|
||||
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprConstantBool* boolExpr, std::optional<TypeId> expectedType, bool forceSingleton)
|
||||
Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprConstantBool* boolExpr, std::optional<TypeId> expectedType, bool forceSingleton)
|
||||
{
|
||||
const TypeId singletonType = boolExpr->value ? builtinTypes->trueType : builtinTypes->falseType;
|
||||
if (forceSingleton)
|
||||
@ -1649,7 +1635,7 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprConstantBo
|
||||
return Inference{builtinTypes->booleanType};
|
||||
}
|
||||
|
||||
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprLocal* local)
|
||||
Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprLocal* local)
|
||||
{
|
||||
const RefinementKey* key = dfg->getRefinementKey(local);
|
||||
std::optional<DefId> rvalueDef = dfg->getRValueDefForCompoundAssign(local);
|
||||
@ -1675,10 +1661,10 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprLocal* loc
|
||||
return Inference{ty, refinementArena.proposition(key, builtinTypes->truthyType)};
|
||||
}
|
||||
else
|
||||
ice->ice("CGB: AstExprLocal came before its declaration?");
|
||||
ice->ice("CG: AstExprLocal came before its declaration?");
|
||||
}
|
||||
|
||||
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprGlobal* global)
|
||||
Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprGlobal* global)
|
||||
{
|
||||
const RefinementKey* key = dfg->getRefinementKey(global);
|
||||
std::optional<DefId> rvalueDef = dfg->getRValueDefForCompoundAssign(global);
|
||||
@ -1704,7 +1690,7 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprGlobal* gl
|
||||
}
|
||||
}
|
||||
|
||||
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIndexName* indexName)
|
||||
Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprIndexName* indexName)
|
||||
{
|
||||
TypeId obj = check(scope, indexName->expr).ty;
|
||||
TypeId result = arena->addType(BlockedType{});
|
||||
@ -1726,7 +1712,7 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIndexName*
|
||||
return Inference{result};
|
||||
}
|
||||
|
||||
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIndexExpr* indexExpr)
|
||||
Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprIndexExpr* indexExpr)
|
||||
{
|
||||
TypeId obj = check(scope, indexExpr->expr).ty;
|
||||
TypeId indexType = check(scope, indexExpr->index).ty;
|
||||
@ -1752,7 +1738,7 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIndexExpr*
|
||||
return Inference{result};
|
||||
}
|
||||
|
||||
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprFunction* func, std::optional<TypeId> expectedType, bool generalize)
|
||||
Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprFunction* func, std::optional<TypeId> expectedType, bool generalize)
|
||||
{
|
||||
Checkpoint startCheckpoint = checkpoint(this);
|
||||
FunctionSignature sig = checkFunctionSignature(scope, func, expectedType);
|
||||
@ -1785,7 +1771,7 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprFunction*
|
||||
}
|
||||
}
|
||||
|
||||
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprUnary* unary)
|
||||
Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprUnary* unary)
|
||||
{
|
||||
auto [operandType, refinement] = check(scope, unary->expr);
|
||||
|
||||
@ -1826,7 +1812,7 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprUnary* una
|
||||
}
|
||||
}
|
||||
|
||||
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprBinary* binary, std::optional<TypeId> expectedType)
|
||||
Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprBinary* binary, std::optional<TypeId> expectedType)
|
||||
{
|
||||
auto [leftType, rightType, refinement] = checkBinary(scope, binary, expectedType);
|
||||
|
||||
@ -1990,7 +1976,7 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprBinary* bi
|
||||
}
|
||||
}
|
||||
|
||||
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIfElse* ifElse, std::optional<TypeId> expectedType)
|
||||
Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprIfElse* ifElse, std::optional<TypeId> expectedType)
|
||||
{
|
||||
ScopePtr condScope = childScope(ifElse->condition, scope);
|
||||
RefinementId refinement = check(condScope, ifElse->condition).refinement;
|
||||
@ -2006,13 +1992,13 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIfElse* if
|
||||
return Inference{expectedType ? *expectedType : simplifyUnion(builtinTypes, arena, thenType, elseType).result};
|
||||
}
|
||||
|
||||
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprTypeAssertion* typeAssert)
|
||||
Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTypeAssertion* typeAssert)
|
||||
{
|
||||
check(scope, typeAssert->expr, std::nullopt);
|
||||
return Inference{resolveType(scope, typeAssert->annotation, /* inTypeArguments */ false)};
|
||||
}
|
||||
|
||||
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprInterpString* interpString)
|
||||
Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprInterpString* interpString)
|
||||
{
|
||||
for (AstExpr* expr : interpString->expressions)
|
||||
check(scope, expr);
|
||||
@ -2020,7 +2006,7 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprInterpStri
|
||||
return Inference{builtinTypes->stringType};
|
||||
}
|
||||
|
||||
std::tuple<TypeId, TypeId, RefinementId> ConstraintGraphBuilder::checkBinary(
|
||||
std::tuple<TypeId, TypeId, RefinementId> ConstraintGenerator::checkBinary(
|
||||
const ScopePtr& scope, AstExprBinary* binary, std::optional<TypeId> expectedType)
|
||||
{
|
||||
if (binary->op == AstExprBinary::And)
|
||||
@ -2133,16 +2119,16 @@ std::tuple<TypeId, TypeId, RefinementId> ConstraintGraphBuilder::checkBinary(
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<TypeId> ConstraintGraphBuilder::checkLValue(const ScopePtr& scope, AstExpr* expr)
|
||||
std::optional<TypeId> ConstraintGenerator::checkLValue(const ScopePtr& scope, AstExpr* expr, TypeId assignedTy)
|
||||
{
|
||||
if (auto local = expr->as<AstExprLocal>())
|
||||
return checkLValue(scope, local);
|
||||
return checkLValue(scope, local, assignedTy);
|
||||
else if (auto global = expr->as<AstExprGlobal>())
|
||||
return checkLValue(scope, global);
|
||||
return checkLValue(scope, global, assignedTy);
|
||||
else if (auto indexName = expr->as<AstExprIndexName>())
|
||||
return checkLValue(scope, indexName);
|
||||
return checkLValue(scope, indexName, assignedTy);
|
||||
else if (auto indexExpr = expr->as<AstExprIndexExpr>())
|
||||
return checkLValue(scope, indexExpr);
|
||||
return checkLValue(scope, indexExpr, assignedTy);
|
||||
else if (auto error = expr->as<AstExprError>())
|
||||
{
|
||||
check(scope, error);
|
||||
@ -2152,7 +2138,7 @@ std::optional<TypeId> ConstraintGraphBuilder::checkLValue(const ScopePtr& scope,
|
||||
ice->ice("checkLValue is inexhaustive");
|
||||
}
|
||||
|
||||
std::optional<TypeId> ConstraintGraphBuilder::checkLValue(const ScopePtr& scope, AstExprLocal* local)
|
||||
std::optional<TypeId> ConstraintGenerator::checkLValue(const ScopePtr& scope, AstExprLocal* local, TypeId assignedTy)
|
||||
{
|
||||
/*
|
||||
* The caller of this method uses the returned type to emit the proper
|
||||
@ -2162,11 +2148,14 @@ std::optional<TypeId> ConstraintGraphBuilder::checkLValue(const ScopePtr& scope,
|
||||
* populated by symbols that have type annotations.
|
||||
*
|
||||
* If this local has an interesting type annotation, it is important that we
|
||||
* return that.
|
||||
* return that and constrain the assigned type.
|
||||
*/
|
||||
std::optional<TypeId> annotatedTy = scope->lookup(local->local);
|
||||
if (annotatedTy)
|
||||
{
|
||||
addConstraint(scope, local->location, SubtypeConstraint{assignedTy, *annotatedTy});
|
||||
return annotatedTy;
|
||||
}
|
||||
|
||||
/*
|
||||
* As a safety measure, we'll assert that no type has yet been ascribed to
|
||||
@ -2177,34 +2166,19 @@ std::optional<TypeId> ConstraintGraphBuilder::checkLValue(const ScopePtr& scope,
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<TypeId> ConstraintGraphBuilder::checkLValue(const ScopePtr& scope, AstExprGlobal* global)
|
||||
std::optional<TypeId> ConstraintGenerator::checkLValue(const ScopePtr& scope, AstExprGlobal* global, TypeId assignedTy)
|
||||
{
|
||||
return scope->lookup(Symbol{global->name});
|
||||
}
|
||||
|
||||
std::optional<TypeId> ConstraintGraphBuilder::checkLValue(const ScopePtr& scope, AstExprIndexName* indexName)
|
||||
std::optional<TypeId> ConstraintGenerator::checkLValue(const ScopePtr& scope, AstExprIndexName* indexName, TypeId assignedTy)
|
||||
{
|
||||
return updateProperty(scope, indexName);
|
||||
return updateProperty(scope, indexName, assignedTy);
|
||||
}
|
||||
|
||||
std::optional<TypeId> ConstraintGraphBuilder::checkLValue(const ScopePtr& scope, AstExprIndexExpr* indexExpr)
|
||||
std::optional<TypeId> ConstraintGenerator::checkLValue(const ScopePtr& scope, AstExprIndexExpr* indexExpr, TypeId assignedTy)
|
||||
{
|
||||
return updateProperty(scope, indexExpr);
|
||||
}
|
||||
|
||||
static bool isIndexNameEquivalent(AstExpr* expr)
|
||||
{
|
||||
if (expr->is<AstExprIndexName>())
|
||||
return true;
|
||||
|
||||
AstExprIndexExpr* e = expr->as<AstExprIndexExpr>();
|
||||
if (e == nullptr)
|
||||
return false;
|
||||
|
||||
if (!e->index->is<AstExprConstantString>())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
return updateProperty(scope, indexExpr, assignedTy);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2212,8 +2186,19 @@ static bool isIndexNameEquivalent(AstExpr* expr)
|
||||
*
|
||||
* If expr has the form name.a.b.c
|
||||
*/
|
||||
TypeId ConstraintGraphBuilder::updateProperty(const ScopePtr& scope, AstExpr* expr)
|
||||
TypeId ConstraintGenerator::updateProperty(const ScopePtr& scope, AstExpr* expr, TypeId assignedTy)
|
||||
{
|
||||
// There are a bunch of cases where we realize that this is not the kind of
|
||||
// assignment that potentially changes the shape of a table. When we
|
||||
// encounter them, we call this to fall back and do the "usual thing."
|
||||
auto fallback = [&]() {
|
||||
TypeId resTy = check(scope, expr).ty;
|
||||
addConstraint(scope, expr->location, SubtypeConstraint{assignedTy, resTy});
|
||||
return resTy;
|
||||
};
|
||||
|
||||
LUAU_ASSERT(expr->is<AstExprIndexName>() || expr->is<AstExprIndexExpr>());
|
||||
|
||||
if (auto indexExpr = expr->as<AstExprIndexExpr>(); indexExpr && !indexExpr->index->is<AstExprConstantString>())
|
||||
{
|
||||
// An indexer is only interesting in an lvalue-ey way if it is at the
|
||||
@ -2231,15 +2216,12 @@ TypeId ConstraintGraphBuilder::updateProperty(const ScopePtr& scope, AstExpr* ex
|
||||
TypeId resultType = arena->addType(BlockedType{});
|
||||
TypeId subjectType = check(scope, indexExpr->expr).ty;
|
||||
TypeId indexType = check(scope, indexExpr->index).ty;
|
||||
TypeId propType = arena->addType(BlockedType{});
|
||||
addConstraint(scope, expr->location, SetIndexerConstraint{resultType, subjectType, indexType, propType});
|
||||
addConstraint(scope, expr->location, SetIndexerConstraint{resultType, subjectType, indexType, assignedTy});
|
||||
|
||||
module->astTypes[expr] = propType;
|
||||
module->astTypes[expr] = assignedTy;
|
||||
|
||||
return propType;
|
||||
return assignedTy;
|
||||
}
|
||||
else if (!isIndexNameEquivalent(expr))
|
||||
return check(scope, expr).ty;
|
||||
|
||||
Symbol sym;
|
||||
const Def* def = nullptr;
|
||||
@ -2269,21 +2251,24 @@ TypeId ConstraintGraphBuilder::updateProperty(const ScopePtr& scope, AstExpr* ex
|
||||
}
|
||||
else if (auto indexExpr = e->as<AstExprIndexExpr>())
|
||||
{
|
||||
// We need to populate the type for the index value
|
||||
check(scope, indexExpr->index);
|
||||
if (auto strIndex = indexExpr->index->as<AstExprConstantString>())
|
||||
{
|
||||
// We need to populate astTypes for the index value.
|
||||
check(scope, indexExpr->index);
|
||||
|
||||
segments.push_back(std::string(strIndex->value.data, strIndex->value.size));
|
||||
exprs.push_back(e);
|
||||
e = indexExpr->expr;
|
||||
}
|
||||
else
|
||||
{
|
||||
return check(scope, expr).ty;
|
||||
return fallback();
|
||||
}
|
||||
}
|
||||
else
|
||||
return check(scope, expr).ty;
|
||||
{
|
||||
return fallback();
|
||||
}
|
||||
}
|
||||
|
||||
LUAU_ASSERT(!segments.empty());
|
||||
@ -2294,16 +2279,14 @@ TypeId ConstraintGraphBuilder::updateProperty(const ScopePtr& scope, AstExpr* ex
|
||||
LUAU_ASSERT(def);
|
||||
std::optional<std::pair<TypeId, Scope*>> lookupResult = scope->lookupEx(NotNull{def});
|
||||
if (!lookupResult)
|
||||
return check(scope, expr).ty;
|
||||
return fallback();
|
||||
|
||||
const auto [subjectType, subjectScope] = *lookupResult;
|
||||
|
||||
TypeId propTy = freshType(scope);
|
||||
|
||||
std::vector<std::string> segmentStrings(begin(segments), end(segments));
|
||||
|
||||
TypeId updatedType = arena->addType(BlockedType{});
|
||||
addConstraint(scope, expr->location, SetPropConstraint{updatedType, subjectType, std::move(segmentStrings), propTy});
|
||||
addConstraint(scope, expr->location, SetPropConstraint{updatedType, subjectType, std::move(segmentStrings), assignedTy});
|
||||
|
||||
TypeId prevSegmentTy = updatedType;
|
||||
for (size_t i = 0; i < segments.size(); ++i)
|
||||
@ -2330,10 +2313,10 @@ TypeId ConstraintGraphBuilder::updateProperty(const ScopePtr& scope, AstExpr* ex
|
||||
}
|
||||
}
|
||||
|
||||
return propTy;
|
||||
return assignedTy;
|
||||
}
|
||||
|
||||
void ConstraintGraphBuilder::updateLValueType(AstExpr* lvalue, TypeId ty)
|
||||
void ConstraintGenerator::updateLValueType(AstExpr* lvalue, TypeId ty)
|
||||
{
|
||||
if (auto local = lvalue->as<AstExprLocal>())
|
||||
{
|
||||
@ -2342,7 +2325,7 @@ void ConstraintGraphBuilder::updateLValueType(AstExpr* lvalue, TypeId ty)
|
||||
}
|
||||
}
|
||||
|
||||
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprTable* expr, std::optional<TypeId> expectedType)
|
||||
Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr, std::optional<TypeId> expectedType)
|
||||
{
|
||||
const bool expectedTypeIsFree = expectedType && get<FreeType>(follow(*expectedType));
|
||||
|
||||
@ -2462,7 +2445,7 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprTable* exp
|
||||
return Inference{ty};
|
||||
}
|
||||
|
||||
ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionSignature(
|
||||
ConstraintGenerator::FunctionSignature ConstraintGenerator::checkFunctionSignature(
|
||||
const ScopePtr& parent, AstExprFunction* fn, std::optional<TypeId> expectedType, std::optional<Location> originalName)
|
||||
{
|
||||
ScopePtr signatureScope = nullptr;
|
||||
@ -2654,7 +2637,7 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS
|
||||
};
|
||||
}
|
||||
|
||||
void ConstraintGraphBuilder::checkFunctionBody(const ScopePtr& scope, AstExprFunction* fn)
|
||||
void ConstraintGenerator::checkFunctionBody(const ScopePtr& scope, AstExprFunction* fn)
|
||||
{
|
||||
visitBlockWithoutChildScope(scope, fn->body);
|
||||
|
||||
@ -2662,12 +2645,12 @@ void ConstraintGraphBuilder::checkFunctionBody(const ScopePtr& scope, AstExprFun
|
||||
|
||||
if (nullptr != getFallthrough(fn->body))
|
||||
{
|
||||
TypePackId empty = arena->addTypePack({}); // TODO we could have CGB retain one of these forever
|
||||
TypePackId empty = arena->addTypePack({}); // TODO we could have CG retain one of these forever
|
||||
addConstraint(scope, fn->location, PackSubtypeConstraint{scope->returnType, empty});
|
||||
}
|
||||
}
|
||||
|
||||
TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, bool inTypeArguments, bool replaceErrorWithFresh)
|
||||
TypeId ConstraintGenerator::resolveType(const ScopePtr& scope, AstType* ty, bool inTypeArguments, bool replaceErrorWithFresh)
|
||||
{
|
||||
TypeId result = nullptr;
|
||||
|
||||
@ -2895,7 +2878,7 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, b
|
||||
return result;
|
||||
}
|
||||
|
||||
TypePackId ConstraintGraphBuilder::resolveTypePack(const ScopePtr& scope, AstTypePack* tp, bool inTypeArgument, bool replaceErrorWithFresh)
|
||||
TypePackId ConstraintGenerator::resolveTypePack(const ScopePtr& scope, AstTypePack* tp, bool inTypeArgument, bool replaceErrorWithFresh)
|
||||
{
|
||||
TypePackId result;
|
||||
if (auto expl = tp->as<AstTypePackExplicit>())
|
||||
@ -2929,7 +2912,7 @@ TypePackId ConstraintGraphBuilder::resolveTypePack(const ScopePtr& scope, AstTyp
|
||||
return result;
|
||||
}
|
||||
|
||||
TypePackId ConstraintGraphBuilder::resolveTypePack(const ScopePtr& scope, const AstTypeList& list, bool inTypeArguments, bool replaceErrorWithFresh)
|
||||
TypePackId ConstraintGenerator::resolveTypePack(const ScopePtr& scope, const AstTypeList& list, bool inTypeArguments, bool replaceErrorWithFresh)
|
||||
{
|
||||
std::vector<TypeId> head;
|
||||
|
||||
@ -2947,7 +2930,7 @@ TypePackId ConstraintGraphBuilder::resolveTypePack(const ScopePtr& scope, const
|
||||
return arena->addTypePack(TypePack{head, tail});
|
||||
}
|
||||
|
||||
std::vector<std::pair<Name, GenericTypeDefinition>> ConstraintGraphBuilder::createGenerics(
|
||||
std::vector<std::pair<Name, GenericTypeDefinition>> ConstraintGenerator::createGenerics(
|
||||
const ScopePtr& scope, AstArray<AstGenericType> generics, bool useCache, bool addTypes)
|
||||
{
|
||||
std::vector<std::pair<Name, GenericTypeDefinition>> result;
|
||||
@ -2977,7 +2960,7 @@ std::vector<std::pair<Name, GenericTypeDefinition>> ConstraintGraphBuilder::crea
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<std::pair<Name, GenericTypePackDefinition>> ConstraintGraphBuilder::createGenericPacks(
|
||||
std::vector<std::pair<Name, GenericTypePackDefinition>> ConstraintGenerator::createGenericPacks(
|
||||
const ScopePtr& scope, AstArray<AstGenericTypePack> generics, bool useCache, bool addTypes)
|
||||
{
|
||||
std::vector<std::pair<Name, GenericTypePackDefinition>> result;
|
||||
@ -3008,7 +2991,7 @@ std::vector<std::pair<Name, GenericTypePackDefinition>> ConstraintGraphBuilder::
|
||||
return result;
|
||||
}
|
||||
|
||||
Inference ConstraintGraphBuilder::flattenPack(const ScopePtr& scope, Location location, InferencePack pack)
|
||||
Inference ConstraintGenerator::flattenPack(const ScopePtr& scope, Location location, InferencePack pack)
|
||||
{
|
||||
const auto& [tp, refinements] = pack;
|
||||
RefinementId refinement = nullptr;
|
||||
@ -3025,7 +3008,7 @@ Inference ConstraintGraphBuilder::flattenPack(const ScopePtr& scope, Location lo
|
||||
return Inference{typeResult, refinement};
|
||||
}
|
||||
|
||||
void ConstraintGraphBuilder::reportError(Location location, TypeErrorData err)
|
||||
void ConstraintGenerator::reportError(Location location, TypeErrorData err)
|
||||
{
|
||||
errors.push_back(TypeError{location, module->name, std::move(err)});
|
||||
|
||||
@ -3033,7 +3016,7 @@ void ConstraintGraphBuilder::reportError(Location location, TypeErrorData err)
|
||||
logger->captureGenerationError(errors.back());
|
||||
}
|
||||
|
||||
void ConstraintGraphBuilder::reportCodeTooComplex(Location location)
|
||||
void ConstraintGenerator::reportCodeTooComplex(Location location)
|
||||
{
|
||||
errors.push_back(TypeError{location, module->name, CodeTooComplex{}});
|
||||
|
||||
@ -3069,7 +3052,7 @@ struct GlobalPrepopulator : AstVisitor
|
||||
}
|
||||
};
|
||||
|
||||
void ConstraintGraphBuilder::prepopulateGlobalScope(const ScopePtr& globalScope, AstStatBlock* program)
|
||||
void ConstraintGenerator::prepopulateGlobalScope(const ScopePtr& globalScope, AstStatBlock* program)
|
||||
{
|
||||
GlobalPrepopulator gp{NotNull{globalScope.get()}, arena, dfg};
|
||||
|
||||
@ -3079,7 +3062,7 @@ void ConstraintGraphBuilder::prepopulateGlobalScope(const ScopePtr& globalScope,
|
||||
program->visit(&gp);
|
||||
}
|
||||
|
||||
void ConstraintGraphBuilder::fillInInferredBindings(const ScopePtr& globalScope, AstStatBlock* block)
|
||||
void ConstraintGenerator::fillInInferredBindings(const ScopePtr& globalScope, AstStatBlock* block)
|
||||
{
|
||||
for (const auto& [symbol, p] : inferredBindings)
|
||||
{
|
||||
@ -3094,7 +3077,7 @@ void ConstraintGraphBuilder::fillInInferredBindings(const ScopePtr& globalScope,
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::optional<TypeId>> ConstraintGraphBuilder::getExpectedCallTypesForFunctionOverloads(const TypeId fnType)
|
||||
std::vector<std::optional<TypeId>> ConstraintGenerator::getExpectedCallTypesForFunctionOverloads(const TypeId fnType)
|
||||
{
|
||||
std::vector<TypeId> funTys;
|
||||
if (auto it = get<IntersectionType>(follow(fnType)))
|
@ -34,7 +34,7 @@ DefId DataFlowGraph::getDef(const AstExpr* expr) const
|
||||
|
||||
std::optional<DefId> DataFlowGraph::getRValueDefForCompoundAssign(const AstExpr* expr) const
|
||||
{
|
||||
auto def = compoundAssignBreadcrumbs.find(expr);
|
||||
auto def = compoundAssignDefs.find(expr);
|
||||
return def ? std::optional<DefId>(*def) : std::nullopt;
|
||||
}
|
||||
|
||||
@ -628,11 +628,11 @@ void DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExpr* e, DefId incomi
|
||||
|
||||
void DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExprLocal* l, DefId incomingDef, bool isCompoundAssignment)
|
||||
{
|
||||
// We need to keep the previous breadcrumb around for a compound assignment.
|
||||
// We need to keep the previous def around for a compound assignment.
|
||||
if (isCompoundAssignment)
|
||||
{
|
||||
if (auto def = scope->lookup(l->local))
|
||||
graph.compoundAssignBreadcrumbs[l] = *def;
|
||||
graph.compoundAssignDefs[l] = *def;
|
||||
}
|
||||
|
||||
// In order to avoid alias tracking, we need to clip the reference to the parent def.
|
||||
@ -643,11 +643,11 @@ void DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExprLocal* l, DefId i
|
||||
|
||||
void DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExprGlobal* g, DefId incomingDef, bool isCompoundAssignment)
|
||||
{
|
||||
// We need to keep the previous breadcrumb around for a compound assignment.
|
||||
// We need to keep the previous def around for a compound assignment.
|
||||
if (isCompoundAssignment)
|
||||
{
|
||||
if (auto def = scope->lookup(g->name))
|
||||
graph.compoundAssignBreadcrumbs[g] = *def;
|
||||
graph.compoundAssignDefs[g] = *def;
|
||||
}
|
||||
|
||||
// In order to avoid alias tracking, we need to clip the reference to the parent def.
|
||||
|
@ -5,7 +5,7 @@
|
||||
#include "Luau/Clone.h"
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/Config.h"
|
||||
#include "Luau/ConstraintGraphBuilder.h"
|
||||
#include "Luau/ConstraintGenerator.h"
|
||||
#include "Luau/ConstraintSolver.h"
|
||||
#include "Luau/DataFlowGraph.h"
|
||||
#include "Luau/DcrLogger.h"
|
||||
@ -1255,13 +1255,13 @@ ModulePtr check(const SourceModule& sourceModule, Mode mode, const std::vector<R
|
||||
|
||||
Normalizer normalizer{&result->internalTypes, builtinTypes, NotNull{&unifierState}};
|
||||
|
||||
ConstraintGraphBuilder cgb{result, NotNull{&normalizer}, moduleResolver, builtinTypes, iceHandler, parentScope, std::move(prepareModuleScope),
|
||||
ConstraintGenerator cg{result, NotNull{&normalizer}, moduleResolver, builtinTypes, iceHandler, parentScope, std::move(prepareModuleScope),
|
||||
logger.get(), NotNull{&dfg}, requireCycles};
|
||||
|
||||
cgb.visitModuleRoot(sourceModule.root);
|
||||
result->errors = std::move(cgb.errors);
|
||||
cg.visitModuleRoot(sourceModule.root);
|
||||
result->errors = std::move(cg.errors);
|
||||
|
||||
ConstraintSolver cs{NotNull{&normalizer}, NotNull(cgb.rootScope), borrowConstraints(cgb.constraints), result->humanReadableName, moduleResolver,
|
||||
ConstraintSolver cs{NotNull{&normalizer}, NotNull(cg.rootScope), borrowConstraints(cg.constraints), result->humanReadableName, moduleResolver,
|
||||
requireCycles, logger.get(), limits};
|
||||
|
||||
if (options.randomizeConstraintResolutionSeed)
|
||||
@ -1283,7 +1283,7 @@ ModulePtr check(const SourceModule& sourceModule, Mode mode, const std::vector<R
|
||||
for (TypeError& e : cs.errors)
|
||||
result->errors.emplace_back(std::move(e));
|
||||
|
||||
result->scopes = std::move(cgb.scopes);
|
||||
result->scopes = std::move(cg.scopes);
|
||||
result->type = sourceModule.type;
|
||||
|
||||
result->clonePublicInterface(builtinTypes, *iceHandler);
|
||||
|
@ -14,9 +14,6 @@
|
||||
|
||||
LUAU_FASTINTVARIABLE(LuauSuggestionDistance, 4)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauLintDeprecatedFenv, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauLintTableIndexer, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
@ -2093,7 +2090,7 @@ private:
|
||||
// getfenv/setfenv are deprecated, however they are still used in some test frameworks and don't have a great general replacement
|
||||
// for now we warn about the deprecation only when they are used with a numeric first argument; this produces fewer warnings and makes use
|
||||
// of getfenv/setfenv a little more localized
|
||||
if (FFlag::LuauLintDeprecatedFenv && !node->self && node->args.size >= 1)
|
||||
if (!node->self && node->args.size >= 1)
|
||||
{
|
||||
if (AstExprGlobal* fenv = node->func->as<AstExprGlobal>(); fenv && (fenv->name == "getfenv" || fenv->name == "setfenv"))
|
||||
{
|
||||
@ -2185,7 +2182,7 @@ private:
|
||||
|
||||
bool visit(AstExprUnary* node) override
|
||||
{
|
||||
if (FFlag::LuauLintTableIndexer && node->op == AstExprUnary::Len)
|
||||
if (node->op == AstExprUnary::Len)
|
||||
checkIndexer(node, node->expr, "#");
|
||||
|
||||
return true;
|
||||
@ -2195,7 +2192,7 @@ private:
|
||||
{
|
||||
if (AstExprGlobal* func = node->func->as<AstExprGlobal>())
|
||||
{
|
||||
if (FFlag::LuauLintTableIndexer && func->name == "ipairs" && node->args.size == 1)
|
||||
if (func->name == "ipairs" && node->args.size == 1)
|
||||
checkIndexer(node, node->args.data[0], "ipairs");
|
||||
}
|
||||
else if (AstExprIndexName* func = node->func->as<AstExprIndexName>())
|
||||
@ -2209,8 +2206,6 @@ private:
|
||||
|
||||
void checkIndexer(AstExpr* node, AstExpr* expr, const char* op)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauLintTableIndexer);
|
||||
|
||||
std::optional<Luau::TypeId> ty = context->getType(expr);
|
||||
if (!ty)
|
||||
return;
|
||||
@ -2653,13 +2648,17 @@ private:
|
||||
case ConstantNumberParseResult::Ok:
|
||||
case ConstantNumberParseResult::Malformed:
|
||||
break;
|
||||
case ConstantNumberParseResult::Imprecise:
|
||||
emitWarning(*context, LintWarning::Code_IntegerParsing, node->location,
|
||||
"Number literal exceeded available precision and was truncated to closest representable number");
|
||||
break;
|
||||
case ConstantNumberParseResult::BinOverflow:
|
||||
emitWarning(*context, LintWarning::Code_IntegerParsing, node->location,
|
||||
"Binary number literal exceeded available precision and has been truncated to 2^64");
|
||||
"Binary number literal exceeded available precision and was truncated to 2^64");
|
||||
break;
|
||||
case ConstantNumberParseResult::HexOverflow:
|
||||
emitWarning(*context, LintWarning::Code_IntegerParsing, node->location,
|
||||
"Hexadecimal number literal exceeded available precision and has been truncated to 2^64");
|
||||
"Hexadecimal number literal exceeded available precision and was truncated to 2^64");
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
#include "Luau/Clone.h"
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/ConstraintGraphBuilder.h"
|
||||
#include "Luau/ConstraintGenerator.h"
|
||||
#include "Luau/Normalize.h"
|
||||
#include "Luau/RecursionCounter.h"
|
||||
#include "Luau/Scope.h"
|
||||
|
@ -8,7 +8,9 @@
|
||||
#include "Luau/Clone.h"
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/RecursionCounter.h"
|
||||
#include "Luau/Subtyping.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/TypeFwd.h"
|
||||
#include "Luau/Unifier.h"
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauCheckNormalizeInvariant, false)
|
||||
@ -19,6 +21,7 @@ LUAU_FASTINTVARIABLE(LuauNormalizeCacheLimit, 100000);
|
||||
LUAU_FASTFLAGVARIABLE(LuauNormalizeCyclicUnions, false);
|
||||
LUAU_FASTFLAG(LuauTransitiveSubtyping)
|
||||
LUAU_FASTFLAG(DebugLuauReadWriteProperties)
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -32,9 +35,14 @@ TypeIds::TypeIds(std::initializer_list<TypeId> tys)
|
||||
void TypeIds::insert(TypeId ty)
|
||||
{
|
||||
ty = follow(ty);
|
||||
auto [_, fresh] = types.insert(ty);
|
||||
if (fresh)
|
||||
|
||||
// get a reference to the slot for `ty` in `types`
|
||||
bool& entry = types[ty];
|
||||
|
||||
// if `ty` is fresh, we can set it to `true`, add it to the order and hash and be done.
|
||||
if (!entry)
|
||||
{
|
||||
entry = true;
|
||||
order.push_back(ty);
|
||||
hash ^= std::hash<TypeId>{}(ty);
|
||||
}
|
||||
@ -75,25 +83,26 @@ TypeIds::const_iterator TypeIds::end() const
|
||||
TypeIds::iterator TypeIds::erase(TypeIds::const_iterator it)
|
||||
{
|
||||
TypeId ty = *it;
|
||||
types.erase(ty);
|
||||
types[ty] = false;
|
||||
hash ^= std::hash<TypeId>{}(ty);
|
||||
return order.erase(it);
|
||||
}
|
||||
|
||||
size_t TypeIds::size() const
|
||||
{
|
||||
return types.size();
|
||||
return order.size();
|
||||
}
|
||||
|
||||
bool TypeIds::empty() const
|
||||
{
|
||||
return types.empty();
|
||||
return order.empty();
|
||||
}
|
||||
|
||||
size_t TypeIds::count(TypeId ty) const
|
||||
{
|
||||
ty = follow(ty);
|
||||
return types.count(ty);
|
||||
const bool* val = types.find(ty);
|
||||
return (val && *val) ? 1 : 0;
|
||||
}
|
||||
|
||||
void TypeIds::retain(const TypeIds& there)
|
||||
@ -122,7 +131,29 @@ bool TypeIds::isNever() const
|
||||
|
||||
bool TypeIds::operator==(const TypeIds& there) const
|
||||
{
|
||||
return hash == there.hash && types == there.types;
|
||||
// we can early return if the hashes don't match.
|
||||
if (hash != there.hash)
|
||||
return false;
|
||||
|
||||
// we have to check equality of the sets themselves if not.
|
||||
|
||||
// if the sets are unequal sizes, then they cannot possibly be equal.
|
||||
// it is important to use `order` here and not `types` since the mappings
|
||||
// may have different sizes since removal is not possible, and so erase
|
||||
// simply writes `false` into the map.
|
||||
if (order.size() != there.order.size())
|
||||
return false;
|
||||
|
||||
// otherwise, we'll need to check that every element we have here is in `there`.
|
||||
for (auto ty : order)
|
||||
{
|
||||
// if it's not, we'll return `false`
|
||||
if (there.count(ty) == 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
// otherwise, we've proven the two equal!
|
||||
return true;
|
||||
}
|
||||
|
||||
NormalizedStringType::NormalizedStringType() {}
|
||||
@ -240,6 +271,42 @@ NormalizedType::NormalizedType(NotNull<BuiltinTypes> builtinTypes)
|
||||
{
|
||||
}
|
||||
|
||||
bool NormalizedType::isUnknown() const
|
||||
{
|
||||
if (get<UnknownType>(tops))
|
||||
return true;
|
||||
|
||||
// Otherwise, we can still be unknown!
|
||||
bool hasAllPrimitives = isPrim(booleans, PrimitiveType::Boolean) && isPrim(nils, PrimitiveType::NilType) && isNumber(numbers) &&
|
||||
strings.isString() && isPrim(threads, PrimitiveType::Thread) && isThread(threads);
|
||||
|
||||
// Check is class
|
||||
bool isTopClass = false;
|
||||
for (auto [t, disj] : classes.classes)
|
||||
{
|
||||
if (auto ct = get<ClassType>(t))
|
||||
{
|
||||
if (ct->name == "class" && disj.empty())
|
||||
{
|
||||
isTopClass = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check is table
|
||||
bool isTopTable = false;
|
||||
for (auto t : tables)
|
||||
{
|
||||
if (isPrim(t, PrimitiveType::Table))
|
||||
{
|
||||
isTopTable = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// any = unknown or error ==> we need to make sure we have all the unknown components, but not errors
|
||||
return get<NeverType>(errors) && hasAllPrimitives && isTopClass && isTopTable && functions.isTop;
|
||||
}
|
||||
|
||||
bool NormalizedType::isExactlyNumber() const
|
||||
{
|
||||
return hasNumbers() && !hasTops() && !hasBooleans() && !hasClasses() && !hasErrors() && !hasNils() && !hasStrings() && !hasThreads() &&
|
||||
@ -647,8 +714,7 @@ static bool areNormalizedClasses(const NormalizedClassType& tys)
|
||||
|
||||
static bool isPlainTyvar(TypeId ty)
|
||||
{
|
||||
return (get<FreeType>(ty) || get<GenericType>(ty) || get<BlockedType>(ty) ||
|
||||
get<PendingExpansionType>(ty) || get<TypeFamilyInstanceType>(ty));
|
||||
return (get<FreeType>(ty) || get<GenericType>(ty) || get<BlockedType>(ty) || get<PendingExpansionType>(ty) || get<TypeFamilyInstanceType>(ty));
|
||||
}
|
||||
|
||||
static bool isNormalizedTyvar(const NormalizedTyvars& tyvars)
|
||||
@ -711,6 +777,11 @@ const NormalizedType* Normalizer::normalize(TypeId ty)
|
||||
std::unordered_set<TypeId> seenSetTypes;
|
||||
if (!unionNormalWithTy(norm, ty, seenSetTypes))
|
||||
return nullptr;
|
||||
if (norm.isUnknown())
|
||||
{
|
||||
clearNormal(norm);
|
||||
norm.tops = builtinTypes->unknownType;
|
||||
}
|
||||
std::unique_ptr<NormalizedType> uniq = std::make_unique<NormalizedType>(std::move(norm));
|
||||
const NormalizedType* result = uniq.get();
|
||||
cachedNormals[ty] = std::move(uniq);
|
||||
@ -1520,8 +1591,8 @@ bool Normalizer::unionNormalWithTy(NormalizedType& here, TypeId there, std::unor
|
||||
}
|
||||
else if (FFlag::LuauTransitiveSubtyping && get<UnknownType>(here.tops))
|
||||
return true;
|
||||
else if (get<GenericType>(there) || get<FreeType>(there) || get<BlockedType>(there) ||
|
||||
get<PendingExpansionType>(there) || get<TypeFamilyInstanceType>(there))
|
||||
else if (get<GenericType>(there) || get<FreeType>(there) || get<BlockedType>(there) || get<PendingExpansionType>(there) ||
|
||||
get<TypeFamilyInstanceType>(there))
|
||||
{
|
||||
if (tyvarIndex(there) <= ignoreSmallerTyvars)
|
||||
return true;
|
||||
@ -2661,8 +2732,8 @@ bool Normalizer::intersectNormalWithTy(NormalizedType& here, TypeId there, std::
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
else if (get<GenericType>(there) || get<FreeType>(there) || get<BlockedType>(there) ||
|
||||
get<PendingExpansionType>(there) || get<TypeFamilyInstanceType>(there))
|
||||
else if (get<GenericType>(there) || get<FreeType>(there) || get<BlockedType>(there) || get<PendingExpansionType>(there) ||
|
||||
get<TypeFamilyInstanceType>(there))
|
||||
{
|
||||
NormalizedType thereNorm{builtinTypes};
|
||||
NormalizedType topNorm{builtinTypes};
|
||||
@ -2915,32 +2986,58 @@ TypeId Normalizer::typeFromNormal(const NormalizedType& norm)
|
||||
|
||||
bool isSubtype(TypeId subTy, TypeId superTy, NotNull<Scope> scope, NotNull<BuiltinTypes> builtinTypes, InternalErrorReporter& ice)
|
||||
{
|
||||
if (!FFlag::LuauTransitiveSubtyping)
|
||||
if (!FFlag::LuauTransitiveSubtyping && !FFlag::DebugLuauDeferredConstraintResolution)
|
||||
return isConsistentSubtype(subTy, superTy, scope, builtinTypes, ice);
|
||||
|
||||
UnifierSharedState sharedState{&ice};
|
||||
TypeArena arena;
|
||||
Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}};
|
||||
Unifier u{NotNull{&normalizer}, scope, Location{}, Covariant};
|
||||
|
||||
u.tryUnify(subTy, superTy);
|
||||
return !u.failure;
|
||||
// Subtyping under DCR is not implemented using unification!
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
Subtyping subtyping{builtinTypes, NotNull{&arena}, NotNull{&normalizer}, NotNull{&ice}, scope};
|
||||
|
||||
return subtyping.isSubtype(subTy, superTy).isSubtype;
|
||||
}
|
||||
else
|
||||
{
|
||||
Unifier u{NotNull{&normalizer}, scope, Location{}, Covariant};
|
||||
|
||||
u.tryUnify(subTy, superTy);
|
||||
return !u.failure;
|
||||
}
|
||||
}
|
||||
|
||||
bool isSubtype(TypePackId subPack, TypePackId superPack, NotNull<Scope> scope, NotNull<BuiltinTypes> builtinTypes, InternalErrorReporter& ice)
|
||||
{
|
||||
if (!FFlag::LuauTransitiveSubtyping)
|
||||
if (!FFlag::LuauTransitiveSubtyping && !FFlag::DebugLuauDeferredConstraintResolution)
|
||||
return isConsistentSubtype(subPack, superPack, scope, builtinTypes, ice);
|
||||
|
||||
UnifierSharedState sharedState{&ice};
|
||||
TypeArena arena;
|
||||
Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}};
|
||||
Unifier u{NotNull{&normalizer}, scope, Location{}, Covariant};
|
||||
|
||||
u.tryUnify(subPack, superPack);
|
||||
return !u.failure;
|
||||
// Subtyping under DCR is not implemented using unification!
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
Subtyping subtyping{builtinTypes, NotNull{&arena}, NotNull{&normalizer}, NotNull{&ice}, scope};
|
||||
|
||||
return subtyping.isSubtype(subPack, superPack).isSubtype;
|
||||
}
|
||||
else
|
||||
{
|
||||
Unifier u{NotNull{&normalizer}, scope, Location{}, Covariant};
|
||||
|
||||
u.tryUnify(subPack, superPack);
|
||||
return !u.failure;
|
||||
}
|
||||
}
|
||||
|
||||
bool isConsistentSubtype(TypeId subTy, TypeId superTy, NotNull<Scope> scope, NotNull<BuiltinTypes> builtinTypes, InternalErrorReporter& ice)
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::DebugLuauDeferredConstraintResolution);
|
||||
|
||||
UnifierSharedState sharedState{&ice};
|
||||
TypeArena arena;
|
||||
Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}};
|
||||
@ -2954,6 +3051,8 @@ bool isConsistentSubtype(TypeId subTy, TypeId superTy, NotNull<Scope> scope, Not
|
||||
bool isConsistentSubtype(
|
||||
TypePackId subPack, TypePackId superPack, NotNull<Scope> scope, NotNull<BuiltinTypes> builtinTypes, InternalErrorReporter& ice)
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::DebugLuauDeferredConstraintResolution);
|
||||
|
||||
UnifierSharedState sharedState{&ice};
|
||||
TypeArena arena;
|
||||
Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}};
|
||||
|
@ -321,14 +321,26 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
|
||||
if (auto subUnion = get<UnionType>(subTy))
|
||||
result = isCovariantWith(env, subUnion, superTy);
|
||||
else if (auto superUnion = get<UnionType>(superTy))
|
||||
{
|
||||
result = isCovariantWith(env, subTy, superUnion);
|
||||
if (!result.isSubtype && !result.isErrorSuppressing && !result.normalizationTooComplex)
|
||||
{
|
||||
SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy));
|
||||
if (semantic.isSubtype)
|
||||
result = semantic;
|
||||
}
|
||||
}
|
||||
else if (auto superIntersection = get<IntersectionType>(superTy))
|
||||
result = isCovariantWith(env, subTy, superIntersection);
|
||||
else if (auto subIntersection = get<IntersectionType>(subTy))
|
||||
{
|
||||
result = isCovariantWith(env, subIntersection, superTy);
|
||||
if (!result.isSubtype && !result.isErrorSuppressing && !result.normalizationTooComplex)
|
||||
result = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy));
|
||||
{
|
||||
SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy));
|
||||
if (semantic.isSubtype)
|
||||
result = semantic;
|
||||
}
|
||||
}
|
||||
else if (get<AnyType>(superTy))
|
||||
result = {true};
|
||||
|
@ -2413,6 +2413,31 @@ struct TypeChecker2
|
||||
}
|
||||
}
|
||||
|
||||
void explainError(TypeId subTy, TypeId superTy, Location location, const SubtypingResult& r)
|
||||
{
|
||||
if (!r.reasoning)
|
||||
return reportError(TypeMismatch{superTy, subTy}, location);
|
||||
|
||||
std::optional<TypeOrPack> subLeaf = traverse(subTy, r.reasoning->subPath, builtinTypes);
|
||||
std::optional<TypeOrPack> superLeaf = traverse(superTy, r.reasoning->superPath, builtinTypes);
|
||||
|
||||
if (!subLeaf || !superLeaf)
|
||||
ice->ice("Subtyping test returned a reasoning with an invalid path", location);
|
||||
|
||||
if (!get2<TypeId, TypeId>(*subLeaf, *superLeaf) && !get2<TypePackId, TypePackId>(*subLeaf, *superLeaf))
|
||||
ice->ice("Subtyping test returned a reasoning where one path ends at a type and the other ends at a pack.", location);
|
||||
|
||||
std::string reason;
|
||||
|
||||
if (r.reasoning->subPath == r.reasoning->superPath)
|
||||
reason = "at " + toString(r.reasoning->subPath) + ", " + toString(*subLeaf) + " is not a subtype of " + toString(*superLeaf);
|
||||
else
|
||||
reason = "type " + toString(subTy) + toString(r.reasoning->subPath) + " (" + toString(*subLeaf) + ") is not a subtype of " +
|
||||
toString(superTy) + toString(r.reasoning->superPath) + " (" + toString(*superLeaf) + ")";
|
||||
|
||||
reportError(TypeMismatch{superTy, subTy, reason}, location);
|
||||
}
|
||||
|
||||
bool testIsSubtype(TypeId subTy, TypeId superTy, Location location)
|
||||
{
|
||||
SubtypingResult r = subtyping->isSubtype(subTy, superTy);
|
||||
@ -2421,27 +2446,7 @@ struct TypeChecker2
|
||||
reportError(NormalizationTooComplex{}, location);
|
||||
|
||||
if (!r.isSubtype && !r.isErrorSuppressing)
|
||||
{
|
||||
if (r.reasoning)
|
||||
{
|
||||
std::optional<TypeOrPack> subLeaf = traverse(subTy, r.reasoning->subPath, builtinTypes);
|
||||
std::optional<TypeOrPack> superLeaf = traverse(superTy, r.reasoning->superPath, builtinTypes);
|
||||
|
||||
if (!subLeaf || !superLeaf)
|
||||
ice->ice("Subtyping test returned a reasoning with an invalid path", location);
|
||||
|
||||
if (!get2<TypeId, TypeId>(*subLeaf, *superLeaf) && !get2<TypePackId, TypePackId>(*subLeaf, *superLeaf))
|
||||
ice->ice("Subtyping test returned a reasoning where one path ends at a type and the other ends at a pack.", location);
|
||||
|
||||
std::string reason = "type " + toString(subTy) + toString(r.reasoning->subPath) + " (" + toString(*subLeaf) +
|
||||
") is not a subtype of " + toString(superTy) + toString(r.reasoning->superPath) + " (" + toString(*superLeaf) +
|
||||
")";
|
||||
|
||||
reportError(TypeMismatch{superTy, subTy, reason}, location);
|
||||
}
|
||||
else
|
||||
reportError(TypeMismatch{superTy, subTy}, location);
|
||||
}
|
||||
explainError(subTy, superTy, location, r);
|
||||
|
||||
return r.isSubtype;
|
||||
}
|
||||
|
@ -35,11 +35,9 @@ LUAU_FASTFLAG(LuauKnowsTheDataModel3)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauSharedSelf, false)
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAllowIndexClassParameters, false)
|
||||
LUAU_FASTFLAG(LuauOccursIsntAlwaysFailure)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTinyControlFlowAnalysis, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauLoopControlFlowAnalysis, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauVariadicOverloadFix, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAlwaysCommitInferencesOfFunctionCalls, false)
|
||||
LUAU_FASTFLAG(LuauParseDeclareClassIndexer)
|
||||
LUAU_FASTFLAG(LuauFloorDivision);
|
||||
@ -3412,15 +3410,12 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex
|
||||
}
|
||||
}
|
||||
|
||||
if (FFlag::LuauAllowIndexClassParameters)
|
||||
if (const ClassType* exprClass = get<ClassType>(exprType))
|
||||
{
|
||||
if (const ClassType* exprClass = get<ClassType>(exprType))
|
||||
{
|
||||
if (isNonstrictMode())
|
||||
return unknownType;
|
||||
reportError(TypeError{expr.location, DynamicPropertyLookupOnClassesUnsafe{exprType}});
|
||||
return errorRecoveryType(scope);
|
||||
}
|
||||
if (isNonstrictMode())
|
||||
return unknownType;
|
||||
reportError(TypeError{expr.location, DynamicPropertyLookupOnClassesUnsafe{exprType}});
|
||||
return errorRecoveryType(scope);
|
||||
}
|
||||
}
|
||||
|
||||
@ -4026,13 +4021,9 @@ void TypeChecker::checkArgumentList(const ScopePtr& scope, const AstExpr& funNam
|
||||
if (argIndex < argLocations.size())
|
||||
location = argLocations[argIndex];
|
||||
|
||||
if (FFlag::LuauVariadicOverloadFix)
|
||||
{
|
||||
state.location = location;
|
||||
state.tryUnify(*argIter, vtp->ty);
|
||||
}
|
||||
else
|
||||
unify(*argIter, vtp->ty, scope, location);
|
||||
state.location = location;
|
||||
state.tryUnify(*argIter, vtp->ty);
|
||||
|
||||
++argIter;
|
||||
++argIndex;
|
||||
}
|
||||
|
@ -18,7 +18,6 @@
|
||||
LUAU_FASTINT(LuauTypeInferTypePackLoopLimit)
|
||||
LUAU_FASTFLAG(LuauErrorRecoveryType)
|
||||
LUAU_FASTFLAGVARIABLE(LuauInstantiateInSubtyping, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauMaintainScopesInUnifier, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTransitiveSubtyping, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauOccursIsntAlwaysFailure, false)
|
||||
LUAU_FASTFLAG(LuauAlwaysCommitInferencesOfFunctionCalls)
|
||||
@ -1514,7 +1513,7 @@ struct WeirdIter
|
||||
auto freePack = log.getMutable<FreeTypePack>(packId);
|
||||
|
||||
level = freePack->level;
|
||||
if (FFlag::LuauMaintainScopesInUnifier && freePack->scope != nullptr)
|
||||
if (freePack->scope != nullptr)
|
||||
scope = freePack->scope;
|
||||
log.replace(packId, BoundTypePack(newTail));
|
||||
packId = newTail;
|
||||
@ -1679,11 +1678,8 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
|
||||
auto superIter = WeirdIter(superTp, log);
|
||||
auto subIter = WeirdIter(subTp, log);
|
||||
|
||||
if (FFlag::LuauMaintainScopesInUnifier)
|
||||
{
|
||||
superIter.scope = scope.get();
|
||||
subIter.scope = scope.get();
|
||||
}
|
||||
superIter.scope = scope.get();
|
||||
subIter.scope = scope.get();
|
||||
|
||||
auto mkFreshType = [this](Scope* scope, TypeLevel level) {
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
|
@ -249,6 +249,7 @@ public:
|
||||
enum class ConstantNumberParseResult
|
||||
{
|
||||
Ok,
|
||||
Imprecise,
|
||||
Malformed,
|
||||
BinOverflow,
|
||||
HexOverflow,
|
||||
|
@ -1,7 +1,6 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
@ -9,7 +8,11 @@ struct Position
|
||||
{
|
||||
unsigned int line, column;
|
||||
|
||||
Position(unsigned int line, unsigned int column);
|
||||
Position(unsigned int line, unsigned int column)
|
||||
: line(line)
|
||||
, column(column)
|
||||
{
|
||||
}
|
||||
|
||||
bool operator==(const Position& rhs) const;
|
||||
bool operator!=(const Position& rhs) const;
|
||||
@ -25,10 +28,29 @@ struct Location
|
||||
{
|
||||
Position begin, end;
|
||||
|
||||
Location();
|
||||
Location(const Position& begin, const Position& end);
|
||||
Location(const Position& begin, unsigned int length);
|
||||
Location(const Location& begin, const Location& end);
|
||||
Location()
|
||||
: begin(0, 0)
|
||||
, end(0, 0)
|
||||
{
|
||||
}
|
||||
|
||||
Location(const Position& begin, const Position& end)
|
||||
: begin(begin)
|
||||
, end(end)
|
||||
{
|
||||
}
|
||||
|
||||
Location(const Position& begin, unsigned int length)
|
||||
: begin(begin)
|
||||
, end(begin.line, begin.column + length)
|
||||
{
|
||||
}
|
||||
|
||||
Location(const Location& begin, const Location& end)
|
||||
: begin(begin.begin)
|
||||
, end(end.end)
|
||||
{
|
||||
}
|
||||
|
||||
bool operator==(const Location& rhs) const;
|
||||
bool operator!=(const Location& rhs) const;
|
||||
|
@ -1,16 +1,9 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/Location.h"
|
||||
#include <string>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
Position::Position(unsigned int line, unsigned int column)
|
||||
: line(line)
|
||||
, column(column)
|
||||
{
|
||||
}
|
||||
|
||||
bool Position::operator==(const Position& rhs) const
|
||||
{
|
||||
return this->column == rhs.column && this->line == rhs.line;
|
||||
@ -61,30 +54,6 @@ void Position::shift(const Position& start, const Position& oldEnd, const Positi
|
||||
}
|
||||
}
|
||||
|
||||
Location::Location()
|
||||
: begin(0, 0)
|
||||
, end(0, 0)
|
||||
{
|
||||
}
|
||||
|
||||
Location::Location(const Position& begin, const Position& end)
|
||||
: begin(begin)
|
||||
, end(end)
|
||||
{
|
||||
}
|
||||
|
||||
Location::Location(const Position& begin, unsigned int length)
|
||||
: begin(begin)
|
||||
, end(begin.line, begin.column + length)
|
||||
{
|
||||
}
|
||||
|
||||
Location::Location(const Location& begin, const Location& end)
|
||||
: begin(begin.begin)
|
||||
, end(end.end)
|
||||
{
|
||||
}
|
||||
|
||||
bool Location::operator==(const Location& rhs) const
|
||||
{
|
||||
return this->begin == rhs.begin && this->end == rhs.end;
|
||||
|
@ -24,6 +24,8 @@ LUAU_FASTFLAG(LuauCheckedFunctionSyntax)
|
||||
LUAU_FASTFLAGVARIABLE(LuauBetterTypeUnionLimits, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauBetterTypeRecLimits, false)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauParseImpreciseNumber, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
@ -2187,6 +2189,12 @@ static ConstantNumberParseResult parseInteger(double& result, const char* data,
|
||||
return base == 2 ? ConstantNumberParseResult::BinOverflow : ConstantNumberParseResult::HexOverflow;
|
||||
}
|
||||
|
||||
if (FFlag::LuauParseImpreciseNumber)
|
||||
{
|
||||
if (value >= (1ull << 53) && static_cast<unsigned long long>(result) != value)
|
||||
return ConstantNumberParseResult::Imprecise;
|
||||
}
|
||||
|
||||
return ConstantNumberParseResult::Ok;
|
||||
}
|
||||
|
||||
@ -2203,8 +2211,32 @@ static ConstantNumberParseResult parseDouble(double& result, const char* data)
|
||||
char* end = nullptr;
|
||||
double value = strtod(data, &end);
|
||||
|
||||
result = value;
|
||||
return *end == 0 ? ConstantNumberParseResult::Ok : ConstantNumberParseResult::Malformed;
|
||||
if (FFlag::LuauParseImpreciseNumber)
|
||||
{
|
||||
// trailing non-numeric characters
|
||||
if (*end != 0)
|
||||
return ConstantNumberParseResult::Malformed;
|
||||
|
||||
result = value;
|
||||
|
||||
// for linting, we detect integer constants that are parsed imprecisely
|
||||
// since the check is expensive we only perform it when the number is larger than the precise integer range
|
||||
if (value >= double(1ull << 53) && strspn(data, "0123456789") == strlen(data))
|
||||
{
|
||||
char repr[512];
|
||||
snprintf(repr, sizeof(repr), "%.0f", value);
|
||||
|
||||
if (strcmp(repr, data) != 0)
|
||||
return ConstantNumberParseResult::Imprecise;
|
||||
}
|
||||
|
||||
return ConstantNumberParseResult::Ok;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = value;
|
||||
return *end == 0 ? ConstantNumberParseResult::Ok : ConstantNumberParseResult::Malformed;
|
||||
}
|
||||
}
|
||||
|
||||
// simpleexp -> NUMBER | STRING | NIL | true | false | ... | constructor | FUNCTION body | primaryexp
|
||||
|
@ -120,6 +120,7 @@ struct CompileStats
|
||||
{
|
||||
size_t lines;
|
||||
size_t bytecode;
|
||||
size_t bytecodeInstructionCount;
|
||||
size_t codegen;
|
||||
|
||||
double readTime;
|
||||
@ -136,6 +137,7 @@ struct CompileStats
|
||||
fprintf(fp, "{\
|
||||
\"lines\": %zu, \
|
||||
\"bytecode\": %zu, \
|
||||
\"bytecodeInstructionCount\": %zu, \
|
||||
\"codegen\": %zu, \
|
||||
\"readTime\": %f, \
|
||||
\"miscTime\": %f, \
|
||||
@ -153,16 +155,22 @@ struct CompileStats
|
||||
\"maxBlockInstructions\": %u, \
|
||||
\"regAllocErrors\": %d, \
|
||||
\"loweringErrors\": %d\
|
||||
}, \
|
||||
\"blockLinearizationStats\": {\
|
||||
\"constPropInstructionCount\": %u, \
|
||||
\"timeSeconds\": %f\
|
||||
}}",
|
||||
lines, bytecode, codegen, readTime, miscTime, parseTime, compileTime, codegenTime, lowerStats.totalFunctions, lowerStats.skippedFunctions,
|
||||
lowerStats.spillsToSlot, lowerStats.spillsToRestore, lowerStats.maxSpillSlotsUsed, lowerStats.blocksPreOpt, lowerStats.blocksPostOpt,
|
||||
lowerStats.maxBlockInstructions, lowerStats.regAllocErrors, lowerStats.loweringErrors);
|
||||
lines, bytecode, bytecodeInstructionCount, codegen, readTime, miscTime, parseTime, compileTime, codegenTime, lowerStats.totalFunctions,
|
||||
lowerStats.skippedFunctions, lowerStats.spillsToSlot, lowerStats.spillsToRestore, lowerStats.maxSpillSlotsUsed, lowerStats.blocksPreOpt,
|
||||
lowerStats.blocksPostOpt, lowerStats.maxBlockInstructions, lowerStats.regAllocErrors, lowerStats.loweringErrors,
|
||||
lowerStats.blockLinearizationStats.constPropInstructionCount, lowerStats.blockLinearizationStats.timeSeconds);
|
||||
}
|
||||
|
||||
CompileStats& operator+=(const CompileStats& that)
|
||||
{
|
||||
this->lines += that.lines;
|
||||
this->bytecode += that.bytecode;
|
||||
this->bytecodeInstructionCount += that.bytecodeInstructionCount;
|
||||
this->codegen += that.codegen;
|
||||
this->readTime += that.readTime;
|
||||
this->miscTime += that.miscTime;
|
||||
@ -257,6 +265,7 @@ static bool compileFile(const char* name, CompileFormat format, Luau::CodeGen::A
|
||||
|
||||
Luau::compileOrThrow(bcb, result, names, copts());
|
||||
stats.bytecode += bcb.getBytecode().size();
|
||||
stats.bytecodeInstructionCount = bcb.getTotalInstructionCount();
|
||||
stats.compileTime += recordDeltaTime(currts);
|
||||
|
||||
switch (format)
|
||||
@ -321,6 +330,30 @@ static int assertionHandler(const char* expr, const char* file, int line, const
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::string escapeFilename(const std::string& filename)
|
||||
{
|
||||
std::string escaped;
|
||||
escaped.reserve(filename.size());
|
||||
|
||||
for (const char ch : filename)
|
||||
{
|
||||
switch (ch)
|
||||
{
|
||||
case '\\':
|
||||
escaped.push_back('/');
|
||||
break;
|
||||
case '"':
|
||||
escaped.push_back('\\');
|
||||
escaped.push_back(ch);
|
||||
break;
|
||||
default:
|
||||
escaped.push_back(ch);
|
||||
}
|
||||
}
|
||||
|
||||
return escaped;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
Luau::assertHandler() = assertionHandler;
|
||||
@ -330,6 +363,7 @@ int main(int argc, char** argv)
|
||||
CompileFormat compileFormat = CompileFormat::Text;
|
||||
Luau::CodeGen::AssemblyOptions::Target assemblyTarget = Luau::CodeGen::AssemblyOptions::Host;
|
||||
RecordStats recordStats = RecordStats::None;
|
||||
std::string statsFile("stats.json");
|
||||
|
||||
for (int i = 1; i < argc; i++)
|
||||
{
|
||||
@ -394,6 +428,16 @@ int main(int argc, char** argv)
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
else if (strncmp(argv[i], "--stats-file=", 13) == 0)
|
||||
{
|
||||
statsFile = argv[i] + 13;
|
||||
|
||||
if (statsFile.size() == 0)
|
||||
{
|
||||
fprintf(stderr, "Error: filename missing for '--stats-file'.\n\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
else if (strncmp(argv[i], "--fflags=", 9) == 0)
|
||||
{
|
||||
setLuauFlags(argv[i] + 9);
|
||||
@ -463,7 +507,7 @@ int main(int argc, char** argv)
|
||||
if (recordStats != RecordStats::None)
|
||||
{
|
||||
|
||||
FILE* fp = fopen("stats.json", "w");
|
||||
FILE* fp = fopen(statsFile.c_str(), "w");
|
||||
|
||||
if (!fp)
|
||||
{
|
||||
@ -480,7 +524,8 @@ int main(int argc, char** argv)
|
||||
fprintf(fp, "{\n");
|
||||
for (size_t i = 0; i < fileCount; ++i)
|
||||
{
|
||||
fprintf(fp, "\"%s\": ", files[i].c_str());
|
||||
std::string escaped(escapeFilename(files[i]));
|
||||
fprintf(fp, "\"%s\": ", escaped.c_str());
|
||||
fileStats[i].serializeToJson(fp);
|
||||
fprintf(fp, i == (fileCount - 1) ? "\n" : ",\n");
|
||||
}
|
||||
|
@ -80,6 +80,27 @@ struct AssemblyOptions
|
||||
void* annotatorContext = nullptr;
|
||||
};
|
||||
|
||||
struct BlockLinearizationStats
|
||||
{
|
||||
unsigned int constPropInstructionCount = 0;
|
||||
double timeSeconds = 0.0;
|
||||
|
||||
BlockLinearizationStats& operator+=(const BlockLinearizationStats& that)
|
||||
{
|
||||
this->constPropInstructionCount += that.constPropInstructionCount;
|
||||
this->timeSeconds += that.timeSeconds;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
BlockLinearizationStats operator+(const BlockLinearizationStats& other) const
|
||||
{
|
||||
BlockLinearizationStats result(*this);
|
||||
result += other;
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
struct LoweringStats
|
||||
{
|
||||
unsigned totalFunctions = 0;
|
||||
@ -94,6 +115,8 @@ struct LoweringStats
|
||||
int regAllocErrors = 0;
|
||||
int loweringErrors = 0;
|
||||
|
||||
BlockLinearizationStats blockLinearizationStats;
|
||||
|
||||
LoweringStats operator+(const LoweringStats& other) const
|
||||
{
|
||||
LoweringStats result(*this);
|
||||
@ -113,6 +136,7 @@ struct LoweringStats
|
||||
this->maxBlockInstructions = std::max(this->maxBlockInstructions, that.maxBlockInstructions);
|
||||
this->regAllocErrors += that.regAllocErrors;
|
||||
this->loweringErrors += that.loweringErrors;
|
||||
this->blockLinearizationStats += that.blockLinearizationStats;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
@ -600,6 +600,10 @@ enum class IrCmd : uint8_t
|
||||
BITCOUNTLZ_UINT,
|
||||
BITCOUNTRZ_UINT,
|
||||
|
||||
// Swap byte order in A
|
||||
// A: int
|
||||
BYTESWAP_UINT,
|
||||
|
||||
// Calls native libm function with 1 or 2 arguments
|
||||
// A: builtin function ID
|
||||
// B: double
|
||||
|
@ -50,6 +50,13 @@ inline void gatherFunctions(std::vector<Proto*>& results, Proto* proto, unsigned
|
||||
gatherFunctions(results, proto->p[i], flags);
|
||||
}
|
||||
|
||||
inline unsigned getInstructionCount(const std::vector<IrInst>& instructions, IrCmd cmd)
|
||||
{
|
||||
return unsigned(std::count_if(instructions.begin(), instructions.end(), [&cmd](const IrInst& inst) {
|
||||
return inst.cmd == cmd;
|
||||
}));
|
||||
}
|
||||
|
||||
template<typename AssemblyBuilder, typename IrLowering>
|
||||
inline bool lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction& function, const std::vector<uint32_t>& sortedBlocks, int bytecodeid,
|
||||
AssemblyOptions options)
|
||||
@ -269,7 +276,25 @@ inline bool lowerFunction(IrBuilder& ir, AssemblyBuilder& build, ModuleHelpers&
|
||||
constPropInBlockChains(ir, useValueNumbering);
|
||||
|
||||
if (!FFlag::DebugCodegenOptSize)
|
||||
{
|
||||
double startTime = 0.0;
|
||||
unsigned constPropInstructionCount = 0;
|
||||
|
||||
if (stats)
|
||||
{
|
||||
constPropInstructionCount = getInstructionCount(ir.function.instructions, IrCmd::SUBSTITUTE);
|
||||
startTime = lua_clock();
|
||||
}
|
||||
|
||||
createLinearBlocks(ir, useValueNumbering);
|
||||
|
||||
if (stats)
|
||||
{
|
||||
stats->blockLinearizationStats.timeSeconds += lua_clock() - startTime;
|
||||
constPropInstructionCount = getInstructionCount(ir.function.instructions, IrCmd::SUBSTITUTE) - constPropInstructionCount;
|
||||
stats->blockLinearizationStats.constPropInstructionCount += constPropInstructionCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<uint32_t> sortedBlocks = getSortedBlockOrder(ir.function);
|
||||
|
@ -531,50 +531,6 @@ const Instruction* executeSETTABLEKS(lua_State* L, const Instruction* pc, StkId
|
||||
}
|
||||
}
|
||||
|
||||
const Instruction* executeNEWCLOSURE(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
|
||||
Proto* pv = cl->l.p->p[LUAU_INSN_D(insn)];
|
||||
LUAU_ASSERT(unsigned(LUAU_INSN_D(insn)) < unsigned(cl->l.p->sizep));
|
||||
|
||||
VM_PROTECT_PC(); // luaF_newLclosure may fail due to OOM
|
||||
|
||||
// note: we save closure to stack early in case the code below wants to capture it by value
|
||||
Closure* ncl = luaF_newLclosure(L, pv->nups, cl->env, pv);
|
||||
setclvalue(L, ra, ncl);
|
||||
|
||||
for (int ui = 0; ui < pv->nups; ++ui)
|
||||
{
|
||||
Instruction uinsn = *pc++;
|
||||
LUAU_ASSERT(LUAU_INSN_OP(uinsn) == LOP_CAPTURE);
|
||||
|
||||
switch (LUAU_INSN_A(uinsn))
|
||||
{
|
||||
case LCT_VAL:
|
||||
setobj(L, &ncl->l.uprefs[ui], VM_REG(LUAU_INSN_B(uinsn)));
|
||||
break;
|
||||
|
||||
case LCT_REF:
|
||||
setupvalue(L, &ncl->l.uprefs[ui], luaF_findupval(L, VM_REG(LUAU_INSN_B(uinsn))));
|
||||
break;
|
||||
|
||||
case LCT_UPVAL:
|
||||
setobj(L, &ncl->l.uprefs[ui], VM_UV(LUAU_INSN_B(uinsn)));
|
||||
break;
|
||||
|
||||
default:
|
||||
LUAU_ASSERT(!"Unknown upvalue capture type");
|
||||
LUAU_UNREACHABLE(); // improves switch() codegen by eliding opcode bounds checks
|
||||
}
|
||||
}
|
||||
|
||||
VM_PROTECT(luaC_checkGC(L));
|
||||
return pc;
|
||||
}
|
||||
|
||||
const Instruction* executeNAMECALL(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
@ -587,43 +543,19 @@ const Instruction* executeNAMECALL(lua_State* L, const Instruction* pc, StkId ba
|
||||
|
||||
if (ttistable(rb))
|
||||
{
|
||||
Table* h = hvalue(rb);
|
||||
// note: we can't use nodemask8 here because we need to query the main position of the table, and 8-bit nodemask8 only works
|
||||
// for predictive lookups
|
||||
LuaNode* n = &h->node[tsvalue(kv)->hash & (sizenode(h) - 1)];
|
||||
// note: lvmexecute.cpp version of NAMECALL has two fast paths, but both fast paths are inlined into IR
|
||||
// as such, if we get here we can just use the generic path which makes the fallback path a little faster
|
||||
|
||||
const TValue* mt = 0;
|
||||
const LuaNode* mtn = 0;
|
||||
|
||||
// fast-path: key is in the table in expected slot
|
||||
if (ttisstring(gkey(n)) && tsvalue(gkey(n)) == tsvalue(kv) && !ttisnil(gval(n)))
|
||||
{
|
||||
// note: order of copies allows rb to alias ra+1 or ra
|
||||
setobj2s(L, ra + 1, rb);
|
||||
setobj2s(L, ra, gval(n));
|
||||
}
|
||||
// fast-path: key is absent from the base, table has an __index table, and it has the result in the expected slot
|
||||
else if (gnext(n) == 0 && (mt = fasttm(L, hvalue(rb)->metatable, TM_INDEX)) && ttistable(mt) &&
|
||||
(mtn = &hvalue(mt)->node[LUAU_INSN_C(insn) & hvalue(mt)->nodemask8]) && ttisstring(gkey(mtn)) && tsvalue(gkey(mtn)) == tsvalue(kv) &&
|
||||
!ttisnil(gval(mtn)))
|
||||
{
|
||||
// note: order of copies allows rb to alias ra+1 or ra
|
||||
setobj2s(L, ra + 1, rb);
|
||||
setobj2s(L, ra, gval(mtn));
|
||||
}
|
||||
else
|
||||
{
|
||||
// slow-path: handles full table lookup
|
||||
setobj2s(L, ra + 1, rb);
|
||||
L->cachedslot = LUAU_INSN_C(insn);
|
||||
VM_PROTECT(luaV_gettable(L, rb, kv, ra));
|
||||
// save cachedslot to accelerate future lookups; patches currently executing instruction since pc-2 rolls back two pc++
|
||||
VM_PATCH_C(pc - 2, L->cachedslot);
|
||||
// recompute ra since stack might have been reallocated
|
||||
ra = VM_REG(LUAU_INSN_A(insn));
|
||||
if (ttisnil(ra))
|
||||
luaG_methoderror(L, ra + 1, tsvalue(kv));
|
||||
}
|
||||
// slow-path: handles full table lookup
|
||||
setobj2s(L, ra + 1, rb);
|
||||
L->cachedslot = LUAU_INSN_C(insn);
|
||||
VM_PROTECT(luaV_gettable(L, rb, kv, ra));
|
||||
// save cachedslot to accelerate future lookups; patches currently executing instruction since pc-2 rolls back two pc++
|
||||
VM_PATCH_C(pc - 2, L->cachedslot);
|
||||
// recompute ra since stack might have been reallocated
|
||||
ra = VM_REG(LUAU_INSN_A(insn));
|
||||
if (ttisnil(ra))
|
||||
luaG_methoderror(L, ra + 1, tsvalue(kv));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -25,7 +25,6 @@ const Instruction* executeGETGLOBAL(lua_State* L, const Instruction* pc, StkId b
|
||||
const Instruction* executeSETGLOBAL(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* executeGETTABLEKS(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* executeSETTABLEKS(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* executeNEWCLOSURE(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* executeNAMECALL(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* executeSETLIST(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* executeFORGPREP(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
|
@ -309,6 +309,8 @@ const char* getCmdName(IrCmd cmd)
|
||||
return "BITCOUNTLZ_UINT";
|
||||
case IrCmd::BITCOUNTRZ_UINT:
|
||||
return "BITCOUNTRZ_UINT";
|
||||
case IrCmd::BYTESWAP_UINT:
|
||||
return "BYTESWAP_UINT";
|
||||
case IrCmd::INVOKE_LIBM:
|
||||
return "INVOKE_LIBM";
|
||||
case IrCmd::GET_TYPE:
|
||||
|
@ -1912,6 +1912,13 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next)
|
||||
build.clz(inst.regA64, inst.regA64);
|
||||
break;
|
||||
}
|
||||
case IrCmd::BYTESWAP_UINT:
|
||||
{
|
||||
inst.regA64 = regs.allocReuse(KindA64::w, index, {inst.a});
|
||||
RegisterA64 temp = tempUint(inst.a);
|
||||
build.rev(inst.regA64, temp);
|
||||
break;
|
||||
}
|
||||
case IrCmd::INVOKE_LIBM:
|
||||
{
|
||||
if (inst.c.kind != IrOpKind::None)
|
||||
|
@ -822,7 +822,19 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next)
|
||||
case IrCmd::UINT_TO_NUM:
|
||||
inst.regX64 = regs.allocReg(SizeX64::xmmword, index);
|
||||
|
||||
build.vcvtsi2sd(inst.regX64, inst.regX64, qwordReg(regOp(inst.a)));
|
||||
// AVX has no uint->double conversion; the source must come from UINT op and they all should clear top 32 bits so we can usually
|
||||
// use 64-bit reg; the one exception is NUM_TO_UINT which doesn't clear top bits
|
||||
if (IrCmd source = function.instOp(inst.a).cmd; source == IrCmd::NUM_TO_UINT)
|
||||
{
|
||||
ScopedRegX64 tmp{regs, SizeX64::dword};
|
||||
build.mov(tmp.reg, regOp(inst.a));
|
||||
build.vcvtsi2sd(inst.regX64, inst.regX64, qwordReg(tmp.reg));
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_ASSERT(source != IrCmd::SUBSTITUTE); // we don't process substitutions
|
||||
build.vcvtsi2sd(inst.regX64, inst.regX64, qwordReg(regOp(inst.a)));
|
||||
}
|
||||
break;
|
||||
case IrCmd::NUM_TO_INT:
|
||||
inst.regX64 = regs.allocReg(SizeX64::dword, index);
|
||||
@ -1633,6 +1645,16 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next)
|
||||
build.setLabel(exit);
|
||||
break;
|
||||
}
|
||||
case IrCmd::BYTESWAP_UINT:
|
||||
{
|
||||
inst.regX64 = regs.allocRegOrReuse(SizeX64::dword, index, {inst.a});
|
||||
|
||||
if (inst.a.kind != IrOpKind::Inst || inst.regX64 != regOp(inst.a))
|
||||
build.mov(inst.regX64, memRegUintOp(inst.a));
|
||||
|
||||
build.bswap(inst.regX64);
|
||||
break;
|
||||
}
|
||||
case IrCmd::INVOKE_LIBM:
|
||||
{
|
||||
IrCallWrapperX64 callWrap(regs, build, index);
|
||||
|
@ -583,8 +583,8 @@ static BuiltinImplResult translateBuiltinBit32ExtractK(
|
||||
return {BuiltinImplType::Full, 1};
|
||||
}
|
||||
|
||||
static BuiltinImplResult translateBuiltinBit32Countz(
|
||||
IrBuilder& build, LuauBuiltinFunction bfid, int nparams, int ra, int arg, IrOp args, int nresults, int pcpos)
|
||||
static BuiltinImplResult translateBuiltinBit32Unary(
|
||||
IrBuilder& build, IrCmd cmd, int nparams, int ra, int arg, IrOp args, int nresults, int pcpos)
|
||||
{
|
||||
if (nparams < 1 || nresults > 1)
|
||||
return {BuiltinImplType::None, -1};
|
||||
@ -594,7 +594,6 @@ static BuiltinImplResult translateBuiltinBit32Countz(
|
||||
|
||||
IrOp vaui = build.inst(IrCmd::NUM_TO_UINT, va);
|
||||
|
||||
IrCmd cmd = (bfid == LBF_BIT32_COUNTLZ) ? IrCmd::BITCOUNTLZ_UINT : IrCmd::BITCOUNTRZ_UINT;
|
||||
IrOp bin = build.inst(cmd, vaui);
|
||||
|
||||
IrOp value = build.inst(IrCmd::UINT_TO_NUM, bin);
|
||||
@ -816,8 +815,9 @@ BuiltinImplResult translateBuiltin(IrBuilder& build, int bfid, int ra, int arg,
|
||||
case LBF_BIT32_EXTRACTK:
|
||||
return translateBuiltinBit32ExtractK(build, LuauBuiltinFunction(bfid), nparams, ra, arg, args, nresults, pcpos);
|
||||
case LBF_BIT32_COUNTLZ:
|
||||
return translateBuiltinBit32Unary(build, IrCmd::BITCOUNTLZ_UINT, nparams, ra, arg, args, nresults, pcpos);
|
||||
case LBF_BIT32_COUNTRZ:
|
||||
return translateBuiltinBit32Countz(build, LuauBuiltinFunction(bfid), nparams, ra, arg, args, nresults, pcpos);
|
||||
return translateBuiltinBit32Unary(build, IrCmd::BITCOUNTRZ_UINT, nparams, ra, arg, args, nresults, pcpos);
|
||||
case LBF_BIT32_REPLACE:
|
||||
return translateBuiltinBit32Replace(build, LuauBuiltinFunction(bfid), nparams, ra, arg, args, nresults, fallback, pcpos);
|
||||
case LBF_TYPE:
|
||||
@ -830,6 +830,8 @@ BuiltinImplResult translateBuiltin(IrBuilder& build, int bfid, int ra, int arg,
|
||||
return translateBuiltinTableInsert(build, nparams, ra, arg, args, nresults, pcpos);
|
||||
case LBF_STRING_LEN:
|
||||
return translateBuiltinStringLen(build, nparams, ra, arg, args, nresults, pcpos);
|
||||
case LBF_BIT32_BYTESWAP:
|
||||
return translateBuiltinBit32Unary(build, IrCmd::BYTESWAP_UINT, nparams, ra, arg, args, nresults, pcpos);
|
||||
default:
|
||||
return {BuiltinImplType::None, -1};
|
||||
}
|
||||
|
@ -163,6 +163,7 @@ IrValueKind getCmdValueKind(IrCmd cmd)
|
||||
case IrCmd::BITRROTATE_UINT:
|
||||
case IrCmd::BITCOUNTLZ_UINT:
|
||||
case IrCmd::BITCOUNTRZ_UINT:
|
||||
case IrCmd::BYTESWAP_UINT:
|
||||
return IrValueKind::Int;
|
||||
case IrCmd::INVOKE_LIBM:
|
||||
return IrValueKind::Double;
|
||||
|
@ -103,7 +103,6 @@ void initFunctions(NativeState& data)
|
||||
data.context.executeGETTABLEKS = executeGETTABLEKS;
|
||||
data.context.executeSETTABLEKS = executeSETTABLEKS;
|
||||
|
||||
data.context.executeNEWCLOSURE = executeNEWCLOSURE;
|
||||
data.context.executeNAMECALL = executeNAMECALL;
|
||||
data.context.executeFORGPREP = executeFORGPREP;
|
||||
data.context.executeGETVARARGSMultRet = executeGETVARARGSMultRet;
|
||||
|
@ -94,7 +94,6 @@ struct NativeContext
|
||||
const Instruction* (*executeSETGLOBAL)(lua_State* L, const Instruction* pc, StkId base, TValue* k) = nullptr;
|
||||
const Instruction* (*executeGETTABLEKS)(lua_State* L, const Instruction* pc, StkId base, TValue* k) = nullptr;
|
||||
const Instruction* (*executeSETTABLEKS)(lua_State* L, const Instruction* pc, StkId base, TValue* k) = nullptr;
|
||||
const Instruction* (*executeNEWCLOSURE)(lua_State* L, const Instruction* pc, StkId base, TValue* k) = nullptr;
|
||||
const Instruction* (*executeNAMECALL)(lua_State* L, const Instruction* pc, StkId base, TValue* k) = nullptr;
|
||||
const Instruction* (*executeSETLIST)(lua_State* L, const Instruction* pc, StkId base, TValue* k) = nullptr;
|
||||
const Instruction* (*executeFORGPREP)(lua_State* L, const Instruction* pc, StkId base, TValue* k) = nullptr;
|
||||
|
@ -1168,6 +1168,7 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
|
||||
case IrCmd::BITLROTATE_UINT:
|
||||
case IrCmd::BITCOUNTLZ_UINT:
|
||||
case IrCmd::BITCOUNTRZ_UINT:
|
||||
case IrCmd::BYTESWAP_UINT:
|
||||
case IrCmd::INVOKE_LIBM:
|
||||
case IrCmd::GET_TYPE:
|
||||
case IrCmd::GET_TYPEOF:
|
||||
|
@ -83,6 +83,7 @@ public:
|
||||
void pushDebugUpval(StringRef name);
|
||||
|
||||
size_t getInstructionCount() const;
|
||||
size_t getTotalInstructionCount() const;
|
||||
uint32_t getDebugPC() const;
|
||||
|
||||
void addDebugRemark(const char* format, ...) LUAU_PRINTF_ATTR(2, 3);
|
||||
@ -232,6 +233,7 @@ private:
|
||||
uint32_t currentFunction = ~0u;
|
||||
uint32_t mainFunction = ~0u;
|
||||
|
||||
size_t totalInstructionCount = 0;
|
||||
std::vector<uint32_t> insns;
|
||||
std::vector<int> lines;
|
||||
std::vector<Constant> constants;
|
||||
|
@ -244,6 +244,7 @@ void BytecodeBuilder::endFunction(uint8_t maxstacksize, uint8_t numupvalues, uin
|
||||
|
||||
currentFunction = ~0u;
|
||||
|
||||
totalInstructionCount += insns.size();
|
||||
insns.clear();
|
||||
lines.clear();
|
||||
constants.clear();
|
||||
@ -539,6 +540,11 @@ size_t BytecodeBuilder::getInstructionCount() const
|
||||
return insns.size();
|
||||
}
|
||||
|
||||
size_t BytecodeBuilder::getTotalInstructionCount() const
|
||||
{
|
||||
return totalInstructionCount;
|
||||
}
|
||||
|
||||
uint32_t BytecodeBuilder::getDebugPC() const
|
||||
{
|
||||
return uint32_t(insns.size());
|
||||
|
@ -27,7 +27,6 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineThresholdMaxBoost, 300)
|
||||
LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5)
|
||||
|
||||
LUAU_FASTFLAG(LuauFloorDivision)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCompileFixContinueValidation2, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCompileIfElseAndOr, false)
|
||||
|
||||
namespace Luau
|
||||
@ -2519,14 +2518,9 @@ struct Compiler
|
||||
// Optimization: body is a "continue" statement with no "else" => we can directly continue in "then" case
|
||||
if (!stat->elsebody && continueStatement != nullptr && !areLocalsCaptured(loops.back().localOffsetContinue))
|
||||
{
|
||||
if (FFlag::LuauCompileFixContinueValidation2)
|
||||
{
|
||||
// track continue statement for repeat..until validation (validateContinueUntil)
|
||||
if (!loops.back().continueUsed)
|
||||
loops.back().continueUsed = continueStatement;
|
||||
}
|
||||
else if (loops.back().untilCondition)
|
||||
validateContinueUntil(continueStatement, loops.back().untilCondition);
|
||||
// track continue statement for repeat..until validation (validateContinueUntil)
|
||||
if (!loops.back().continueUsed)
|
||||
loops.back().continueUsed = continueStatement;
|
||||
|
||||
// fallthrough = proceed with the loop body as usual
|
||||
std::vector<size_t> elseJump;
|
||||
@ -2587,7 +2581,7 @@ struct Compiler
|
||||
size_t oldJumps = loopJumps.size();
|
||||
size_t oldLocals = localStack.size();
|
||||
|
||||
loops.push_back({oldLocals, oldLocals, nullptr, nullptr});
|
||||
loops.push_back({oldLocals, oldLocals, nullptr});
|
||||
hasLoops = true;
|
||||
|
||||
size_t loopLabel = bytecode.emitLabel();
|
||||
@ -2623,7 +2617,7 @@ struct Compiler
|
||||
size_t oldJumps = loopJumps.size();
|
||||
size_t oldLocals = localStack.size();
|
||||
|
||||
loops.push_back({oldLocals, oldLocals, stat->condition, nullptr});
|
||||
loops.push_back({oldLocals, oldLocals, nullptr});
|
||||
hasLoops = true;
|
||||
|
||||
size_t loopLabel = bytecode.emitLabel();
|
||||
@ -2648,7 +2642,7 @@ struct Compiler
|
||||
|
||||
// if continue was called from this statement, then any local defined after this in the loop body should not be accessed by until condition
|
||||
// it is sufficient to check this condition once, as if this holds for the first continue, it must hold for all subsequent continues.
|
||||
if (FFlag::LuauCompileFixContinueValidation2 && loops.back().continueUsed && !continueValidated)
|
||||
if (loops.back().continueUsed && !continueValidated)
|
||||
{
|
||||
validateContinueUntil(loops.back().continueUsed, stat->condition, body, i + 1);
|
||||
continueValidated = true;
|
||||
@ -2870,7 +2864,7 @@ struct Compiler
|
||||
size_t oldLocals = localStack.size();
|
||||
size_t oldJumps = loopJumps.size();
|
||||
|
||||
loops.push_back({oldLocals, oldLocals, nullptr, nullptr});
|
||||
loops.push_back({oldLocals, oldLocals, nullptr});
|
||||
|
||||
for (int iv = 0; iv < tripCount; ++iv)
|
||||
{
|
||||
@ -2921,7 +2915,7 @@ struct Compiler
|
||||
size_t oldLocals = localStack.size();
|
||||
size_t oldJumps = loopJumps.size();
|
||||
|
||||
loops.push_back({oldLocals, oldLocals, nullptr, nullptr});
|
||||
loops.push_back({oldLocals, oldLocals, nullptr});
|
||||
hasLoops = true;
|
||||
|
||||
// register layout: limit, step, index
|
||||
@ -2986,7 +2980,7 @@ struct Compiler
|
||||
size_t oldLocals = localStack.size();
|
||||
size_t oldJumps = loopJumps.size();
|
||||
|
||||
loops.push_back({oldLocals, oldLocals, nullptr, nullptr});
|
||||
loops.push_back({oldLocals, oldLocals, nullptr});
|
||||
hasLoops = true;
|
||||
|
||||
// register layout: generator, state, index, variables...
|
||||
@ -3398,14 +3392,9 @@ struct Compiler
|
||||
{
|
||||
LUAU_ASSERT(!loops.empty());
|
||||
|
||||
if (FFlag::LuauCompileFixContinueValidation2)
|
||||
{
|
||||
// track continue statement for repeat..until validation (validateContinueUntil)
|
||||
if (!loops.back().continueUsed)
|
||||
loops.back().continueUsed = stat;
|
||||
}
|
||||
else if (loops.back().untilCondition)
|
||||
validateContinueUntil(stat, loops.back().untilCondition);
|
||||
// track continue statement for repeat..until validation (validateContinueUntil)
|
||||
if (!loops.back().continueUsed)
|
||||
loops.back().continueUsed = stat;
|
||||
|
||||
// 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
|
||||
@ -3488,21 +3477,8 @@ struct Compiler
|
||||
}
|
||||
}
|
||||
|
||||
void validateContinueUntil(AstStat* cont, AstExpr* condition)
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::LuauCompileFixContinueValidation2);
|
||||
UndefinedLocalVisitor visitor(this);
|
||||
condition->visit(&visitor);
|
||||
|
||||
if (visitor.undef)
|
||||
CompileError::raise(condition->location,
|
||||
"Local %s used in the repeat..until condition is undefined because continue statement on line %d jumps over it",
|
||||
visitor.undef->name.value, cont->location.begin.line + 1);
|
||||
}
|
||||
|
||||
void validateContinueUntil(AstStat* cont, AstExpr* condition, AstStatBlock* body, size_t start)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauCompileFixContinueValidation2);
|
||||
UndefinedLocalVisitor visitor(this);
|
||||
|
||||
for (size_t i = start; i < body->body.size; ++i)
|
||||
@ -3748,18 +3724,8 @@ struct Compiler
|
||||
|
||||
void check(AstLocal* local)
|
||||
{
|
||||
if (FFlag::LuauCompileFixContinueValidation2)
|
||||
{
|
||||
if (!undef && locals.contains(local))
|
||||
undef = local;
|
||||
}
|
||||
else
|
||||
{
|
||||
Local& l = self->locals[local];
|
||||
|
||||
if (!l.allocated && !undef)
|
||||
undef = local;
|
||||
}
|
||||
if (!undef && locals.contains(local))
|
||||
undef = local;
|
||||
}
|
||||
|
||||
bool visit(AstExprLocal* node) override
|
||||
@ -3904,9 +3870,6 @@ struct Compiler
|
||||
size_t localOffset;
|
||||
size_t localOffsetContinue;
|
||||
|
||||
// TODO: Remove with LuauCompileFixContinueValidation2
|
||||
AstExpr* untilCondition;
|
||||
|
||||
AstStatContinue* continueUsed;
|
||||
};
|
||||
|
||||
|
@ -156,7 +156,7 @@ target_sources(Luau.Analysis PRIVATE
|
||||
Analysis/include/Luau/Cancellation.h
|
||||
Analysis/include/Luau/Clone.h
|
||||
Analysis/include/Luau/Constraint.h
|
||||
Analysis/include/Luau/ConstraintGraphBuilder.h
|
||||
Analysis/include/Luau/ConstraintGenerator.h
|
||||
Analysis/include/Luau/ConstraintSolver.h
|
||||
Analysis/include/Luau/ControlFlow.h
|
||||
Analysis/include/Luau/DataFlowGraph.h
|
||||
@ -223,7 +223,7 @@ target_sources(Luau.Analysis PRIVATE
|
||||
Analysis/src/BuiltinDefinitions.cpp
|
||||
Analysis/src/Clone.cpp
|
||||
Analysis/src/Constraint.cpp
|
||||
Analysis/src/ConstraintGraphBuilder.cpp
|
||||
Analysis/src/ConstraintGenerator.cpp
|
||||
Analysis/src/ConstraintSolver.cpp
|
||||
Analysis/src/DataFlowGraph.cpp
|
||||
Analysis/src/DcrLogger.cpp
|
||||
@ -385,8 +385,8 @@ if(TARGET Luau.UnitTest)
|
||||
tests/CodeAllocator.test.cpp
|
||||
tests/Compiler.test.cpp
|
||||
tests/Config.test.cpp
|
||||
tests/ConstraintGraphBuilderFixture.cpp
|
||||
tests/ConstraintGraphBuilderFixture.h
|
||||
tests/ConstraintGeneratorFixture.cpp
|
||||
tests/ConstraintGeneratorFixture.h
|
||||
tests/ConstraintSolver.test.cpp
|
||||
tests/CostModel.test.cpp
|
||||
tests/DataFlowGraph.test.cpp
|
||||
|
@ -1353,7 +1353,7 @@ static int luauF_readinteger(lua_State* L, StkId res, TValue* arg0, int nresults
|
||||
return -1;
|
||||
|
||||
T val;
|
||||
memcpy(&val, (char*)bufvalue(arg0)->data + offset, sizeof(T));
|
||||
memcpy(&val, (char*)bufvalue(arg0)->data + unsigned(offset), sizeof(T));
|
||||
setnvalue(res, double(val));
|
||||
return 1;
|
||||
}
|
||||
@ -1378,7 +1378,7 @@ static int luauF_writeinteger(lua_State* L, StkId res, TValue* arg0, int nresult
|
||||
luai_num2unsigned(value, incoming);
|
||||
|
||||
T val = T(value);
|
||||
memcpy((char*)bufvalue(arg0)->data + offset, &val, sizeof(T));
|
||||
memcpy((char*)bufvalue(arg0)->data + unsigned(offset), &val, sizeof(T));
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
@ -1398,7 +1398,12 @@ static int luauF_readfp(lua_State* L, StkId res, TValue* arg0, int nresults, Stk
|
||||
return -1;
|
||||
|
||||
T val;
|
||||
memcpy(&val, (char*)bufvalue(arg0)->data + offset, sizeof(T));
|
||||
#ifdef _MSC_VER
|
||||
// avoid memcpy path on MSVC because it results in integer stack copy + floating-point ops on stack
|
||||
val = *(T*)((char*)bufvalue(arg0)->data + unsigned(offset));
|
||||
#else
|
||||
memcpy(&val, (char*)bufvalue(arg0)->data + unsigned(offset), sizeof(T));
|
||||
#endif
|
||||
setnvalue(res, double(val));
|
||||
return 1;
|
||||
}
|
||||
@ -1419,7 +1424,12 @@ static int luauF_writefp(lua_State* L, StkId res, TValue* arg0, int nresults, St
|
||||
return -1;
|
||||
|
||||
T val = T(nvalue(args + 1));
|
||||
memcpy((char*)bufvalue(arg0)->data + offset, &val, sizeof(T));
|
||||
#ifdef _MSC_VER
|
||||
// avoid memcpy path on MSVC because it results in integer stack copy + floating-point ops on stack
|
||||
*(T*)((char*)bufvalue(arg0)->data + unsigned(offset)) = val;
|
||||
#else
|
||||
memcpy((char*)bufvalue(arg0)->data + unsigned(offset), &val, sizeof(T));
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
@ -17,8 +17,6 @@
|
||||
|
||||
#include <string.h>
|
||||
|
||||
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauHandlerClose, false)
|
||||
|
||||
/*
|
||||
** {======================================================
|
||||
** Error-recovery functions
|
||||
@ -409,7 +407,7 @@ static void resume_handle(lua_State* L, void* ud)
|
||||
L->ci = restoreci(L, old_ci);
|
||||
|
||||
// close eventual pending closures; this means it's now safe to restore stack
|
||||
luaF_close(L, DFFlag::LuauHandlerClose ? L->ci->base : L->base);
|
||||
luaF_close(L, L->ci->base);
|
||||
|
||||
// finish cont call and restore stack to previous ci top
|
||||
luau_poscall(L, L->top - n);
|
||||
|
@ -132,7 +132,8 @@ function test()
|
||||
local ts0 = os.clock()
|
||||
|
||||
for i = 1, 100 do
|
||||
sha256(input)
|
||||
local res = sha256(input)
|
||||
assert(res == "45849646c50337988ccc877d23fcc0de50d1df7490fdc3b9333aed0de8ab492a")
|
||||
end
|
||||
|
||||
local ts1 = os.clock()
|
||||
|
@ -1909,8 +1909,6 @@ RETURN R0 0
|
||||
|
||||
TEST_CASE("LoopContinueIgnoresImplicitConstant")
|
||||
{
|
||||
ScopedFastFlag luauCompileFixContinueValidation{"LuauCompileFixContinueValidation2", true};
|
||||
|
||||
// this used to crash the compiler :(
|
||||
CHECK_EQ("\n" + compileFunction0(R"(
|
||||
local _
|
||||
@ -1926,8 +1924,6 @@ RETURN R0 0
|
||||
|
||||
TEST_CASE("LoopContinueIgnoresExplicitConstant")
|
||||
{
|
||||
ScopedFastFlag luauCompileFixContinueValidation{"LuauCompileFixContinueValidation2", 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
|
||||
@ -1943,8 +1939,6 @@ RETURN R0 0
|
||||
|
||||
TEST_CASE("LoopContinueRespectsExplicitConstant")
|
||||
{
|
||||
ScopedFastFlag luauCompileFixContinueValidation{"LuauCompileFixContinueValidation2", 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
|
||||
{
|
||||
@ -1969,8 +1963,6 @@ until c
|
||||
|
||||
TEST_CASE("LoopContinueIgnoresImplicitConstantAfterInline")
|
||||
{
|
||||
ScopedFastFlag luauCompileFixContinueValidation{"LuauCompileFixContinueValidation2", true};
|
||||
|
||||
// Inlining might also replace some locals with constants instead of allocating them
|
||||
CHECK_EQ("\n" + compileFunction(R"(
|
||||
local function inline(f)
|
||||
@ -1994,7 +1986,6 @@ RETURN R0 0
|
||||
|
||||
TEST_CASE("LoopContinueCorrectlyHandlesImplicitConstantAfterUnroll")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauCompileFixContinueValidation2", true};
|
||||
ScopedFastInt sfi("LuauCompileLoopUnrollThreshold", 200);
|
||||
|
||||
// access to implicit constant that depends on the unrolled loop constant is still invalid even though we can constant-propagate it
|
||||
|
@ -435,8 +435,6 @@ static int cxxthrow(lua_State* L)
|
||||
|
||||
TEST_CASE("PCall")
|
||||
{
|
||||
ScopedFastFlag sff("LuauHandlerClose", true);
|
||||
|
||||
runConformance(
|
||||
"pcall.lua",
|
||||
[](lua_State* L) {
|
||||
|
@ -1,10 +1,10 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "ConstraintGraphBuilderFixture.h"
|
||||
#include "ConstraintGeneratorFixture.h"
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
ConstraintGraphBuilderFixture::ConstraintGraphBuilderFixture()
|
||||
ConstraintGeneratorFixture::ConstraintGeneratorFixture()
|
||||
: Fixture()
|
||||
, mainModule(new Module)
|
||||
, forceTheFlag{"DebugLuauDeferredConstraintResolution", true}
|
||||
@ -15,18 +15,18 @@ ConstraintGraphBuilderFixture::ConstraintGraphBuilderFixture()
|
||||
BlockedTypePack::nextIndex = 0;
|
||||
}
|
||||
|
||||
void ConstraintGraphBuilderFixture::generateConstraints(const std::string& code)
|
||||
void ConstraintGeneratorFixture::generateConstraints(const std::string& code)
|
||||
{
|
||||
AstStatBlock* root = parse(code);
|
||||
dfg = std::make_unique<DataFlowGraph>(DataFlowGraphBuilder::build(root, NotNull{&ice}));
|
||||
cgb = std::make_unique<ConstraintGraphBuilder>(mainModule, NotNull{&normalizer}, NotNull(&moduleResolver), builtinTypes, NotNull(&ice),
|
||||
cg = std::make_unique<ConstraintGenerator>(mainModule, NotNull{&normalizer}, NotNull(&moduleResolver), builtinTypes, NotNull(&ice),
|
||||
frontend.globals.globalScope, /*prepareModuleScope*/ nullptr, &logger, NotNull{dfg.get()}, std::vector<RequireCycle>());
|
||||
cgb->visitModuleRoot(root);
|
||||
rootScope = cgb->rootScope;
|
||||
constraints = Luau::borrowConstraints(cgb->constraints);
|
||||
cg->visitModuleRoot(root);
|
||||
rootScope = cg->rootScope;
|
||||
constraints = Luau::borrowConstraints(cg->constraints);
|
||||
}
|
||||
|
||||
void ConstraintGraphBuilderFixture::solve(const std::string& code)
|
||||
void ConstraintGeneratorFixture::solve(const std::string& code)
|
||||
{
|
||||
generateConstraints(code);
|
||||
ConstraintSolver cs{NotNull{&normalizer}, NotNull{rootScope}, constraints, "MainModule", NotNull(&moduleResolver), {}, &logger, {}};
|
@ -1,7 +1,7 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/ConstraintGraphBuilder.h"
|
||||
#include "Luau/ConstraintGenerator.h"
|
||||
#include "Luau/ConstraintSolver.h"
|
||||
#include "Luau/DcrLogger.h"
|
||||
#include "Luau/TypeArena.h"
|
||||
@ -13,7 +13,7 @@
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct ConstraintGraphBuilderFixture : Fixture
|
||||
struct ConstraintGeneratorFixture : Fixture
|
||||
{
|
||||
TypeArena arena;
|
||||
ModulePtr mainModule;
|
||||
@ -22,14 +22,14 @@ struct ConstraintGraphBuilderFixture : Fixture
|
||||
Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}};
|
||||
|
||||
std::unique_ptr<DataFlowGraph> dfg;
|
||||
std::unique_ptr<ConstraintGraphBuilder> cgb;
|
||||
std::unique_ptr<ConstraintGenerator> cg;
|
||||
Scope* rootScope = nullptr;
|
||||
|
||||
std::vector<NotNull<Constraint>> constraints;
|
||||
|
||||
ScopedFastFlag forceTheFlag;
|
||||
|
||||
ConstraintGraphBuilderFixture();
|
||||
ConstraintGeneratorFixture();
|
||||
|
||||
void generateConstraints(const std::string& code);
|
||||
void solve(const std::string& code);
|
@ -1,6 +1,6 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
|
||||
#include "ConstraintGraphBuilderFixture.h"
|
||||
#include "ConstraintGeneratorFixture.h"
|
||||
#include "Fixture.h"
|
||||
#include "doctest.h"
|
||||
|
||||
@ -17,7 +17,7 @@ static TypeId requireBinding(Scope* scope, const char* name)
|
||||
|
||||
TEST_SUITE_BEGIN("ConstraintSolver");
|
||||
|
||||
TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "hello")
|
||||
TEST_CASE_FIXTURE(ConstraintGeneratorFixture, "hello")
|
||||
{
|
||||
solve(R"(
|
||||
local a = 55
|
||||
@ -29,7 +29,7 @@ TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "hello")
|
||||
CHECK("number" == toString(bType));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "generic_function")
|
||||
TEST_CASE_FIXTURE(ConstraintGeneratorFixture, "generic_function")
|
||||
{
|
||||
solve(R"(
|
||||
local function id(a)
|
||||
@ -42,7 +42,7 @@ TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "generic_function")
|
||||
CHECK("<a>(a) -> a" == toString(idType));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "proper_let_generalization")
|
||||
TEST_CASE_FIXTURE(ConstraintGeneratorFixture, "proper_let_generalization")
|
||||
{
|
||||
solve(R"(
|
||||
local function a(c)
|
||||
|
@ -17,7 +17,7 @@ TEST_CASE("TypeError_code_should_return_nonzero_code")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "metatable_names_show_instead_of_tables")
|
||||
{
|
||||
frontend.options.retainFullTypeGraphs = false;
|
||||
ScopedFastFlag sff{"LuauStacklessTypeClone2", true};
|
||||
ScopedFastFlag sff{"LuauStacklessTypeClone3", true};
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
local Account = {}
|
||||
|
@ -3106,3 +3106,37 @@ bb_1:
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
||||
TEST_SUITE_BEGIN("Dump");
|
||||
|
||||
TEST_CASE_FIXTURE(IrBuilderFixture, "ToDot")
|
||||
{
|
||||
IrOp entry = build.block(IrBlockKind::Internal);
|
||||
IrOp a = build.block(IrBlockKind::Internal);
|
||||
IrOp b = build.block(IrBlockKind::Internal);
|
||||
IrOp exit = build.block(IrBlockKind::Internal);
|
||||
|
||||
build.beginBlock(entry);
|
||||
build.inst(IrCmd::JUMP_EQ_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(0)), build.constTag(tnumber), a, b);
|
||||
|
||||
build.beginBlock(a);
|
||||
build.inst(IrCmd::STORE_TVALUE, build.vmReg(2), build.inst(IrCmd::LOAD_TVALUE, build.vmReg(1)));
|
||||
build.inst(IrCmd::JUMP, exit);
|
||||
|
||||
build.beginBlock(b);
|
||||
build.inst(IrCmd::STORE_TVALUE, build.vmReg(3), build.inst(IrCmd::LOAD_TVALUE, build.vmReg(1)));
|
||||
build.inst(IrCmd::JUMP, exit);
|
||||
|
||||
build.beginBlock(exit);
|
||||
build.inst(IrCmd::RETURN, build.vmReg(2), build.constInt(2));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
computeCfgInfo(build.function);
|
||||
|
||||
// note: we don't validate the output of these to avoid test churn when dot formatting changes, but we run these to make sure they don't assert/crash
|
||||
toDot(build.function, /* includeInst= */ true);
|
||||
toDotCfg(build.function);
|
||||
toDotDjGraph(build.function);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -1517,8 +1517,6 @@ end
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "DeprecatedApiFenv")
|
||||
{
|
||||
ScopedFastFlag sff("LuauLintDeprecatedFenv", true);
|
||||
|
||||
LintResult result = lint(R"(
|
||||
local f, g, h = ...
|
||||
|
||||
@ -1591,8 +1589,6 @@ table.create(42, {} :: {})
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "TableOperationsIndexer")
|
||||
{
|
||||
ScopedFastFlag sff("LuauLintTableIndexer", true);
|
||||
|
||||
LintResult result = lint(R"(
|
||||
local t1 = {} -- ok: empty
|
||||
local t2 = {1, 2} -- ok: array
|
||||
@ -1827,8 +1823,71 @@ local _ = 0x10000000000000000
|
||||
)");
|
||||
|
||||
REQUIRE(2 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].text, "Binary number literal exceeded available precision and has been truncated to 2^64");
|
||||
CHECK_EQ(result.warnings[1].text, "Hexadecimal number literal exceeded available precision and has been truncated to 2^64");
|
||||
CHECK_EQ(result.warnings[0].text, "Binary number literal exceeded available precision and was truncated to 2^64");
|
||||
CHECK_EQ(result.warnings[1].text, "Hexadecimal number literal exceeded available precision and was truncated to 2^64");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "IntegerParsingDecimalImprecise")
|
||||
{
|
||||
ScopedFastFlag sff("LuauParseImpreciseNumber", true);
|
||||
|
||||
LintResult result = lint(R"(
|
||||
local _ = 10000000000000000000000000000000000000000000000000000000000000000
|
||||
local _ = 10000000000000001
|
||||
local _ = -10000000000000001
|
||||
|
||||
-- 10^16 = 2^16 * 5^16, 5^16 only requires 38 bits
|
||||
local _ = 10000000000000000
|
||||
local _ = -10000000000000000
|
||||
|
||||
-- smallest possible number that is parsed imprecisely
|
||||
local _ = 9007199254740993
|
||||
local _ = -9007199254740993
|
||||
|
||||
-- note that numbers before and after parse precisely (number after is even => 1 more mantissa bit)
|
||||
local _ = 9007199254740992
|
||||
local _ = 9007199254740994
|
||||
|
||||
-- large powers of two should work as well (this is 2^63)
|
||||
local _ = -9223372036854775808
|
||||
)");
|
||||
|
||||
REQUIRE(5 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].text, "Number literal exceeded available precision and was truncated to closest representable number");
|
||||
CHECK_EQ(result.warnings[0].location.begin.line, 1);
|
||||
CHECK_EQ(result.warnings[1].text, "Number literal exceeded available precision and was truncated to closest representable number");
|
||||
CHECK_EQ(result.warnings[1].location.begin.line, 2);
|
||||
CHECK_EQ(result.warnings[2].text, "Number literal exceeded available precision and was truncated to closest representable number");
|
||||
CHECK_EQ(result.warnings[2].location.begin.line, 3);
|
||||
CHECK_EQ(result.warnings[3].text, "Number literal exceeded available precision and was truncated to closest representable number");
|
||||
CHECK_EQ(result.warnings[3].location.begin.line, 10);
|
||||
CHECK_EQ(result.warnings[4].text, "Number literal exceeded available precision and was truncated to closest representable number");
|
||||
CHECK_EQ(result.warnings[4].location.begin.line, 11);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "IntegerParsingHexImprecise")
|
||||
{
|
||||
ScopedFastFlag sff("LuauParseImpreciseNumber", true);
|
||||
|
||||
LintResult result = lint(R"(
|
||||
local _ = 0x1234567812345678
|
||||
|
||||
-- smallest possible number that is parsed imprecisely
|
||||
local _ = 0x20000000000001
|
||||
|
||||
-- note that numbers before and after parse precisely (number after is even => 1 more mantissa bit)
|
||||
local _ = 0x20000000000000
|
||||
local _ = 0x20000000000002
|
||||
|
||||
-- large powers of two should work as well (this is 2^63)
|
||||
local _ = -9223372036854775808
|
||||
)");
|
||||
|
||||
REQUIRE(2 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].text, "Number literal exceeded available precision and was truncated to closest representable number");
|
||||
CHECK_EQ(result.warnings[0].location.begin.line, 1);
|
||||
CHECK_EQ(result.warnings[1].text, "Number literal exceeded available precision and was truncated to closest representable number");
|
||||
CHECK_EQ(result.warnings[1].location.begin.line, 4);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "ComparisonPrecedence")
|
||||
|
@ -14,7 +14,7 @@
|
||||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
LUAU_FASTFLAG(LuauStacklessTypeClone2)
|
||||
LUAU_FASTFLAG(LuauStacklessTypeClone3)
|
||||
|
||||
TEST_SUITE_BEGIN("ModuleTests");
|
||||
|
||||
@ -336,7 +336,7 @@ TEST_CASE_FIXTURE(Fixture, "clone_recursion_limit")
|
||||
int limit = 400;
|
||||
#endif
|
||||
|
||||
ScopedFastFlag sff{"LuauStacklessTypeClone2", false};
|
||||
ScopedFastFlag sff{"LuauStacklessTypeClone3", false};
|
||||
ScopedFastInt luauTypeCloneRecursionLimit{"LuauTypeCloneRecursionLimit", limit};
|
||||
|
||||
TypeArena src;
|
||||
@ -360,7 +360,7 @@ TEST_CASE_FIXTURE(Fixture, "clone_recursion_limit")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "clone_iteration_limit")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauStacklessTypeClone2", true};
|
||||
ScopedFastFlag sff{"LuauStacklessTypeClone3", true};
|
||||
ScopedFastInt sfi{"LuauTypeCloneIterationLimit", 500};
|
||||
|
||||
TypeArena src;
|
||||
@ -534,4 +534,36 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "clone_table_bound_to_table_bound_to_table")
|
||||
REQUIRE(!tableA->boundTo);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "clone_a_bound_type_to_a_persistent_type")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauStacklessTypeClone3", true};
|
||||
|
||||
TypeArena arena;
|
||||
|
||||
TypeId boundTo = arena.addType(BoundType{builtinTypes->numberType});
|
||||
REQUIRE(builtinTypes->numberType->persistent);
|
||||
|
||||
TypeArena dest;
|
||||
CloneState state{builtinTypes};
|
||||
TypeId res = clone(boundTo, dest, state);
|
||||
|
||||
REQUIRE(res == follow(boundTo));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "clone_a_bound_typepack_to_a_persistent_typepack")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauStacklessTypeClone3", true};
|
||||
|
||||
TypeArena arena;
|
||||
|
||||
TypePackId boundTo = arena.addTypePack(BoundTypePack{builtinTypes->neverTypePack});
|
||||
REQUIRE(builtinTypes->neverTypePack->persistent);
|
||||
|
||||
TypeArena dest;
|
||||
CloneState state{builtinTypes};
|
||||
TypePackId res = clone(boundTo, dest, state);
|
||||
|
||||
REQUIRE(res == follow(boundTo));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -31,6 +31,9 @@ struct IsSubtypeFixture : Fixture
|
||||
|
||||
bool isConsistentSubtype(TypeId a, TypeId b)
|
||||
{
|
||||
// any test that is testing isConsistentSubtype is testing the old solver exclusively!
|
||||
ScopedFastFlag noDcr{"DebugLuauDeferredConstraintResolution", false};
|
||||
|
||||
Location location;
|
||||
ModulePtr module = getMainModule();
|
||||
REQUIRE(module);
|
||||
@ -169,7 +172,10 @@ TEST_CASE_FIXTURE(IsSubtypeFixture, "table_with_union_prop")
|
||||
TypeId a = requireType("a");
|
||||
TypeId b = requireType("b");
|
||||
|
||||
CHECK(isSubtype(a, b));
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK(!isSubtype(a, b)); // table properties are invariant
|
||||
else
|
||||
CHECK(isSubtype(a, b));
|
||||
CHECK(!isSubtype(b, a));
|
||||
}
|
||||
|
||||
@ -187,7 +193,10 @@ TEST_CASE_FIXTURE(IsSubtypeFixture, "table_with_any_prop")
|
||||
TypeId a = requireType("a");
|
||||
TypeId b = requireType("b");
|
||||
|
||||
CHECK(isSubtype(a, b));
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK(!isSubtype(a, b)); // table properties are invariant
|
||||
else
|
||||
CHECK(isSubtype(a, b));
|
||||
CHECK(!isSubtype(b, a));
|
||||
CHECK(isConsistentSubtype(b, a));
|
||||
}
|
||||
@ -249,7 +258,10 @@ TEST_CASE_FIXTURE(IsSubtypeFixture, "tables")
|
||||
TypeId c = requireType("c");
|
||||
TypeId d = requireType("d");
|
||||
|
||||
CHECK(isSubtype(a, b));
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK(!isSubtype(a, b)); // table properties are invariant
|
||||
else
|
||||
CHECK(isSubtype(a, b));
|
||||
CHECK(!isSubtype(b, a));
|
||||
CHECK(isConsistentSubtype(b, a));
|
||||
|
||||
@ -259,7 +271,10 @@ TEST_CASE_FIXTURE(IsSubtypeFixture, "tables")
|
||||
CHECK(isSubtype(d, a));
|
||||
CHECK(!isSubtype(a, d));
|
||||
|
||||
CHECK(isSubtype(d, b));
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK(!isSubtype(d, b)); // table properties are invariant
|
||||
else
|
||||
CHECK(isSubtype(d, b));
|
||||
CHECK(!isSubtype(b, d));
|
||||
}
|
||||
|
||||
@ -705,6 +720,19 @@ TEST_CASE_FIXTURE(NormalizeFixture, "specific_functions_cannot_be_negated")
|
||||
CHECK(nullptr == toNormalizedType("Not<(boolean) -> boolean>"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "trivial_intersection_inhabited")
|
||||
{
|
||||
// this test was used to fix a bug in normalization when working with intersections/unions of the same type.
|
||||
|
||||
TypeId a = arena.addType(FunctionType{builtinTypes->emptyTypePack, builtinTypes->anyTypePack, std::nullopt, false});
|
||||
TypeId c = arena.addType(IntersectionType{{a, a}});
|
||||
|
||||
const NormalizedType* n = normalizer.normalize(c);
|
||||
REQUIRE(n);
|
||||
|
||||
CHECK(normalizer.isInhabited(n));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "bare_negated_boolean")
|
||||
{
|
||||
// TODO: We don't yet have a way to say number | string | thread | nil | Class | Table | Function
|
||||
@ -906,4 +934,12 @@ TEST_CASE_FIXTURE(NormalizeFixture, "normalize_is_exactly_number")
|
||||
CHECK(!unionIntersection->isExactlyNumber());
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "normalize_unknown")
|
||||
{
|
||||
auto nt = toNormalizedType("Not<string> | Not<number>");
|
||||
CHECK(nt);
|
||||
CHECK(nt->isUnknown());
|
||||
CHECK(toString(normalizer.typeFromNormal(*nt)) == "unknown");
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -857,6 +857,15 @@ TEST_CASE_FIXTURE(SubtypeFixture, "Child & ~GrandchildOne <!: number")
|
||||
CHECK_IS_NOT_SUBTYPE(meet(childClass, negate(grandchildOneClass)), builtinTypes->numberType);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(SubtypeFixture, "semantic_subtyping_disj")
|
||||
{
|
||||
TypeId subTy = builtinTypes->unknownType;
|
||||
TypeId superTy = join(negate(builtinTypes->numberType), negate(builtinTypes->stringType));
|
||||
SubtypingResult result = isSubtype(subTy, superTy);
|
||||
CHECK(result.isSubtype);
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE_FIXTURE(SubtypeFixture, "t1 where t1 = {trim: (t1) -> string} <: t2 where t2 = {trim: (t2) -> string}")
|
||||
{
|
||||
TypeId t1 = cyclicTable([&](TypeId ty, TableType* tt) {
|
||||
|
@ -185,7 +185,6 @@ TEST_CASE_FIXTURE(Fixture, "mutually_recursive_aliases")
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
#if 0
|
||||
TEST_CASE_FIXTURE(Fixture, "generic_aliases")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
@ -200,7 +199,7 @@ TEST_CASE_FIXTURE(Fixture, "generic_aliases")
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
const std::string expected =
|
||||
R"(Type 'bad' could not be converted into 'T<number>'; type bad["v"] (string) is not a subtype of T<number>["v"] (number))";
|
||||
R"(Type 'bad' could not be converted into 'T<number>'; at ["v"], string is not a subtype of number)";
|
||||
CHECK(result.errors[0].location == Location{{4, 31}, {4, 44}});
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
@ -220,12 +219,11 @@ TEST_CASE_FIXTURE(Fixture, "dependent_generic_aliases")
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
const std::string expected =
|
||||
R"(Type 'bad' could not be converted into 'U<number>'; type bad["t"]["v"] (string) is not a subtype of U<number>["t"]["v"] (number))";
|
||||
R"(Type 'bad' could not be converted into 'U<number>'; at ["t"]["v"], string is not a subtype of number)";
|
||||
|
||||
CHECK(result.errors[0].location == Location{{4, 31}, {4, 52}});
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "mutually_recursive_generic_aliases")
|
||||
{
|
||||
|
@ -934,7 +934,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "prototyping_and_visiting_alias_has_the_same_
|
||||
{
|
||||
ScopedFastFlag sff{"LuauTinyControlFlowAnalysis", true};
|
||||
|
||||
// In CGB, we walk the block to prototype aliases. We then visit the block in-order, which will resolve the prototype to a real type.
|
||||
// In CG, we walk the block to prototype aliases. We then visit the block in-order, which will resolve the prototype to a real type.
|
||||
// That second walk assumes that the name occurs in the same `Scope` that the prototype walk had. If we arbitrarily change scope midway
|
||||
// through, we'd invoke UB.
|
||||
CheckResult result = check(R"(
|
||||
|
@ -426,8 +426,6 @@ TEST_CASE_FIXTURE(ClassFixture, "unions_of_intersections_of_classes")
|
||||
|
||||
TEST_CASE_FIXTURE(ClassFixture, "index_instance_property")
|
||||
{
|
||||
ScopedFastFlag luauAllowIndexClassParameters{"LuauAllowIndexClassParameters", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function execute(object: BaseClass, name: string)
|
||||
print(object[name])
|
||||
@ -440,8 +438,6 @@ TEST_CASE_FIXTURE(ClassFixture, "index_instance_property")
|
||||
|
||||
TEST_CASE_FIXTURE(ClassFixture, "index_instance_property_nonstrict")
|
||||
{
|
||||
ScopedFastFlag luauAllowIndexClassParameters{"LuauAllowIndexClassParameters", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
--!nonstrict
|
||||
|
||||
|
@ -976,7 +976,6 @@ local y = x["Bar"]
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
#if 0
|
||||
TEST_CASE_FIXTURE(Fixture, "cli_80596_simplify_degenerate_intersections")
|
||||
{
|
||||
ScopedFastFlag dcr{"DebugLuauDeferredConstraintResolution", true};
|
||||
@ -1026,6 +1025,5 @@ TEST_CASE_FIXTURE(Fixture, "cli_80596_simplify_more_realistic_intersections")
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -415,7 +415,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "promise_type_error_too_complex" * doctest::t
|
||||
// TODO: LTI changes to function call resolution have rendered this test impossibly slow
|
||||
// shared self should fix it, but there may be other mitigations possible as well
|
||||
REQUIRE(!FFlag::DebugLuauDeferredConstraintResolution);
|
||||
ScopedFastFlag sff{"LuauStacklessTypeClone2", true};
|
||||
ScopedFastFlag sff{"LuauStacklessTypeClone3", true};
|
||||
|
||||
frontend.options.retainFullTypeGraphs = false;
|
||||
|
||||
|
@ -350,7 +350,6 @@ Table type 'a' not compatible with type 'Bad' because the former is missing fiel
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
||||
#if 0
|
||||
TEST_CASE_FIXTURE(Fixture, "parametric_tagged_union_alias")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
@ -372,7 +371,6 @@ TEST_CASE_FIXTURE(Fixture, "parametric_tagged_union_alias")
|
||||
|
||||
CHECK(toString(result.errors[0]) == expectedError);
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "if_then_else_expression_singleton_options")
|
||||
{
|
||||
|
@ -43,7 +43,10 @@ TEST_CASE_FIXTURE(Fixture, "basic")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "augment_table")
|
||||
{
|
||||
CheckResult result = check("local t = {} t.foo = 'bar'");
|
||||
CheckResult result = check(R"(
|
||||
local t = {}
|
||||
t.foo = 'bar'
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
const TableType* tType = get<TableType>(requireType("t"));
|
||||
@ -70,6 +73,35 @@ TEST_CASE_FIXTURE(Fixture, "augment_nested_table")
|
||||
CHECK("{ p: { foo: string } }" == toString(requireType("t"), {true}));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "assign_key_at_index_expr")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
function f(t: {[string]: number})
|
||||
t["hello"] = 1
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
// We had a bug where we forgot to record the astType of this particular node.
|
||||
CHECK("string" == toString(requireTypeAtPosition({2, 19})));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "index_expression_is_checked_against_the_indexer_type")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
function f(t: {[boolean]: number})
|
||||
t["hello"] = 15
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_MESSAGE(get<CannotExtendTable>(result.errors[0]), "Expected CannotExtendTable but got " << toString(result.errors[0]));
|
||||
else
|
||||
CHECK(get<TypeMismatch>(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "cannot_augment_sealed_table")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
|
@ -1453,8 +1453,6 @@ TEST_CASE_FIXTURE(Fixture, "promote_tail_type_packs")
|
||||
*/
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "be_sure_to_use_active_txnlog_when_evaluating_a_variadic_overload")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauVariadicOverloadFix", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function concat<T>(target: {T}, ...: {T} | T): {T}
|
||||
return (nil :: any) :: {T}
|
||||
|
@ -15,6 +15,9 @@ LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
|
||||
struct TryUnifyFixture : Fixture
|
||||
{
|
||||
// Cannot use `TryUnifyFixture` under DCR.
|
||||
ScopedFastFlag noDcr{"DebugLuauDeferredConstraintResolution", false};
|
||||
|
||||
TypeArena arena;
|
||||
ScopePtr globalScope{new Scope{arena.addTypePack({TypeId{}})}};
|
||||
InternalErrorReporter iceHandler;
|
||||
@ -139,7 +142,7 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "incompatible_tables_are_preserved")
|
||||
CHECK_NE(*getMutable<TableType>(&tableOne)->props["foo"].type(), *getMutable<TableType>(&tableTwo)->props["foo"].type());
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(TryUnifyFixture, "uninhabited_intersection_sub_never")
|
||||
TEST_CASE_FIXTURE(Fixture, "uninhabited_intersection_sub_never")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
function f(arg : string & number) : never
|
||||
@ -149,7 +152,7 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "uninhabited_intersection_sub_never")
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(TryUnifyFixture, "uninhabited_intersection_sub_anything")
|
||||
TEST_CASE_FIXTURE(Fixture, "uninhabited_intersection_sub_anything")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
function f(arg : string & number) : boolean
|
||||
@ -159,7 +162,7 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "uninhabited_intersection_sub_anything")
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(TryUnifyFixture, "uninhabited_table_sub_never")
|
||||
TEST_CASE_FIXTURE(Fixture, "uninhabited_table_sub_never")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
function f(arg : { prop : string & number }) : never
|
||||
@ -169,7 +172,7 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "uninhabited_table_sub_never")
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(TryUnifyFixture, "uninhabited_table_sub_anything")
|
||||
TEST_CASE_FIXTURE(Fixture, "uninhabited_table_sub_anything")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
function f(arg : { prop : string & number }) : boolean
|
||||
@ -179,9 +182,11 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "uninhabited_table_sub_anything")
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(TryUnifyFixture, "members_of_failed_typepack_unification_are_unified_with_errorType")
|
||||
TEST_CASE_FIXTURE(Fixture, "members_of_failed_typepack_unification_are_unified_with_errorType")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauAlwaysCommitInferencesOfFunctionCalls", true};
|
||||
ScopedFastFlag sff[] = {
|
||||
{"LuauAlwaysCommitInferencesOfFunctionCalls", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function f(arg: number) end
|
||||
@ -196,9 +201,11 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "members_of_failed_typepack_unification_are_u
|
||||
CHECK_EQ("*error-type*", toString(requireType("b")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(TryUnifyFixture, "result_of_failed_typepack_unification_is_constrained")
|
||||
TEST_CASE_FIXTURE(Fixture, "result_of_failed_typepack_unification_is_constrained")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauAlwaysCommitInferencesOfFunctionCalls", true};
|
||||
ScopedFastFlag sff[] = {
|
||||
{"LuauAlwaysCommitInferencesOfFunctionCalls", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function f(arg: number) return arg end
|
||||
@ -214,7 +221,7 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "result_of_failed_typepack_unification_is_con
|
||||
CHECK_EQ("number", toString(requireType("c")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(TryUnifyFixture, "typepack_unification_should_trim_free_tails")
|
||||
TEST_CASE_FIXTURE(Fixture, "typepack_unification_should_trim_free_tails")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
@ -254,7 +261,7 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "variadic_tails_respect_progress")
|
||||
CHECK(state.errors.empty());
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(TryUnifyFixture, "variadics_should_use_reversed_properly")
|
||||
TEST_CASE_FIXTURE(Fixture, "variadics_should_use_reversed_properly")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
@ -373,22 +380,12 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "metatables_unify_against_shape_of_free_table
|
||||
state.log.commit();
|
||||
|
||||
REQUIRE_EQ(state.errors.size(), 1);
|
||||
// clang-format off
|
||||
const std::string expected =
|
||||
(FFlag::DebugLuauDeferredConstraintResolution) ?
|
||||
R"(Type
|
||||
'{ @metatable { __index: { foo: string } }, {| |} }'
|
||||
could not be converted into
|
||||
'{- foo: number -}'
|
||||
caused by:
|
||||
Type 'number' could not be converted into 'string')" :
|
||||
R"(Type
|
||||
const std::string expected = R"(Type
|
||||
'{ @metatable {| __index: {| foo: string |} |}, { } }'
|
||||
could not be converted into
|
||||
'{- foo: number -}'
|
||||
caused by:
|
||||
Type 'number' could not be converted into 'string')";
|
||||
// clang-format on
|
||||
CHECK_EQ(expected, toString(state.errors[0]));
|
||||
}
|
||||
|
||||
|
@ -93,7 +93,6 @@ TEST_SUITE_BEGIN("TypePathTraversal");
|
||||
LUAU_REQUIRE_NO_ERRORS(result); \
|
||||
} while (false);
|
||||
|
||||
#if 0
|
||||
TEST_CASE_FIXTURE(Fixture, "empty_traversal")
|
||||
{
|
||||
CHECK(traverseForType(builtinTypes->numberType, kEmpty, builtinTypes) == builtinTypes->numberType);
|
||||
@ -475,7 +474,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "complex_chains")
|
||||
CHECK(*result == builtinTypes->falseType);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_SUITE_END(); // TypePathTraversal
|
||||
|
||||
|
@ -140,6 +140,11 @@ assert(bit32.byteswap(0x10203040) == 0x40302010)
|
||||
assert(bit32.byteswap(0) == 0)
|
||||
assert(bit32.byteswap(-1) == 0xffffffff)
|
||||
|
||||
-- bit32.bor(n, 0) must clear top bits
|
||||
-- we check this obscuring the constant through a global to make sure this gets evaluated fully
|
||||
high32 = 0x42_1234_5678
|
||||
assert(bit32.bor(high32, 0) == 0x1234_5678)
|
||||
|
||||
--[[
|
||||
This test verifies a fix in luauF_replace() where if the 4th
|
||||
parameter was not a number, but the first three are numbers, it will
|
||||
|
@ -61,6 +61,7 @@ assert(1111111111111111-1111111111111110== 1000.00e-03)
|
||||
-- 1234567890123456
|
||||
assert(1.1 == '1.'+'.1')
|
||||
assert('1111111111111111'-'1111111111111110' == tonumber" +0.001e+3 \n\t")
|
||||
assert(10000000000000001 == 10000000000000000)
|
||||
|
||||
function eq (a,b,limit)
|
||||
if not limit then limit = 10E-10 end
|
||||
|
@ -15,20 +15,33 @@ end
|
||||
|
||||
local justone = "^" .. utf8.charpattern .. "$"
|
||||
|
||||
-- 't' is the list of codepoints of 's'
|
||||
local function checksyntax (s, t)
|
||||
-- creates a string "return '\u{t[1]}...\u{t[n]}'"
|
||||
local ts = {"return '"}
|
||||
for i = 1, #t do ts[i + 1] = string.format("\\u{%x}", t[i]) end
|
||||
ts[#t + 2] = "'"
|
||||
ts = table.concat(ts)
|
||||
-- its execution should result in 's'
|
||||
assert(assert(loadstring(ts))() == s)
|
||||
end
|
||||
|
||||
assert(not utf8.offset("alo", 5))
|
||||
assert(not utf8.offset("alo", -4))
|
||||
|
||||
-- 'check' makes several tests over the validity of string 's'.
|
||||
-- 't' is the list of codepoints of 's'.
|
||||
local function check (s, t, nonstrict)
|
||||
local l = utf8.len(s, 1, -1, nonstrict)
|
||||
local function check (s, t)
|
||||
local l = utf8.len(s, 1, -1)
|
||||
assert(#t == l and len(s) == l)
|
||||
assert(utf8.char(table.unpack(t)) == s) -- 't' and 's' are equivalent
|
||||
|
||||
assert(utf8.offset(s, 0) == 1)
|
||||
|
||||
checksyntax(s, t)
|
||||
|
||||
-- creates new table with all codepoints of 's'
|
||||
local t1 = {utf8.codepoint(s, 1, -1, nonstrict)}
|
||||
local t1 = {utf8.codepoint(s, 1, -1)}
|
||||
assert(#t == #t1)
|
||||
for i = 1, #t do assert(t[i] == t1[i]) end -- 't' is equal to 't1'
|
||||
|
||||
@ -38,25 +51,25 @@ local function check (s, t, nonstrict)
|
||||
assert(string.find(string.sub(s, pi, pi1 - 1), justone))
|
||||
assert(utf8.offset(s, -1, pi1) == pi)
|
||||
assert(utf8.offset(s, i - l - 1) == pi)
|
||||
assert(pi1 - pi == #utf8.char(utf8.codepoint(s, pi, pi, nonstrict)))
|
||||
assert(pi1 - pi == #utf8.char(utf8.codepoint(s, pi, pi)))
|
||||
for j = pi, pi1 - 1 do
|
||||
assert(utf8.offset(s, 0, j) == pi)
|
||||
end
|
||||
for j = pi + 1, pi1 - 1 do
|
||||
assert(not utf8.len(s, j))
|
||||
end
|
||||
assert(utf8.len(s, pi, pi, nonstrict) == 1)
|
||||
assert(utf8.len(s, pi, pi1 - 1, nonstrict) == 1)
|
||||
assert(utf8.len(s, pi, -1, nonstrict) == l - i + 1)
|
||||
assert(utf8.len(s, pi1, -1, nonstrict) == l - i)
|
||||
assert(utf8.len(s, 1, pi, nonstrict) == i)
|
||||
assert(utf8.len(s, pi, pi) == 1)
|
||||
assert(utf8.len(s, pi, pi1 - 1) == 1)
|
||||
assert(utf8.len(s, pi, -1) == l - i + 1)
|
||||
assert(utf8.len(s, pi1, -1) == l - i)
|
||||
assert(utf8.len(s, 1, pi) == i)
|
||||
end
|
||||
|
||||
local i = 0
|
||||
for p, c in utf8.codes(s, nonstrict) do
|
||||
for p, c in utf8.codes(s) do
|
||||
i = i + 1
|
||||
assert(c == t[i] and p == utf8.offset(s, i))
|
||||
assert(utf8.codepoint(s, p, p, nonstrict) == c)
|
||||
assert(utf8.codepoint(s, p, p) == c)
|
||||
end
|
||||
assert(i == #t)
|
||||
|
||||
@ -80,9 +93,15 @@ do -- error indication in utf8.len
|
||||
assert(not a and b == p)
|
||||
end
|
||||
check("abc\xE3def", 4)
|
||||
check("汉字\x80", #("汉字") + 1)
|
||||
check("\xF4\x9F\xBF", 1)
|
||||
check("\xF4\x9F\xBF\xBF", 1)
|
||||
-- spurious continuation bytes
|
||||
check("汉字\x80", #("汉字") + 1)
|
||||
check("\x80hello", 1)
|
||||
check("hel\x80lo", 4)
|
||||
check("汉字\xBF", #("汉字") + 1)
|
||||
check("\xBFhello", 1)
|
||||
check("hel\xBFlo", 4)
|
||||
end
|
||||
|
||||
-- errors in utf8.codes
|
||||
@ -94,7 +113,17 @@ do
|
||||
end)
|
||||
end
|
||||
errorcodes("ab\xff")
|
||||
-- errorcodes("\u{110000}")
|
||||
errorcodes("\244\144\128\128") -- "\u{110000}" in Lua 5.4
|
||||
errorcodes("in\x80valid")
|
||||
errorcodes("\xbfinvalid")
|
||||
errorcodes("αλφ\xBFα")
|
||||
|
||||
-- calling interation function with invalid arguments
|
||||
local f = utf8.codes("")
|
||||
assert(f("", 2) == nil)
|
||||
assert(f("", -1) == nil)
|
||||
assert(f("", math.mininteger) == nil)
|
||||
|
||||
end
|
||||
|
||||
-- error in initial position for offset
|
||||
@ -131,16 +160,16 @@ do
|
||||
-- surrogates
|
||||
assert(utf8.codepoint("\u{D7FF}") == 0xD800 - 1)
|
||||
assert(utf8.codepoint("\u{E000}") == 0xDFFF + 1)
|
||||
assert(utf8.codepoint("\u{D800}", 1, 1, true) == 0xD800)
|
||||
assert(utf8.codepoint("\u{DFFF}", 1, 1, true) == 0xDFFF)
|
||||
-- assert(utf8.codepoint("\u{7FFFFFFF}", 1, 1, true) == 0x7FFFFFFF)
|
||||
assert(utf8.codepoint("\u{D800}", 1, 1) == 0xD800) -- TODO: this is an error in Lua 5.4
|
||||
assert(utf8.codepoint("\u{DFFF}", 1, 1) == 0xDFFF) -- TODO: this is an error in Lua 5.4
|
||||
assert(pcall(utf8.codepoint, "\253\191\191\191\191\191") == false) -- 0x7FFFFFFF in Lua 5.4 when called with lax=true
|
||||
end
|
||||
|
||||
assert(utf8.char() == "")
|
||||
assert(utf8.char(0, 97, 98, 99, 1) == "\0abc\1")
|
||||
|
||||
assert(utf8.codepoint(utf8.char(0x10FFFF)) == 0x10FFFF)
|
||||
-- assert(utf8.codepoint(utf8.char(0x7FFFFFFF), 1, 1, true) == 2147483647)
|
||||
assert(pcall(utf8.char, 0x7FFFFFFF) == false) -- valid in Lua 5.4
|
||||
|
||||
checkerror("value out of range", utf8.char, 0x7FFFFFFF + 1)
|
||||
checkerror("value out of range", utf8.char, -1)
|
||||
@ -154,8 +183,8 @@ end
|
||||
invalid("\xF4\x9F\xBF\xBF")
|
||||
|
||||
-- surrogates
|
||||
-- invalid("\u{D800}")
|
||||
-- invalid("\u{DFFF}")
|
||||
-- invalid("\u{D800}") TODO: this is an error in Lua 5.4
|
||||
-- invalid("\u{DFFF}") TODO: this is an error in Lua 5.4
|
||||
|
||||
-- overlong sequences
|
||||
invalid("\xC0\x80") -- zero
|
||||
@ -182,7 +211,7 @@ s = "\0 \x7F\z
|
||||
s = string.gsub(s, " ", "")
|
||||
check(s, {0,0x7F, 0x80,0x7FF, 0x800,0xFFFF, 0x10000,0x10FFFF})
|
||||
|
||||
x = "日本語a-4\0éó"
|
||||
local x = "日本語a-4\0éó"
|
||||
check(x, {26085, 26412, 35486, 97, 45, 52, 0, 233, 243})
|
||||
|
||||
|
||||
|
@ -28,6 +28,7 @@
|
||||
#endif
|
||||
|
||||
#include <optional>
|
||||
#include <stdio.h>
|
||||
|
||||
// Indicates if verbose output is enabled; can be overridden via --verbose
|
||||
// Currently, this enables output from 'print', but other verbose output could be enabled eventually.
|
||||
@ -180,6 +181,70 @@ struct BoostLikeReporter : doctest::IReporter
|
||||
void test_case_skipped(const doctest::TestCaseData&) override {}
|
||||
};
|
||||
|
||||
struct TeamCityReporter : doctest::IReporter
|
||||
{
|
||||
const doctest::TestCaseData* currentTest = nullptr;
|
||||
|
||||
TeamCityReporter(const doctest::ContextOptions& in) {}
|
||||
|
||||
void report_query(const doctest::QueryData&) override {}
|
||||
|
||||
void test_run_start() override {}
|
||||
|
||||
void test_run_end(const doctest::TestRunStats& /*in*/) override {}
|
||||
|
||||
void test_case_start(const doctest::TestCaseData& in) override
|
||||
{
|
||||
currentTest = ∈
|
||||
printf("##teamcity[testStarted name='%s: %s' captureStandardOutput='true']\n", in.m_test_suite, in.m_name);
|
||||
}
|
||||
|
||||
// called when a test case is reentered because of unfinished subcases
|
||||
void test_case_reenter(const doctest::TestCaseData& /*in*/) override {}
|
||||
|
||||
void test_case_end(const doctest::CurrentTestCaseStats& in) override
|
||||
{
|
||||
printf("##teamcity[testMetadata testName='%s: %s' name='total_asserts' type='number' value='%d']\n", currentTest->m_test_suite, currentTest->m_name, in.numAssertsCurrentTest);
|
||||
printf("##teamcity[testMetadata testName='%s: %s' name='failed_asserts' type='number' value='%d']\n", currentTest->m_test_suite, currentTest->m_name, in.numAssertsFailedCurrentTest);
|
||||
printf("##teamcity[testMetadata testName='%s: %s' name='runtime' type='number' value='%f']\n", currentTest->m_test_suite, currentTest->m_name, in.seconds);
|
||||
|
||||
if (!in.testCaseSuccess)
|
||||
printf("##teamcity[testFailed name='%s: %s']\n", currentTest->m_test_suite, currentTest->m_name);
|
||||
|
||||
printf("##teamcity[testFinished name='%s: %s']\n", currentTest->m_test_suite, currentTest->m_name);
|
||||
}
|
||||
|
||||
void test_case_exception(const doctest::TestCaseException& in) override {
|
||||
printf("##teamcity[testFailed name='%s: %s' message='Unhandled exception' details='%s']\n", currentTest->m_test_suite, currentTest->m_name, in.error_string.c_str());
|
||||
}
|
||||
|
||||
void subcase_start(const doctest::SubcaseSignature& /*in*/) override {}
|
||||
void subcase_end() override {}
|
||||
|
||||
void log_assert(const doctest::AssertData& ad) override {
|
||||
if(!ad.m_failed)
|
||||
return;
|
||||
|
||||
if (ad.m_decomp.size())
|
||||
fprintf(stderr, "%s(%d): ERROR: %s (%s)\n", ad.m_file, ad.m_line, ad.m_expr, ad.m_decomp.c_str());
|
||||
else
|
||||
fprintf(stderr, "%s(%d): ERROR: %s\n", ad.m_file, ad.m_line, ad.m_expr);
|
||||
}
|
||||
|
||||
void log_message(const doctest::MessageData& md) override {
|
||||
const char* severity = (md.m_severity & doctest::assertType::is_warn) ? "WARNING" : "ERROR";
|
||||
bool isError = md.m_severity & (doctest::assertType::is_require | doctest::assertType::is_check);
|
||||
fprintf(isError ? stderr : stdout, "%s(%d): %s: %s\n", md.m_file, md.m_line, severity, md.m_string.c_str());
|
||||
}
|
||||
|
||||
void test_case_skipped(const doctest::TestCaseData& in) override
|
||||
{
|
||||
printf("##teamcity[testIgnored name='%s: %s' captureStandardOutput='false']\n", in.m_test_suite, in.m_name);
|
||||
}
|
||||
};
|
||||
|
||||
REGISTER_REPORTER("teamcity", 1, TeamCityReporter);
|
||||
|
||||
template<typename T>
|
||||
using FValueResult = std::pair<std::string, T>;
|
||||
|
||||
|
@ -8,8 +8,6 @@ AstQuery::getDocumentationSymbolAtPosition.table_overloaded_function_prop
|
||||
AutocompleteTest.anonymous_autofilled_generic_on_argument_type_pack_vararg
|
||||
AutocompleteTest.anonymous_autofilled_generic_type_pack_vararg
|
||||
AutocompleteTest.autocomplete_interpolated_string_as_singleton
|
||||
AutocompleteTest.autocomplete_oop_implicit_self
|
||||
AutocompleteTest.autocomplete_response_perf1
|
||||
AutocompleteTest.autocomplete_string_singleton_equality
|
||||
AutocompleteTest.autocomplete_string_singleton_escape
|
||||
AutocompleteTest.autocomplete_string_singletons
|
||||
@ -230,6 +228,7 @@ IntersectionTypes.table_intersection_write_sealed
|
||||
IntersectionTypes.table_intersection_write_sealed_indirect
|
||||
IntersectionTypes.table_write_sealed_indirect
|
||||
IntersectionTypes.union_saturate_overloaded_functions
|
||||
isSubtype.any_is_unknown_union_error
|
||||
Linter.DeprecatedApiFenv
|
||||
Linter.FormatStringTyped
|
||||
Linter.TableOperationsIndexer
|
||||
@ -312,7 +311,7 @@ TableTests.accidentally_checked_prop_in_opposite_branch
|
||||
TableTests.any_when_indexing_into_an_unsealed_table_with_no_indexer_in_nonstrict_mode
|
||||
TableTests.array_factory_function
|
||||
TableTests.call_method
|
||||
TableTests.cannot_change_type_of_unsealed_table_prop
|
||||
TableTests.call_method_with_explicit_self_argument
|
||||
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_indexer4
|
||||
@ -333,9 +332,7 @@ TableTests.cyclic_shifted_tables
|
||||
TableTests.disallow_indexing_into_an_unsealed_table_with_no_indexer_in_strict_mode
|
||||
TableTests.dont_crash_when_setmetatable_does_not_produce_a_metatabletypevar
|
||||
TableTests.dont_extend_unsealed_tables_in_rvalue_position
|
||||
TableTests.dont_hang_when_trying_to_look_up_in_cyclic_metatable_index
|
||||
TableTests.dont_leak_free_table_props
|
||||
TableTests.dont_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.error_detailed_indexer_key
|
||||
@ -379,7 +376,6 @@ 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_function_call
|
||||
TableTests.only_ascribe_synthetic_names_at_module_scope
|
||||
TableTests.oop_indexer_works
|
||||
TableTests.oop_polymorphic
|
||||
TableTests.open_table_unification_2
|
||||
TableTests.pass_a_union_of_tables_to_a_function_that_requires_a_table
|
||||
@ -424,6 +420,8 @@ TableTests.unification_of_unions_in_a_self_referential_type
|
||||
TableTests.unifying_tables_shouldnt_uaf1
|
||||
TableTests.used_colon_instead_of_dot
|
||||
TableTests.used_dot_instead_of_colon
|
||||
TableTests.used_dot_instead_of_colon_but_correctly
|
||||
TableTests.when_augmenting_an_unsealed_table_with_an_indexer_apply_the_correct_scope_to_the_indexer_type
|
||||
TableTests.wrong_assign_does_hit_indexer
|
||||
ToDot.function
|
||||
ToString.exhaustive_toString_of_cyclic_table
|
||||
@ -557,7 +555,6 @@ TypeInferFunctions.function_is_supertype_of_concrete_functions
|
||||
TypeInferFunctions.function_statement_sealed_table_assignment_through_indexer
|
||||
TypeInferFunctions.generic_packs_are_not_variadic
|
||||
TypeInferFunctions.higher_order_function_2
|
||||
TypeInferFunctions.higher_order_function_3
|
||||
TypeInferFunctions.higher_order_function_4
|
||||
TypeInferFunctions.improved_function_arg_mismatch_error_nonstrict
|
||||
TypeInferFunctions.improved_function_arg_mismatch_errors
|
||||
@ -596,10 +593,6 @@ TypeInferFunctions.too_many_return_values_no_function
|
||||
TypeInferFunctions.vararg_function_is_quantified
|
||||
TypeInferLoops.cli_68448_iterators_need_not_accept_nil
|
||||
TypeInferLoops.dcr_iteration_explore_raycast_minimization
|
||||
TypeInferLoops.dcr_iteration_fragmented_keys
|
||||
TypeInferLoops.dcr_iteration_minimized_fragmented_keys_1
|
||||
TypeInferLoops.dcr_iteration_minimized_fragmented_keys_2
|
||||
TypeInferLoops.dcr_iteration_minimized_fragmented_keys_3
|
||||
TypeInferLoops.dcr_iteration_on_never_gives_never
|
||||
TypeInferLoops.dcr_xpath_candidates
|
||||
TypeInferLoops.for_in_loop
|
||||
@ -647,7 +640,6 @@ TypeInferOOP.methods_are_topologically_sorted
|
||||
TypeInferOOP.object_constructor_can_refer_to_method_of_self
|
||||
TypeInferOOP.promise_type_error_too_complex
|
||||
TypeInferOOP.react_style_oo
|
||||
TypeInferOOP.table_oop
|
||||
TypeInferOperators.add_type_family_works
|
||||
TypeInferOperators.and_binexps_dont_unify
|
||||
TypeInferOperators.cli_38355_recursive_union
|
||||
@ -694,7 +686,6 @@ TypePackTests.type_alias_type_packs_import
|
||||
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
|
||||
|
Loading…
Reference in New Issue
Block a user