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:
aaron 2023-11-03 16:45:04 -07:00 committed by GitHub
parent 1a9159daff
commit 7105c81579
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
69 changed files with 1013 additions and 579 deletions

View file

@ -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.
*/

View file

@ -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};

View file

@ -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;

View file

@ -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);

View file

@ -12,33 +12,29 @@ 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>)
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 (auto innerTy = ty.get_if<TypeId>())
return get<T>(*innerTy);
if (const TypeId* ty = get<TypeId>(tyOrTp))
return get<T>(*ty);
else
return nullptr;
}
else if constexpr (TypePackVariant::is_part_of_v<T>)
template<typename T, typename std::enable_if_t<TypePackVariant::is_part_of_v<T>, bool> = true>
const T* get(const TypeOrPack& tyOrTp)
{
if (auto innerTp = ty.get_if<TypePackId>())
return get<T>(*innerTp);
if (const TypePackId* tp = get<TypePackId>(tyOrTp))
return get<T>(*tp);
else
return nullptr;
}
else
{
static_assert(always_false_v<T>, "invalid T to get from TypeOrPack");
LUAU_UNREACHABLE();
}
}
TypeOrPack follow(TypeOrPack ty);

View file

@ -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,6 +145,15 @@ static bool checkTypeMatch(TypeId subTy, TypeId superTy, NotNull<Scope> scope, T
InternalErrorReporter iceReporter;
UnifierSharedState unifierState(&iceReporter);
Normalizer normalizer{typeArena, builtinTypes, NotNull{&unifierState}};
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
@ -152,6 +163,8 @@ static bool checkTypeMatch(TypeId subTy, TypeId superTy, NotNull<Scope> scope, T
return unifier.canUnify(subTy, superTy).empty();
}
}
static TypeCorrectKind checkTypeCorrectKind(
const Module& module, TypeArena* typeArena, NotNull<BuiltinTypes> builtinTypes, AstNode* node, Position position, TypeId ty)
{

View file

@ -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"

View file

@ -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}};

View file

@ -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,11 +856,8 @@ 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});
}
}
@ -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)))

View file

@ -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.

View file

@ -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);

View file

@ -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;
}

View file

@ -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"

View file

@ -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}};
// 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}};
// 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}};

View file

@ -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};

View file

@ -2413,17 +2413,11 @@ struct TypeChecker2
}
}
bool testIsSubtype(TypeId subTy, TypeId superTy, Location location)
void explainError(TypeId subTy, TypeId superTy, Location location, const SubtypingResult& r)
{
SubtypingResult r = subtyping->isSubtype(subTy, superTy);
if (!r.reasoning)
return reportError(TypeMismatch{superTy, subTy}, location);
if (r.normalizationTooComplex)
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);
@ -2433,15 +2427,26 @@ struct TypeChecker2
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) +
")";
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);
}
else
reportError(TypeMismatch{superTy, subTy}, location);
}
bool testIsSubtype(TypeId subTy, TypeId superTy, Location location)
{
SubtypingResult r = subtyping->isSubtype(subTy, superTy);
if (r.normalizationTooComplex)
reportError(NormalizationTooComplex{}, location);
if (!r.isSubtype && !r.isErrorSuppressing)
explainError(subTy, superTy, location, r);
return r.isSubtype;
}

View file

@ -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,8 +3410,6 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex
}
}
if (FFlag::LuauAllowIndexClassParameters)
{
if (const ClassType* exprClass = get<ClassType>(exprType))
{
if (isNonstrictMode())
@ -3422,7 +3418,6 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex
return errorRecoveryType(scope);
}
}
}
TableType* exprTable = getMutableTableType(exprType);
@ -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);
++argIter;
++argIndex;
}

View file

@ -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();
}
auto mkFreshType = [this](Scope* scope, TypeLevel level) {
if (FFlag::DebugLuauDeferredConstraintResolution)

View file

@ -249,6 +249,7 @@ public:
enum class ConstantNumberParseResult
{
Ok,
Imprecise,
Malformed,
BinOverflow,
HexOverflow,

View file

@ -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;

View file

@ -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;

View file

@ -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,9 +2211,33 @@ static ConstantNumberParseResult parseDouble(double& result, const char* data)
char* end = nullptr;
double value = strtod(data, &end);
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
AstExpr* Parser::parseSimpleExpr()

View file

@ -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");
}

View file

@ -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;
}
};

View file

@ -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

View file

@ -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);

View file

@ -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,32 +543,9 @@ 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);
@ -624,7 +557,6 @@ const Instruction* executeNAMECALL(lua_State* L, const Instruction* pc, StkId ba
if (ttisnil(ra))
luaG_methoderror(L, ra + 1, tsvalue(kv));
}
}
else
{
Table* mt = ttisuserdata(rb) ? uvalue(rb)->metatable : L->global->mt[ttype(rb)];

View file

@ -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);

View file

@ -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:

View file

@ -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)

View file

@ -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);
// 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);

View file

@ -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};
}

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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:

View file

@ -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;

View file

@ -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());

View file

@ -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
@ -2518,15 +2517,10 @@ 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);
// 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);
// 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)
@ -3747,20 +3723,10 @@ 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;
}
}
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;
};

View file

@ -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

View file

@ -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

View file

@ -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);

View file

@ -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()

View file

@ -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

View file

@ -435,8 +435,6 @@ static int cxxthrow(lua_State* L)
TEST_CASE("PCall")
{
ScopedFastFlag sff("LuauHandlerClose", true);
runConformance(
"pcall.lua",
[](lua_State* L) {

View file

@ -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, {}};

View file

@ -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);

View file

@ -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)

View file

@ -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 = {}

View file

@ -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();

View file

@ -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")

View file

@ -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();

View file

@ -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,6 +172,9 @@ TEST_CASE_FIXTURE(IsSubtypeFixture, "table_with_union_prop")
TypeId a = requireType("a");
TypeId b = requireType("b");
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK(!isSubtype(a, b)); // table properties are invariant
else
CHECK(isSubtype(a, b));
CHECK(!isSubtype(b, a));
}
@ -187,6 +193,9 @@ TEST_CASE_FIXTURE(IsSubtypeFixture, "table_with_any_prop")
TypeId a = requireType("a");
TypeId b = requireType("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,6 +258,9 @@ TEST_CASE_FIXTURE(IsSubtypeFixture, "tables")
TypeId c = requireType("c");
TypeId d = requireType("d");
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,6 +271,9 @@ TEST_CASE_FIXTURE(IsSubtypeFixture, "tables")
CHECK(isSubtype(d, a));
CHECK(!isSubtype(a, d));
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();

View file

@ -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) {

View file

@ -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")
{

View file

@ -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"(

View file

@ -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

View file

@ -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();

View file

@ -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;

View file

@ -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")
{

View file

@ -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"(

View file

@ -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}

View file

@ -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]));
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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})

View file

@ -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 = &in;
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>;

View file

@ -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