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 // 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 // scope pointers; the scopes themselves borrow pointers to other scopes to
@ -68,7 +68,7 @@ struct ConstraintGraphBuilder
NotNull<BuiltinTypes> builtinTypes; NotNull<BuiltinTypes> builtinTypes;
const NotNull<TypeArena> arena; const NotNull<TypeArena> arena;
// The root scope of the module we're generating constraints for. // 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; Scope* rootScope;
struct InferredBinding struct InferredBinding
@ -116,13 +116,13 @@ struct ConstraintGraphBuilder
DcrLogger* logger; 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, NotNull<BuiltinTypes> builtinTypes, NotNull<InternalErrorReporter> ice, const ScopePtr& globalScope,
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope, DcrLogger* logger, NotNull<DataFlowGraph> dfg, std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope, DcrLogger* logger, NotNull<DataFlowGraph> dfg,
std::vector<RequireCycle> requireCycles); 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. * of scopes, constraints, and free types that can be solved later.
* @param block the root block to generate constraints for. * @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); 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::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); * Generate constraints to assign assignedTy to the expression expr
std::optional<TypeId> checkLValue(const ScopePtr& scope, AstExprGlobal* global); * @returns the type of the expression. This may or may not be assignedTy itself.
std::optional<TypeId> checkLValue(const ScopePtr& scope, AstExprIndexName* indexName); */
std::optional<TypeId> checkLValue(const ScopePtr& scope, AstExprIndexExpr* indexExpr); std::optional<TypeId> checkLValue(const ScopePtr& scope, AstExpr* expr, TypeId assignedTy);
TypeId updateProperty(const ScopePtr& scope, AstExpr* expr); 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); void updateLValueType(AstExpr* lvalue, TypeId ty);
@ -324,7 +328,7 @@ private:
/** Scan the program for global definitions. /** 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 * 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. * initial scan of the AST and note what globals are defined.
*/ */

View File

@ -34,7 +34,7 @@ struct DataFlowGraph
DataFlowGraph& operator=(DataFlowGraph&&) = default; DataFlowGraph& operator=(DataFlowGraph&&) = default;
DefId getDef(const AstExpr* expr) const; 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; std::optional<DefId> getRValueDefForCompoundAssign(const AstExpr* expr) const;
DefId getDef(const AstLocal* local) 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 // 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. // 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}; DenseHashMap<const AstExpr*, const RefinementKey*> astRefinementKeys{nullptr};

View File

@ -29,7 +29,7 @@ bool isConsistentSubtype(TypePackId subTy, TypePackId superTy, NotNull<Scope> sc
class TypeIds class TypeIds
{ {
private: private:
std::unordered_set<TypeId> types; DenseHashMap<TypeId, bool> types{nullptr};
std::vector<TypeId> order; std::vector<TypeId> order;
std::size_t hash = 0; std::size_t hash = 0;
@ -277,6 +277,7 @@ struct NormalizedType
NormalizedType& operator=(NormalizedType&&) = default; NormalizedType& operator=(NormalizedType&&) = default;
// IsType functions // IsType functions
bool isUnknown() const;
/// Returns true if the type is exactly a number. Behaves like Type::isNumber() /// Returns true if the type is exactly a number. Behaves like Type::isNumber()
bool isExactlyNumber() const; bool isExactlyNumber() const;

View File

@ -2,8 +2,6 @@
#pragma once #pragma once
#include "Luau/Ast.h"
#include "Luau/Module.h"
#include "Luau/NotNull.h" #include "Luau/NotNull.h"
namespace Luau namespace Luau
@ -13,6 +11,8 @@ struct BuiltinTypes;
struct DcrLogger; struct DcrLogger;
struct TypeCheckLimits; struct TypeCheckLimits;
struct UnifierSharedState; struct UnifierSharedState;
struct SourceModule;
struct Module;
void check(NotNull<BuiltinTypes> builtinTypes, NotNull<UnifierSharedState> sharedState, NotNull<TypeCheckLimits> limits, DcrLogger* logger, void check(NotNull<BuiltinTypes> builtinTypes, NotNull<UnifierSharedState> sharedState, NotNull<TypeCheckLimits> limits, DcrLogger* logger,
const SourceModule& sourceModule, Module* module); const SourceModule& sourceModule, Module* module);

View File

@ -12,32 +12,28 @@ namespace Luau
const void* ptr(TypeOrPack ty); const void* ptr(TypeOrPack ty);
template<typename T> template<typename T, typename std::enable_if_t<TypeOrPack::is_part_of_v<T>, bool> = true>
const T* get(TypeOrPack ty) const T* get(const TypeOrPack& tyOrTp)
{ {
if constexpr (std::is_same_v<T, TypeId>) return tyOrTp.get_if<T>();
return ty.get_if<TypeId>(); }
else if constexpr (std::is_same_v<T, TypePackId>)
return ty.get_if<TypePackId>(); template<typename T, typename std::enable_if_t<TypeVariant::is_part_of_v<T>, bool> = true>
else if constexpr (TypeVariant::is_part_of_v<T>) const T* get(const TypeOrPack& tyOrTp)
{ {
if (auto innerTy = ty.get_if<TypeId>()) if (const TypeId* ty = get<TypeId>(tyOrTp))
return get<T>(*innerTy); return get<T>(*ty);
else
return nullptr;
}
else if constexpr (TypePackVariant::is_part_of_v<T>)
{
if (auto innerTp = ty.get_if<TypePackId>())
return get<T>(*innerTp);
else
return nullptr;
}
else else
{ return nullptr;
static_assert(always_false_v<T>, "invalid T to get from TypeOrPack"); }
LUAU_UNREACHABLE();
} template<typename T, typename std::enable_if_t<TypePackVariant::is_part_of_v<T>, bool> = true>
const T* get(const TypeOrPack& tyOrTp)
{
if (const TypePackId* tp = get<TypePackId>(tyOrTp))
return get<T>(*tp);
else
return nullptr;
} }
TypeOrPack follow(TypeOrPack ty); TypeOrPack follow(TypeOrPack ty);

View File

@ -5,6 +5,7 @@
#include "Luau/BuiltinDefinitions.h" #include "Luau/BuiltinDefinitions.h"
#include "Luau/Frontend.h" #include "Luau/Frontend.h"
#include "Luau/ToString.h" #include "Luau/ToString.h"
#include "Luau/Subtyping.h"
#include "Luau/TypeInfer.h" #include "Luau/TypeInfer.h"
#include "Luau/TypePack.h" #include "Luau/TypePack.h"
@ -12,6 +13,7 @@
#include <unordered_set> #include <unordered_set>
#include <utility> #include <utility>
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
LUAU_FASTFLAG(DebugLuauReadWriteProperties); LUAU_FASTFLAG(DebugLuauReadWriteProperties);
LUAU_FASTFLAG(LuauClipExtraHasEndProps); LUAU_FASTFLAG(LuauClipExtraHasEndProps);
LUAU_FASTFLAGVARIABLE(LuauAutocompleteDoEnd, false); LUAU_FASTFLAGVARIABLE(LuauAutocompleteDoEnd, false);
@ -143,13 +145,24 @@ static bool checkTypeMatch(TypeId subTy, TypeId superTy, NotNull<Scope> scope, T
InternalErrorReporter iceReporter; InternalErrorReporter iceReporter;
UnifierSharedState unifierState(&iceReporter); UnifierSharedState unifierState(&iceReporter);
Normalizer normalizer{typeArena, builtinTypes, NotNull{&unifierState}}; Normalizer normalizer{typeArena, builtinTypes, NotNull{&unifierState}};
Unifier unifier(NotNull<Normalizer>{&normalizer}, scope, Location(), Variance::Covariant);
// Cost of normalization can be too high for autocomplete response time requirements if (FFlag::DebugLuauDeferredConstraintResolution)
unifier.normalize = false; {
unifier.checkInhabited = false; Subtyping subtyping{builtinTypes, NotNull{typeArena}, NotNull{&normalizer}, NotNull{&iceReporter}, scope};
return subtyping.isSubtype(subTy, superTy).isSubtype;
}
else
{
Unifier unifier(NotNull<Normalizer>{&normalizer}, scope, Location(), Variance::Covariant);
// Cost of normalization can be too high for autocomplete response time requirements
unifier.normalize = false;
unifier.checkInhabited = false;
return unifier.canUnify(subTy, superTy).empty();
}
return unifier.canUnify(subTy, superTy).empty();
} }
static TypeCorrectKind checkTypeCorrectKind( static TypeCorrectKind checkTypeCorrectKind(

View File

@ -7,7 +7,7 @@
#include "Luau/Common.h" #include "Luau/Common.h"
#include "Luau/ToString.h" #include "Luau/ToString.h"
#include "Luau/ConstraintSolver.h" #include "Luau/ConstraintSolver.h"
#include "Luau/ConstraintGraphBuilder.h" #include "Luau/ConstraintGenerator.h"
#include "Luau/NotNull.h" #include "Luau/NotNull.h"
#include "Luau/TypeInfer.h" #include "Luau/TypeInfer.h"
#include "Luau/TypeFamily.h" #include "Luau/TypeFamily.h"

View File

@ -14,7 +14,7 @@ LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
LUAU_FASTINTVARIABLE(LuauTypeCloneRecursionLimit, 300) LUAU_FASTINTVARIABLE(LuauTypeCloneRecursionLimit, 300)
LUAU_FASTFLAGVARIABLE(LuauCloneCyclicUnions, false) LUAU_FASTFLAGVARIABLE(LuauCloneCyclicUnions, false)
LUAU_FASTFLAGVARIABLE(LuauStacklessTypeClone2, false) LUAU_FASTFLAGVARIABLE(LuauStacklessTypeClone3, false)
LUAU_FASTINTVARIABLE(LuauTypeCloneIterationLimit, 100'000) LUAU_FASTINTVARIABLE(LuauTypeCloneIterationLimit, 100'000)
namespace Luau namespace Luau
@ -118,6 +118,8 @@ private:
ty = follow(ty, FollowOption::DisableLazyTypeThunks); ty = follow(ty, FollowOption::DisableLazyTypeThunks);
if (auto it = types->find(ty); it != types->end()) if (auto it = types->find(ty); it != types->end())
return it->second; return it->second;
else if (ty->persistent)
return ty;
return std::nullopt; return std::nullopt;
} }
@ -126,6 +128,8 @@ private:
tp = follow(tp); tp = follow(tp);
if (auto it = packs->find(tp); it != packs->end()) if (auto it = packs->find(tp); it != packs->end())
return it->second; return it->second;
else if (tp->persistent)
return tp;
return std::nullopt; return std::nullopt;
} }
@ -879,7 +883,7 @@ TypePackId clone(TypePackId tp, TypeArena& dest, CloneState& cloneState)
if (tp->persistent) if (tp->persistent)
return tp; return tp;
if (FFlag::LuauStacklessTypeClone2) if (FFlag::LuauStacklessTypeClone3)
{ {
TypeCloner2 cloner{NotNull{&dest}, cloneState.builtinTypes, NotNull{&cloneState.seenTypes}, NotNull{&cloneState.seenTypePacks}}; TypeCloner2 cloner{NotNull{&dest}, cloneState.builtinTypes, NotNull{&cloneState.seenTypes}, NotNull{&cloneState.seenTypePacks}};
return cloner.clone(tp); return cloner.clone(tp);
@ -905,7 +909,7 @@ TypeId clone(TypeId typeId, TypeArena& dest, CloneState& cloneState)
if (typeId->persistent) if (typeId->persistent)
return typeId; return typeId;
if (FFlag::LuauStacklessTypeClone2) if (FFlag::LuauStacklessTypeClone3)
{ {
TypeCloner2 cloner{NotNull{&dest}, cloneState.builtinTypes, NotNull{&cloneState.seenTypes}, NotNull{&cloneState.seenTypePacks}}; TypeCloner2 cloner{NotNull{&dest}, cloneState.builtinTypes, NotNull{&cloneState.seenTypes}, NotNull{&cloneState.seenTypePacks}};
return cloner.clone(typeId); 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) 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}}; 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 // 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/Ast.h"
#include "Luau/Def.h" #include "Luau/Def.h"
@ -126,21 +126,21 @@ struct Checkpoint
size_t offset; 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> 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) for (size_t i = start.offset; i < end.offset; ++i)
f(cgb->constraints[i]); f(cg->constraints[i]);
} }
} // namespace } // 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, NotNull<BuiltinTypes> builtinTypes, NotNull<InternalErrorReporter> ice, const ScopePtr& globalScope,
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope, DcrLogger* logger, NotNull<DataFlowGraph> dfg, std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope, DcrLogger* logger, NotNull<DataFlowGraph> dfg,
std::vector<RequireCycle> requireCycles) std::vector<RequireCycle> requireCycles)
@ -160,7 +160,7 @@ ConstraintGraphBuilder::ConstraintGraphBuilder(ModulePtr module, NotNull<Normali
LUAU_ASSERT(module); LUAU_ASSERT(module);
} }
void ConstraintGraphBuilder::visitModuleRoot(AstStatBlock* block) void ConstraintGenerator::visitModuleRoot(AstStatBlock* block)
{ {
LUAU_ASSERT(scopes.empty()); LUAU_ASSERT(scopes.empty());
LUAU_ASSERT(rootScope == nullptr); LUAU_ASSERT(rootScope == nullptr);
@ -181,18 +181,18 @@ void ConstraintGraphBuilder::visitModuleRoot(AstStatBlock* block)
logger->captureGenerationModule(module); logger->captureGenerationModule(module);
} }
TypeId ConstraintGraphBuilder::freshType(const ScopePtr& scope) TypeId ConstraintGenerator::freshType(const ScopePtr& scope)
{ {
return Luau::freshType(arena, builtinTypes, scope.get()); return Luau::freshType(arena, builtinTypes, scope.get());
} }
TypePackId ConstraintGraphBuilder::freshTypePack(const ScopePtr& scope) TypePackId ConstraintGenerator::freshTypePack(const ScopePtr& scope)
{ {
FreeTypePack f{scope.get()}; FreeTypePack f{scope.get()};
return arena->addTypePack(TypePackVar{std::move(f)}); 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); auto scope = std::make_shared<Scope>(parent);
scopes.emplace_back(node->location, scope); scopes.emplace_back(node->location, scope);
@ -206,17 +206,17 @@ ScopePtr ConstraintGraphBuilder::childScope(AstNode* node, const ScopePtr& paren
return scope; 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()}; 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()}; 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) { const auto intersect = [&](const std::vector<TypeId>& types) {
if (1 == types.size()) 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) if (!refinement)
return; return;
@ -382,7 +382,7 @@ bool mustDeferIntersection(TypeId ty)
} }
} // namespace } // namespace
void ConstraintGraphBuilder::applyRefinements(const ScopePtr& scope, Location location, RefinementId refinement) void ConstraintGenerator::applyRefinements(const ScopePtr& scope, Location location, RefinementId refinement)
{ {
if (!refinement) if (!refinement)
return; return;
@ -439,7 +439,7 @@ void ConstraintGraphBuilder::applyRefinements(const ScopePtr& scope, Location lo
addConstraint(scope, location, c); addConstraint(scope, location, c);
} }
ControlFlow ConstraintGraphBuilder::visitBlockWithoutChildScope(const ScopePtr& scope, AstStatBlock* block) ControlFlow ConstraintGenerator::visitBlockWithoutChildScope(const ScopePtr& scope, AstStatBlock* block)
{ {
RecursionCounter counter{&recursionCount}; RecursionCounter counter{&recursionCount};
@ -502,7 +502,7 @@ ControlFlow ConstraintGraphBuilder::visitBlockWithoutChildScope(const ScopePtr&
return firstControlFlow.value_or(ControlFlow::None); 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}; 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; std::vector<std::optional<TypeId>> varTypes;
varTypes.reserve(statLocal->vars.size); varTypes.reserve(statLocal->vars.size);
@ -663,7 +663,7 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocal* s
return ControlFlow::None; return ControlFlow::None;
} }
ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFor* for_) ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFor* for_)
{ {
TypeId annotationTy = builtinTypes->numberType; TypeId annotationTy = builtinTypes->numberType;
if (for_->var->annotation) if (for_->var->annotation)
@ -693,7 +693,7 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFor* for
return ControlFlow::None; return ControlFlow::None;
} }
ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatForIn* forIn) ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatForIn* forIn)
{ {
ScopePtr loopScope = childScope(forIn, scope); ScopePtr loopScope = childScope(forIn, scope);
@ -728,7 +728,7 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatForIn* f
return ControlFlow::None; 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; RefinementId refinement = check(scope, while_->condition).refinement;
@ -740,7 +740,7 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatWhile* w
return ControlFlow::None; return ControlFlow::None;
} }
ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatRepeat* repeat) ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatRepeat* repeat)
{ {
ScopePtr repeatScope = childScope(repeat, scope); ScopePtr repeatScope = childScope(repeat, scope);
@ -751,7 +751,7 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatRepeat*
return ControlFlow::None; return ControlFlow::None;
} }
ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocalFunction* function) ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocalFunction* function)
{ {
// Local // Local
// Global // Global
@ -801,7 +801,7 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocalFun
return ControlFlow::None; 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. // Name could be AstStatLocal, AstStatGlobal, AstStatIndexName.
// With or without self // With or without self
@ -846,7 +846,7 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFunction
else if (AstExprIndexName* indexName = function->name->as<AstExprIndexName>()) else if (AstExprIndexName* indexName = function->name->as<AstExprIndexName>())
{ {
Checkpoint check1 = checkpoint(this); Checkpoint check1 = checkpoint(this);
std::optional<TypeId> lvalueType = checkLValue(scope, indexName); std::optional<TypeId> lvalueType = checkLValue(scope, indexName, generalizedType);
LUAU_ASSERT(lvalueType); LUAU_ASSERT(lvalueType);
Checkpoint check2 = checkpoint(this); Checkpoint check2 = checkpoint(this);
@ -856,12 +856,9 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFunction
// TODO figure out how to populate the location field of the table Property. // TODO figure out how to populate the location field of the table Property.
if (lvalueType) if (lvalueType && *lvalueType != generalizedType)
{ {
if (get<FreeType>(*lvalueType)) addConstraint(scope, indexName->location, SubtypeConstraint{*lvalueType, generalizedType});
asMutable(*lvalueType)->ty.emplace<BoundType>(generalizedType);
else
addConstraint(scope, indexName->location, SubtypeConstraint{*lvalueType, generalizedType});
} }
} }
else if (AstExprError* err = function->name->as<AstExprError>()) else if (AstExprError* err = function->name->as<AstExprError>())
@ -900,7 +897,7 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFunction
return ControlFlow::None; 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 // At this point, the only way scope->returnType should have anything
// interesting in it is if the function has an explicit return annotation. // 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; return ControlFlow::Returns;
} }
ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatBlock* block) ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatBlock* block)
{ {
ScopePtr innerScope = childScope(block, scope); ScopePtr innerScope = childScope(block, scope);
@ -944,7 +941,7 @@ static void bindFreeType(TypeId a, TypeId b)
asMutable(b)->ty.emplace<BoundType>(a); 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; std::vector<std::optional<TypeId>> expectedTypes;
expectedTypes.reserve(assign->vars.size); expectedTypes.reserve(assign->vars.size);
@ -957,16 +954,7 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatAssign*
TypeId assignee = arena->addType(BlockedType{}); TypeId assignee = arena->addType(BlockedType{});
assignees.push_back(assignee); assignees.push_back(assignee);
std::optional<TypeId> upperBound = follow(checkLValue(scope, lvalue)); checkLValue(scope, lvalue, assignee);
if (upperBound)
{
if (get<FreeType>(*upperBound))
expectedTypes.push_back(std::nullopt);
else
expectedTypes.push_back(*upperBound);
addConstraint(scope, lvalue->location, SubtypeConstraint{assignee, *upperBound});
}
DefId def = dfg->getDef(lvalue); DefId def = dfg->getDef(lvalue);
scope->lvalueTypes[def] = assignee; scope->lvalueTypes[def] = assignee;
@ -979,14 +967,12 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatAssign*
return ControlFlow::None; 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}; AstExprBinary binop = AstExprBinary{assign->location, assign->op, assign->var, assign->value};
TypeId resultTy = check(scope, &binop).ty; 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); DefId def = dfg->getDef(assign->var);
scope->lvalueTypes[def] = resultTy; scope->lvalueTypes[def] = resultTy;
@ -994,7 +980,7 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatCompound
return ControlFlow::None; 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; RefinementId refinement = check(scope, ifStatement->condition, std::nullopt).refinement;
@ -1041,7 +1027,7 @@ static bool occursCheck(TypeId needle, TypeId haystack)
return false; return false;
} }
ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatTypeAlias* alias) ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeAlias* alias)
{ {
ScopePtr* defnScope = astTypeAliasDefiningScopes.find(alias); ScopePtr* defnScope = astTypeAliasDefiningScopes.find(alias);
@ -1090,7 +1076,7 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatTypeAlia
return ControlFlow::None; return ControlFlow::None;
} }
ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatDeclareGlobal* global) ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareGlobal* global)
{ {
LUAU_ASSERT(global->type); LUAU_ASSERT(global->type);
@ -1115,7 +1101,7 @@ static bool isMetamethod(const Name& name)
(FFlag::LuauFloorDivision && name == "__idiv"); (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); std::optional<TypeId> superTy = std::make_optional(builtinTypes->classType);
if (declaredClass->superName) if (declaredClass->superName)
@ -1234,7 +1220,7 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatDeclareC
return ControlFlow::None; 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, GenericTypeDefinition>> generics = createGenerics(scope, global->generics);
std::vector<std::pair<Name, GenericTypePackDefinition>> genericPacks = createGenericPacks(scope, global->genericPacks); 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; return ControlFlow::None;
} }
ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatError* error) ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatError* error)
{ {
for (AstStat* stat : error->statements) for (AstStat* stat : error->statements)
visit(scope, stat); visit(scope, stat);
@ -1289,7 +1275,7 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatError* e
return ControlFlow::None; return ControlFlow::None;
} }
InferencePack ConstraintGraphBuilder::checkPack( InferencePack ConstraintGenerator::checkPack(
const ScopePtr& scope, AstArray<AstExpr*> exprs, const std::vector<std::optional<TypeId>>& expectedTypes) const ScopePtr& scope, AstArray<AstExpr*> exprs, const std::vector<std::optional<TypeId>>& expectedTypes)
{ {
std::vector<TypeId> head; std::vector<TypeId> head;
@ -1320,7 +1306,7 @@ InferencePack ConstraintGraphBuilder::checkPack(
return InferencePack{arena->addTypePack(TypePack{std::move(head), tail})}; 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) const ScopePtr& scope, AstExpr* expr, const std::vector<std::optional<TypeId>>& expectedTypes, bool generalize)
{ {
RecursionCounter counter{&recursionCount}; RecursionCounter counter{&recursionCount};
@ -1356,7 +1342,7 @@ InferencePack ConstraintGraphBuilder::checkPack(
return result; return result;
} }
InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCall* call) InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall* call)
{ {
std::vector<AstExpr*> exprArgs; 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) const ScopePtr& scope, AstExpr* expr, std::optional<TypeId> expectedType, bool forceSingleton, bool generalize)
{ {
RecursionCounter counter{&recursionCount}; RecursionCounter counter{&recursionCount};
@ -1600,7 +1586,7 @@ Inference ConstraintGraphBuilder::check(
return result; 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) if (forceSingleton)
return Inference{arena->addType(SingletonType{StringSingleton{std::string{string->value.data, string->value.size}}})}; 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}; 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; const TypeId singletonType = boolExpr->value ? builtinTypes->trueType : builtinTypes->falseType;
if (forceSingleton) if (forceSingleton)
@ -1649,7 +1635,7 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprConstantBo
return Inference{builtinTypes->booleanType}; 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); const RefinementKey* key = dfg->getRefinementKey(local);
std::optional<DefId> rvalueDef = dfg->getRValueDefForCompoundAssign(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)}; return Inference{ty, refinementArena.proposition(key, builtinTypes->truthyType)};
} }
else 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); const RefinementKey* key = dfg->getRefinementKey(global);
std::optional<DefId> rvalueDef = dfg->getRValueDefForCompoundAssign(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 obj = check(scope, indexName->expr).ty;
TypeId result = arena->addType(BlockedType{}); TypeId result = arena->addType(BlockedType{});
@ -1726,7 +1712,7 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIndexName*
return Inference{result}; 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 obj = check(scope, indexExpr->expr).ty;
TypeId indexType = check(scope, indexExpr->index).ty; TypeId indexType = check(scope, indexExpr->index).ty;
@ -1752,7 +1738,7 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIndexExpr*
return Inference{result}; 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); Checkpoint startCheckpoint = checkpoint(this);
FunctionSignature sig = checkFunctionSignature(scope, func, expectedType); 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); 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); 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); ScopePtr condScope = childScope(ifElse->condition, scope);
RefinementId refinement = check(condScope, ifElse->condition).refinement; 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}; 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); check(scope, typeAssert->expr, std::nullopt);
return Inference{resolveType(scope, typeAssert->annotation, /* inTypeArguments */ false)}; 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) for (AstExpr* expr : interpString->expressions)
check(scope, expr); check(scope, expr);
@ -2020,7 +2006,7 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprInterpStri
return Inference{builtinTypes->stringType}; 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) const ScopePtr& scope, AstExprBinary* binary, std::optional<TypeId> expectedType)
{ {
if (binary->op == AstExprBinary::And) 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>()) if (auto local = expr->as<AstExprLocal>())
return checkLValue(scope, local); return checkLValue(scope, local, assignedTy);
else if (auto global = expr->as<AstExprGlobal>()) else if (auto global = expr->as<AstExprGlobal>())
return checkLValue(scope, global); return checkLValue(scope, global, assignedTy);
else if (auto indexName = expr->as<AstExprIndexName>()) else if (auto indexName = expr->as<AstExprIndexName>())
return checkLValue(scope, indexName); return checkLValue(scope, indexName, assignedTy);
else if (auto indexExpr = expr->as<AstExprIndexExpr>()) else if (auto indexExpr = expr->as<AstExprIndexExpr>())
return checkLValue(scope, indexExpr); return checkLValue(scope, indexExpr, assignedTy);
else if (auto error = expr->as<AstExprError>()) else if (auto error = expr->as<AstExprError>())
{ {
check(scope, error); check(scope, error);
@ -2152,7 +2138,7 @@ std::optional<TypeId> ConstraintGraphBuilder::checkLValue(const ScopePtr& scope,
ice->ice("checkLValue is inexhaustive"); 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 * 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. * populated by symbols that have type annotations.
* *
* If this local has an interesting type annotation, it is important that we * 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); std::optional<TypeId> annotatedTy = scope->lookup(local->local);
if (annotatedTy) if (annotatedTy)
{
addConstraint(scope, local->location, SubtypeConstraint{assignedTy, *annotatedTy});
return annotatedTy; return annotatedTy;
}
/* /*
* As a safety measure, we'll assert that no type has yet been ascribed to * 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; 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}); 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); return updateProperty(scope, indexExpr, assignedTy);
}
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;
} }
/** /**
@ -2212,8 +2186,19 @@ static bool isIndexNameEquivalent(AstExpr* expr)
* *
* If expr has the form name.a.b.c * If expr has the form name.a.b.c
*/ */
TypeId ConstraintGraphBuilder::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>()) 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 // 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 resultType = arena->addType(BlockedType{});
TypeId subjectType = check(scope, indexExpr->expr).ty; TypeId subjectType = check(scope, indexExpr->expr).ty;
TypeId indexType = check(scope, indexExpr->index).ty; TypeId indexType = check(scope, indexExpr->index).ty;
TypeId propType = arena->addType(BlockedType{}); addConstraint(scope, expr->location, SetIndexerConstraint{resultType, subjectType, indexType, assignedTy});
addConstraint(scope, expr->location, SetIndexerConstraint{resultType, subjectType, indexType, propType});
module->astTypes[expr] = propType; module->astTypes[expr] = assignedTy;
return propType; return assignedTy;
} }
else if (!isIndexNameEquivalent(expr))
return check(scope, expr).ty;
Symbol sym; Symbol sym;
const Def* def = nullptr; const Def* def = nullptr;
@ -2269,21 +2251,24 @@ TypeId ConstraintGraphBuilder::updateProperty(const ScopePtr& scope, AstExpr* ex
} }
else if (auto indexExpr = e->as<AstExprIndexExpr>()) 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>()) 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)); segments.push_back(std::string(strIndex->value.data, strIndex->value.size));
exprs.push_back(e); exprs.push_back(e);
e = indexExpr->expr; e = indexExpr->expr;
} }
else else
{ {
return check(scope, expr).ty; return fallback();
} }
} }
else else
return check(scope, expr).ty; {
return fallback();
}
} }
LUAU_ASSERT(!segments.empty()); LUAU_ASSERT(!segments.empty());
@ -2294,16 +2279,14 @@ TypeId ConstraintGraphBuilder::updateProperty(const ScopePtr& scope, AstExpr* ex
LUAU_ASSERT(def); LUAU_ASSERT(def);
std::optional<std::pair<TypeId, Scope*>> lookupResult = scope->lookupEx(NotNull{def}); std::optional<std::pair<TypeId, Scope*>> lookupResult = scope->lookupEx(NotNull{def});
if (!lookupResult) if (!lookupResult)
return check(scope, expr).ty; return fallback();
const auto [subjectType, subjectScope] = *lookupResult; const auto [subjectType, subjectScope] = *lookupResult;
TypeId propTy = freshType(scope);
std::vector<std::string> segmentStrings(begin(segments), end(segments)); std::vector<std::string> segmentStrings(begin(segments), end(segments));
TypeId updatedType = arena->addType(BlockedType{}); 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; TypeId prevSegmentTy = updatedType;
for (size_t i = 0; i < segments.size(); ++i) 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>()) 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)); const bool expectedTypeIsFree = expectedType && get<FreeType>(follow(*expectedType));
@ -2462,7 +2445,7 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprTable* exp
return Inference{ty}; return Inference{ty};
} }
ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionSignature( ConstraintGenerator::FunctionSignature ConstraintGenerator::checkFunctionSignature(
const ScopePtr& parent, AstExprFunction* fn, std::optional<TypeId> expectedType, std::optional<Location> originalName) const ScopePtr& parent, AstExprFunction* fn, std::optional<TypeId> expectedType, std::optional<Location> originalName)
{ {
ScopePtr signatureScope = nullptr; 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); visitBlockWithoutChildScope(scope, fn->body);
@ -2662,12 +2645,12 @@ void ConstraintGraphBuilder::checkFunctionBody(const ScopePtr& scope, AstExprFun
if (nullptr != getFallthrough(fn->body)) 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}); 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; TypeId result = nullptr;
@ -2895,7 +2878,7 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, b
return result; 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; TypePackId result;
if (auto expl = tp->as<AstTypePackExplicit>()) if (auto expl = tp->as<AstTypePackExplicit>())
@ -2929,7 +2912,7 @@ TypePackId ConstraintGraphBuilder::resolveTypePack(const ScopePtr& scope, AstTyp
return result; 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; std::vector<TypeId> head;
@ -2947,7 +2930,7 @@ TypePackId ConstraintGraphBuilder::resolveTypePack(const ScopePtr& scope, const
return arena->addTypePack(TypePack{head, tail}); 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) const ScopePtr& scope, AstArray<AstGenericType> generics, bool useCache, bool addTypes)
{ {
std::vector<std::pair<Name, GenericTypeDefinition>> result; std::vector<std::pair<Name, GenericTypeDefinition>> result;
@ -2977,7 +2960,7 @@ std::vector<std::pair<Name, GenericTypeDefinition>> ConstraintGraphBuilder::crea
return result; 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) const ScopePtr& scope, AstArray<AstGenericTypePack> generics, bool useCache, bool addTypes)
{ {
std::vector<std::pair<Name, GenericTypePackDefinition>> result; std::vector<std::pair<Name, GenericTypePackDefinition>> result;
@ -3008,7 +2991,7 @@ std::vector<std::pair<Name, GenericTypePackDefinition>> ConstraintGraphBuilder::
return result; 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; const auto& [tp, refinements] = pack;
RefinementId refinement = nullptr; RefinementId refinement = nullptr;
@ -3025,7 +3008,7 @@ Inference ConstraintGraphBuilder::flattenPack(const ScopePtr& scope, Location lo
return Inference{typeResult, refinement}; 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)}); 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()); logger->captureGenerationError(errors.back());
} }
void ConstraintGraphBuilder::reportCodeTooComplex(Location location) void ConstraintGenerator::reportCodeTooComplex(Location location)
{ {
errors.push_back(TypeError{location, module->name, CodeTooComplex{}}); 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}; GlobalPrepopulator gp{NotNull{globalScope.get()}, arena, dfg};
@ -3079,7 +3062,7 @@ void ConstraintGraphBuilder::prepopulateGlobalScope(const ScopePtr& globalScope,
program->visit(&gp); 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) 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; std::vector<TypeId> funTys;
if (auto it = get<IntersectionType>(follow(fnType))) 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 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; 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) 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 (isCompoundAssignment)
{ {
if (auto def = scope->lookup(l->local)) 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. // 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) 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 (isCompoundAssignment)
{ {
if (auto def = scope->lookup(g->name)) 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. // 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/Clone.h"
#include "Luau/Common.h" #include "Luau/Common.h"
#include "Luau/Config.h" #include "Luau/Config.h"
#include "Luau/ConstraintGraphBuilder.h" #include "Luau/ConstraintGenerator.h"
#include "Luau/ConstraintSolver.h" #include "Luau/ConstraintSolver.h"
#include "Luau/DataFlowGraph.h" #include "Luau/DataFlowGraph.h"
#include "Luau/DcrLogger.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}}; 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}; logger.get(), NotNull{&dfg}, requireCycles};
cgb.visitModuleRoot(sourceModule.root); cg.visitModuleRoot(sourceModule.root);
result->errors = std::move(cgb.errors); 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}; requireCycles, logger.get(), limits};
if (options.randomizeConstraintResolutionSeed) if (options.randomizeConstraintResolutionSeed)
@ -1283,7 +1283,7 @@ ModulePtr check(const SourceModule& sourceModule, Mode mode, const std::vector<R
for (TypeError& e : cs.errors) for (TypeError& e : cs.errors)
result->errors.emplace_back(std::move(e)); result->errors.emplace_back(std::move(e));
result->scopes = std::move(cgb.scopes); result->scopes = std::move(cg.scopes);
result->type = sourceModule.type; result->type = sourceModule.type;
result->clonePublicInterface(builtinTypes, *iceHandler); result->clonePublicInterface(builtinTypes, *iceHandler);

View File

@ -14,9 +14,6 @@
LUAU_FASTINTVARIABLE(LuauSuggestionDistance, 4) LUAU_FASTINTVARIABLE(LuauSuggestionDistance, 4)
LUAU_FASTFLAGVARIABLE(LuauLintDeprecatedFenv, false)
LUAU_FASTFLAGVARIABLE(LuauLintTableIndexer, false)
namespace Luau 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 // 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 // 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 // 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")) if (AstExprGlobal* fenv = node->func->as<AstExprGlobal>(); fenv && (fenv->name == "getfenv" || fenv->name == "setfenv"))
{ {
@ -2185,7 +2182,7 @@ private:
bool visit(AstExprUnary* node) override bool visit(AstExprUnary* node) override
{ {
if (FFlag::LuauLintTableIndexer && node->op == AstExprUnary::Len) if (node->op == AstExprUnary::Len)
checkIndexer(node, node->expr, "#"); checkIndexer(node, node->expr, "#");
return true; return true;
@ -2195,7 +2192,7 @@ private:
{ {
if (AstExprGlobal* func = node->func->as<AstExprGlobal>()) 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"); checkIndexer(node, node->args.data[0], "ipairs");
} }
else if (AstExprIndexName* func = node->func->as<AstExprIndexName>()) else if (AstExprIndexName* func = node->func->as<AstExprIndexName>())
@ -2209,8 +2206,6 @@ private:
void checkIndexer(AstExpr* node, AstExpr* expr, const char* op) void checkIndexer(AstExpr* node, AstExpr* expr, const char* op)
{ {
LUAU_ASSERT(FFlag::LuauLintTableIndexer);
std::optional<Luau::TypeId> ty = context->getType(expr); std::optional<Luau::TypeId> ty = context->getType(expr);
if (!ty) if (!ty)
return; return;
@ -2653,13 +2648,17 @@ private:
case ConstantNumberParseResult::Ok: case ConstantNumberParseResult::Ok:
case ConstantNumberParseResult::Malformed: case ConstantNumberParseResult::Malformed:
break; 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: case ConstantNumberParseResult::BinOverflow:
emitWarning(*context, LintWarning::Code_IntegerParsing, node->location, 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; break;
case ConstantNumberParseResult::HexOverflow: case ConstantNumberParseResult::HexOverflow:
emitWarning(*context, LintWarning::Code_IntegerParsing, node->location, emitWarning(*context, LintWarning::Code_IntegerParsing, node->location,
"Hexadecimal number literal exceeded available precision and has been truncated to 2^64"); "Hexadecimal number literal exceeded available precision and was truncated to 2^64");
break; break;
} }

View File

@ -3,7 +3,7 @@
#include "Luau/Clone.h" #include "Luau/Clone.h"
#include "Luau/Common.h" #include "Luau/Common.h"
#include "Luau/ConstraintGraphBuilder.h" #include "Luau/ConstraintGenerator.h"
#include "Luau/Normalize.h" #include "Luau/Normalize.h"
#include "Luau/RecursionCounter.h" #include "Luau/RecursionCounter.h"
#include "Luau/Scope.h" #include "Luau/Scope.h"

View File

@ -8,7 +8,9 @@
#include "Luau/Clone.h" #include "Luau/Clone.h"
#include "Luau/Common.h" #include "Luau/Common.h"
#include "Luau/RecursionCounter.h" #include "Luau/RecursionCounter.h"
#include "Luau/Subtyping.h"
#include "Luau/Type.h" #include "Luau/Type.h"
#include "Luau/TypeFwd.h"
#include "Luau/Unifier.h" #include "Luau/Unifier.h"
LUAU_FASTFLAGVARIABLE(DebugLuauCheckNormalizeInvariant, false) LUAU_FASTFLAGVARIABLE(DebugLuauCheckNormalizeInvariant, false)
@ -19,6 +21,7 @@ LUAU_FASTINTVARIABLE(LuauNormalizeCacheLimit, 100000);
LUAU_FASTFLAGVARIABLE(LuauNormalizeCyclicUnions, false); LUAU_FASTFLAGVARIABLE(LuauNormalizeCyclicUnions, false);
LUAU_FASTFLAG(LuauTransitiveSubtyping) LUAU_FASTFLAG(LuauTransitiveSubtyping)
LUAU_FASTFLAG(DebugLuauReadWriteProperties) LUAU_FASTFLAG(DebugLuauReadWriteProperties)
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
namespace Luau namespace Luau
{ {
@ -32,9 +35,14 @@ TypeIds::TypeIds(std::initializer_list<TypeId> tys)
void TypeIds::insert(TypeId ty) void TypeIds::insert(TypeId ty)
{ {
ty = follow(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); order.push_back(ty);
hash ^= std::hash<TypeId>{}(ty); hash ^= std::hash<TypeId>{}(ty);
} }
@ -75,25 +83,26 @@ TypeIds::const_iterator TypeIds::end() const
TypeIds::iterator TypeIds::erase(TypeIds::const_iterator it) TypeIds::iterator TypeIds::erase(TypeIds::const_iterator it)
{ {
TypeId ty = *it; TypeId ty = *it;
types.erase(ty); types[ty] = false;
hash ^= std::hash<TypeId>{}(ty); hash ^= std::hash<TypeId>{}(ty);
return order.erase(it); return order.erase(it);
} }
size_t TypeIds::size() const size_t TypeIds::size() const
{ {
return types.size(); return order.size();
} }
bool TypeIds::empty() const bool TypeIds::empty() const
{ {
return types.empty(); return order.empty();
} }
size_t TypeIds::count(TypeId ty) const size_t TypeIds::count(TypeId ty) const
{ {
ty = follow(ty); ty = follow(ty);
return types.count(ty); const bool* val = types.find(ty);
return (val && *val) ? 1 : 0;
} }
void TypeIds::retain(const TypeIds& there) void TypeIds::retain(const TypeIds& there)
@ -122,7 +131,29 @@ bool TypeIds::isNever() const
bool TypeIds::operator==(const TypeIds& there) 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() {} 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 bool NormalizedType::isExactlyNumber() const
{ {
return hasNumbers() && !hasTops() && !hasBooleans() && !hasClasses() && !hasErrors() && !hasNils() && !hasStrings() && !hasThreads() && return hasNumbers() && !hasTops() && !hasBooleans() && !hasClasses() && !hasErrors() && !hasNils() && !hasStrings() && !hasThreads() &&
@ -647,8 +714,7 @@ static bool areNormalizedClasses(const NormalizedClassType& tys)
static bool isPlainTyvar(TypeId ty) static bool isPlainTyvar(TypeId ty)
{ {
return (get<FreeType>(ty) || get<GenericType>(ty) || get<BlockedType>(ty) || return (get<FreeType>(ty) || get<GenericType>(ty) || get<BlockedType>(ty) || get<PendingExpansionType>(ty) || get<TypeFamilyInstanceType>(ty));
get<PendingExpansionType>(ty) || get<TypeFamilyInstanceType>(ty));
} }
static bool isNormalizedTyvar(const NormalizedTyvars& tyvars) static bool isNormalizedTyvar(const NormalizedTyvars& tyvars)
@ -711,6 +777,11 @@ const NormalizedType* Normalizer::normalize(TypeId ty)
std::unordered_set<TypeId> seenSetTypes; std::unordered_set<TypeId> seenSetTypes;
if (!unionNormalWithTy(norm, ty, seenSetTypes)) if (!unionNormalWithTy(norm, ty, seenSetTypes))
return nullptr; return nullptr;
if (norm.isUnknown())
{
clearNormal(norm);
norm.tops = builtinTypes->unknownType;
}
std::unique_ptr<NormalizedType> uniq = std::make_unique<NormalizedType>(std::move(norm)); std::unique_ptr<NormalizedType> uniq = std::make_unique<NormalizedType>(std::move(norm));
const NormalizedType* result = uniq.get(); const NormalizedType* result = uniq.get();
cachedNormals[ty] = std::move(uniq); 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)) else if (FFlag::LuauTransitiveSubtyping && get<UnknownType>(here.tops))
return true; return true;
else if (get<GenericType>(there) || get<FreeType>(there) || get<BlockedType>(there) || else if (get<GenericType>(there) || get<FreeType>(there) || get<BlockedType>(there) || get<PendingExpansionType>(there) ||
get<PendingExpansionType>(there) || get<TypeFamilyInstanceType>(there)) get<TypeFamilyInstanceType>(there))
{ {
if (tyvarIndex(there) <= ignoreSmallerTyvars) if (tyvarIndex(there) <= ignoreSmallerTyvars)
return true; return true;
@ -2661,8 +2732,8 @@ bool Normalizer::intersectNormalWithTy(NormalizedType& here, TypeId there, std::
return false; return false;
return true; return true;
} }
else if (get<GenericType>(there) || get<FreeType>(there) || get<BlockedType>(there) || else if (get<GenericType>(there) || get<FreeType>(there) || get<BlockedType>(there) || get<PendingExpansionType>(there) ||
get<PendingExpansionType>(there) || get<TypeFamilyInstanceType>(there)) get<TypeFamilyInstanceType>(there))
{ {
NormalizedType thereNorm{builtinTypes}; NormalizedType thereNorm{builtinTypes};
NormalizedType topNorm{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) 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); return isConsistentSubtype(subTy, superTy, scope, builtinTypes, ice);
UnifierSharedState sharedState{&ice}; UnifierSharedState sharedState{&ice};
TypeArena arena; TypeArena arena;
Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}}; Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}};
Unifier u{NotNull{&normalizer}, scope, Location{}, Covariant};
u.tryUnify(subTy, superTy); // Subtyping under DCR is not implemented using unification!
return !u.failure; 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) 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); return isConsistentSubtype(subPack, superPack, scope, builtinTypes, ice);
UnifierSharedState sharedState{&ice}; UnifierSharedState sharedState{&ice};
TypeArena arena; TypeArena arena;
Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}}; Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}};
Unifier u{NotNull{&normalizer}, scope, Location{}, Covariant};
u.tryUnify(subPack, superPack); // Subtyping under DCR is not implemented using unification!
return !u.failure; 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) bool isConsistentSubtype(TypeId subTy, TypeId superTy, NotNull<Scope> scope, NotNull<BuiltinTypes> builtinTypes, InternalErrorReporter& ice)
{ {
LUAU_ASSERT(!FFlag::DebugLuauDeferredConstraintResolution);
UnifierSharedState sharedState{&ice}; UnifierSharedState sharedState{&ice};
TypeArena arena; TypeArena arena;
Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}}; Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}};
@ -2954,6 +3051,8 @@ bool isConsistentSubtype(TypeId subTy, TypeId superTy, NotNull<Scope> scope, Not
bool isConsistentSubtype( bool isConsistentSubtype(
TypePackId subPack, TypePackId superPack, NotNull<Scope> scope, NotNull<BuiltinTypes> builtinTypes, InternalErrorReporter& ice) TypePackId subPack, TypePackId superPack, NotNull<Scope> scope, NotNull<BuiltinTypes> builtinTypes, InternalErrorReporter& ice)
{ {
LUAU_ASSERT(!FFlag::DebugLuauDeferredConstraintResolution);
UnifierSharedState sharedState{&ice}; UnifierSharedState sharedState{&ice};
TypeArena arena; TypeArena arena;
Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}}; 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)) if (auto subUnion = get<UnionType>(subTy))
result = isCovariantWith(env, subUnion, superTy); result = isCovariantWith(env, subUnion, superTy);
else if (auto superUnion = get<UnionType>(superTy)) else if (auto superUnion = get<UnionType>(superTy))
{
result = isCovariantWith(env, subTy, superUnion); 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)) else if (auto superIntersection = get<IntersectionType>(superTy))
result = isCovariantWith(env, subTy, superIntersection); result = isCovariantWith(env, subTy, superIntersection);
else if (auto subIntersection = get<IntersectionType>(subTy)) else if (auto subIntersection = get<IntersectionType>(subTy))
{ {
result = isCovariantWith(env, subIntersection, superTy); result = isCovariantWith(env, subIntersection, superTy);
if (!result.isSubtype && !result.isErrorSuppressing && !result.normalizationTooComplex) 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)) else if (get<AnyType>(superTy))
result = {true}; result = {true};

View File

@ -2413,6 +2413,31 @@ struct TypeChecker2
} }
} }
void explainError(TypeId subTy, TypeId superTy, Location location, const SubtypingResult& r)
{
if (!r.reasoning)
return reportError(TypeMismatch{superTy, subTy}, location);
std::optional<TypeOrPack> subLeaf = traverse(subTy, r.reasoning->subPath, builtinTypes);
std::optional<TypeOrPack> superLeaf = traverse(superTy, r.reasoning->superPath, builtinTypes);
if (!subLeaf || !superLeaf)
ice->ice("Subtyping test returned a reasoning with an invalid path", location);
if (!get2<TypeId, TypeId>(*subLeaf, *superLeaf) && !get2<TypePackId, TypePackId>(*subLeaf, *superLeaf))
ice->ice("Subtyping test returned a reasoning where one path ends at a type and the other ends at a pack.", location);
std::string reason;
if (r.reasoning->subPath == r.reasoning->superPath)
reason = "at " + toString(r.reasoning->subPath) + ", " + toString(*subLeaf) + " is not a subtype of " + toString(*superLeaf);
else
reason = "type " + toString(subTy) + toString(r.reasoning->subPath) + " (" + toString(*subLeaf) + ") is not a subtype of " +
toString(superTy) + toString(r.reasoning->superPath) + " (" + toString(*superLeaf) + ")";
reportError(TypeMismatch{superTy, subTy, reason}, location);
}
bool testIsSubtype(TypeId subTy, TypeId superTy, Location location) bool testIsSubtype(TypeId subTy, TypeId superTy, Location location)
{ {
SubtypingResult r = subtyping->isSubtype(subTy, superTy); SubtypingResult r = subtyping->isSubtype(subTy, superTy);
@ -2421,27 +2446,7 @@ struct TypeChecker2
reportError(NormalizationTooComplex{}, location); reportError(NormalizationTooComplex{}, location);
if (!r.isSubtype && !r.isErrorSuppressing) if (!r.isSubtype && !r.isErrorSuppressing)
{ explainError(subTy, superTy, location, r);
if (r.reasoning)
{
std::optional<TypeOrPack> subLeaf = traverse(subTy, r.reasoning->subPath, builtinTypes);
std::optional<TypeOrPack> superLeaf = traverse(superTy, r.reasoning->superPath, builtinTypes);
if (!subLeaf || !superLeaf)
ice->ice("Subtyping test returned a reasoning with an invalid path", location);
if (!get2<TypeId, TypeId>(*subLeaf, *superLeaf) && !get2<TypePackId, TypePackId>(*subLeaf, *superLeaf))
ice->ice("Subtyping test returned a reasoning where one path ends at a type and the other ends at a pack.", location);
std::string reason = "type " + toString(subTy) + toString(r.reasoning->subPath) + " (" + toString(*subLeaf) +
") is not a subtype of " + toString(superTy) + toString(r.reasoning->superPath) + " (" + toString(*superLeaf) +
")";
reportError(TypeMismatch{superTy, subTy, reason}, location);
}
else
reportError(TypeMismatch{superTy, subTy}, location);
}
return r.isSubtype; return r.isSubtype;
} }

View File

@ -35,11 +35,9 @@ LUAU_FASTFLAG(LuauKnowsTheDataModel3)
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false) LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false)
LUAU_FASTFLAGVARIABLE(DebugLuauSharedSelf, false) LUAU_FASTFLAGVARIABLE(DebugLuauSharedSelf, false)
LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAGVARIABLE(LuauAllowIndexClassParameters, false)
LUAU_FASTFLAG(LuauOccursIsntAlwaysFailure) LUAU_FASTFLAG(LuauOccursIsntAlwaysFailure)
LUAU_FASTFLAGVARIABLE(LuauTinyControlFlowAnalysis, false) LUAU_FASTFLAGVARIABLE(LuauTinyControlFlowAnalysis, false)
LUAU_FASTFLAGVARIABLE(LuauLoopControlFlowAnalysis, false) LUAU_FASTFLAGVARIABLE(LuauLoopControlFlowAnalysis, false)
LUAU_FASTFLAGVARIABLE(LuauVariadicOverloadFix, false)
LUAU_FASTFLAGVARIABLE(LuauAlwaysCommitInferencesOfFunctionCalls, false) LUAU_FASTFLAGVARIABLE(LuauAlwaysCommitInferencesOfFunctionCalls, false)
LUAU_FASTFLAG(LuauParseDeclareClassIndexer) LUAU_FASTFLAG(LuauParseDeclareClassIndexer)
LUAU_FASTFLAG(LuauFloorDivision); LUAU_FASTFLAG(LuauFloorDivision);
@ -3412,15 +3410,12 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex
} }
} }
if (FFlag::LuauAllowIndexClassParameters) if (const ClassType* exprClass = get<ClassType>(exprType))
{ {
if (const ClassType* exprClass = get<ClassType>(exprType)) if (isNonstrictMode())
{ return unknownType;
if (isNonstrictMode()) reportError(TypeError{expr.location, DynamicPropertyLookupOnClassesUnsafe{exprType}});
return unknownType; return errorRecoveryType(scope);
reportError(TypeError{expr.location, DynamicPropertyLookupOnClassesUnsafe{exprType}});
return errorRecoveryType(scope);
}
} }
} }
@ -4026,13 +4021,9 @@ void TypeChecker::checkArgumentList(const ScopePtr& scope, const AstExpr& funNam
if (argIndex < argLocations.size()) if (argIndex < argLocations.size())
location = argLocations[argIndex]; location = argLocations[argIndex];
if (FFlag::LuauVariadicOverloadFix) state.location = location;
{ state.tryUnify(*argIter, vtp->ty);
state.location = location;
state.tryUnify(*argIter, vtp->ty);
}
else
unify(*argIter, vtp->ty, scope, location);
++argIter; ++argIter;
++argIndex; ++argIndex;
} }

View File

@ -18,7 +18,6 @@
LUAU_FASTINT(LuauTypeInferTypePackLoopLimit) LUAU_FASTINT(LuauTypeInferTypePackLoopLimit)
LUAU_FASTFLAG(LuauErrorRecoveryType) LUAU_FASTFLAG(LuauErrorRecoveryType)
LUAU_FASTFLAGVARIABLE(LuauInstantiateInSubtyping, false) LUAU_FASTFLAGVARIABLE(LuauInstantiateInSubtyping, false)
LUAU_FASTFLAGVARIABLE(LuauMaintainScopesInUnifier, false)
LUAU_FASTFLAGVARIABLE(LuauTransitiveSubtyping, false) LUAU_FASTFLAGVARIABLE(LuauTransitiveSubtyping, false)
LUAU_FASTFLAGVARIABLE(LuauOccursIsntAlwaysFailure, false) LUAU_FASTFLAGVARIABLE(LuauOccursIsntAlwaysFailure, false)
LUAU_FASTFLAG(LuauAlwaysCommitInferencesOfFunctionCalls) LUAU_FASTFLAG(LuauAlwaysCommitInferencesOfFunctionCalls)
@ -1514,7 +1513,7 @@ struct WeirdIter
auto freePack = log.getMutable<FreeTypePack>(packId); auto freePack = log.getMutable<FreeTypePack>(packId);
level = freePack->level; level = freePack->level;
if (FFlag::LuauMaintainScopesInUnifier && freePack->scope != nullptr) if (freePack->scope != nullptr)
scope = freePack->scope; scope = freePack->scope;
log.replace(packId, BoundTypePack(newTail)); log.replace(packId, BoundTypePack(newTail));
packId = newTail; packId = newTail;
@ -1679,11 +1678,8 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
auto superIter = WeirdIter(superTp, log); auto superIter = WeirdIter(superTp, log);
auto subIter = WeirdIter(subTp, log); auto subIter = WeirdIter(subTp, log);
if (FFlag::LuauMaintainScopesInUnifier) superIter.scope = scope.get();
{ subIter.scope = scope.get();
superIter.scope = scope.get();
subIter.scope = scope.get();
}
auto mkFreshType = [this](Scope* scope, TypeLevel level) { auto mkFreshType = [this](Scope* scope, TypeLevel level) {
if (FFlag::DebugLuauDeferredConstraintResolution) if (FFlag::DebugLuauDeferredConstraintResolution)

View File

@ -249,6 +249,7 @@ public:
enum class ConstantNumberParseResult enum class ConstantNumberParseResult
{ {
Ok, Ok,
Imprecise,
Malformed, Malformed,
BinOverflow, BinOverflow,
HexOverflow, 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 // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once #pragma once
#include <string>
namespace Luau namespace Luau
{ {
@ -9,7 +8,11 @@ struct Position
{ {
unsigned int line, column; 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;
bool operator!=(const Position& rhs) const; bool operator!=(const Position& rhs) const;
@ -25,10 +28,29 @@ struct Location
{ {
Position begin, end; Position begin, end;
Location(); Location()
Location(const Position& begin, const Position& end); : begin(0, 0)
Location(const Position& begin, unsigned int length); , end(0, 0)
Location(const Location& begin, const Location& end); {
}
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;
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 // 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 "Luau/Location.h"
#include <string>
namespace Luau namespace Luau
{ {
Position::Position(unsigned int line, unsigned int column)
: line(line)
, column(column)
{
}
bool Position::operator==(const Position& rhs) const bool Position::operator==(const Position& rhs) const
{ {
return this->column == rhs.column && this->line == rhs.line; 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 bool Location::operator==(const Location& rhs) const
{ {
return this->begin == rhs.begin && this->end == rhs.end; return this->begin == rhs.begin && this->end == rhs.end;

View File

@ -24,6 +24,8 @@ LUAU_FASTFLAG(LuauCheckedFunctionSyntax)
LUAU_FASTFLAGVARIABLE(LuauBetterTypeUnionLimits, false) LUAU_FASTFLAGVARIABLE(LuauBetterTypeUnionLimits, false)
LUAU_FASTFLAGVARIABLE(LuauBetterTypeRecLimits, false) LUAU_FASTFLAGVARIABLE(LuauBetterTypeRecLimits, false)
LUAU_FASTFLAGVARIABLE(LuauParseImpreciseNumber, false)
namespace Luau namespace Luau
{ {
@ -2187,6 +2189,12 @@ static ConstantNumberParseResult parseInteger(double& result, const char* data,
return base == 2 ? ConstantNumberParseResult::BinOverflow : ConstantNumberParseResult::HexOverflow; 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; return ConstantNumberParseResult::Ok;
} }
@ -2203,8 +2211,32 @@ static ConstantNumberParseResult parseDouble(double& result, const char* data)
char* end = nullptr; char* end = nullptr;
double value = strtod(data, &end); double value = strtod(data, &end);
result = value; if (FFlag::LuauParseImpreciseNumber)
return *end == 0 ? ConstantNumberParseResult::Ok : ConstantNumberParseResult::Malformed; {
// 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 // simpleexp -> NUMBER | STRING | NIL | true | false | ... | constructor | FUNCTION body | primaryexp

View File

@ -120,6 +120,7 @@ struct CompileStats
{ {
size_t lines; size_t lines;
size_t bytecode; size_t bytecode;
size_t bytecodeInstructionCount;
size_t codegen; size_t codegen;
double readTime; double readTime;
@ -136,6 +137,7 @@ struct CompileStats
fprintf(fp, "{\ fprintf(fp, "{\
\"lines\": %zu, \ \"lines\": %zu, \
\"bytecode\": %zu, \ \"bytecode\": %zu, \
\"bytecodeInstructionCount\": %zu, \
\"codegen\": %zu, \ \"codegen\": %zu, \
\"readTime\": %f, \ \"readTime\": %f, \
\"miscTime\": %f, \ \"miscTime\": %f, \
@ -153,16 +155,22 @@ struct CompileStats
\"maxBlockInstructions\": %u, \ \"maxBlockInstructions\": %u, \
\"regAllocErrors\": %d, \ \"regAllocErrors\": %d, \
\"loweringErrors\": %d\ \"loweringErrors\": %d\
}, \
\"blockLinearizationStats\": {\
\"constPropInstructionCount\": %u, \
\"timeSeconds\": %f\
}}", }}",
lines, bytecode, codegen, readTime, miscTime, parseTime, compileTime, codegenTime, lowerStats.totalFunctions, lowerStats.skippedFunctions, lines, bytecode, bytecodeInstructionCount, codegen, readTime, miscTime, parseTime, compileTime, codegenTime, lowerStats.totalFunctions,
lowerStats.spillsToSlot, lowerStats.spillsToRestore, lowerStats.maxSpillSlotsUsed, lowerStats.blocksPreOpt, lowerStats.blocksPostOpt, lowerStats.skippedFunctions, lowerStats.spillsToSlot, lowerStats.spillsToRestore, lowerStats.maxSpillSlotsUsed, lowerStats.blocksPreOpt,
lowerStats.maxBlockInstructions, lowerStats.regAllocErrors, lowerStats.loweringErrors); lowerStats.blocksPostOpt, lowerStats.maxBlockInstructions, lowerStats.regAllocErrors, lowerStats.loweringErrors,
lowerStats.blockLinearizationStats.constPropInstructionCount, lowerStats.blockLinearizationStats.timeSeconds);
} }
CompileStats& operator+=(const CompileStats& that) CompileStats& operator+=(const CompileStats& that)
{ {
this->lines += that.lines; this->lines += that.lines;
this->bytecode += that.bytecode; this->bytecode += that.bytecode;
this->bytecodeInstructionCount += that.bytecodeInstructionCount;
this->codegen += that.codegen; this->codegen += that.codegen;
this->readTime += that.readTime; this->readTime += that.readTime;
this->miscTime += that.miscTime; 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()); Luau::compileOrThrow(bcb, result, names, copts());
stats.bytecode += bcb.getBytecode().size(); stats.bytecode += bcb.getBytecode().size();
stats.bytecodeInstructionCount = bcb.getTotalInstructionCount();
stats.compileTime += recordDeltaTime(currts); stats.compileTime += recordDeltaTime(currts);
switch (format) switch (format)
@ -321,6 +330,30 @@ static int assertionHandler(const char* expr, const char* file, int line, const
return 1; 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) int main(int argc, char** argv)
{ {
Luau::assertHandler() = assertionHandler; Luau::assertHandler() = assertionHandler;
@ -330,6 +363,7 @@ int main(int argc, char** argv)
CompileFormat compileFormat = CompileFormat::Text; CompileFormat compileFormat = CompileFormat::Text;
Luau::CodeGen::AssemblyOptions::Target assemblyTarget = Luau::CodeGen::AssemblyOptions::Host; Luau::CodeGen::AssemblyOptions::Target assemblyTarget = Luau::CodeGen::AssemblyOptions::Host;
RecordStats recordStats = RecordStats::None; RecordStats recordStats = RecordStats::None;
std::string statsFile("stats.json");
for (int i = 1; i < argc; i++) for (int i = 1; i < argc; i++)
{ {
@ -394,6 +428,16 @@ int main(int argc, char** argv)
return 1; 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) else if (strncmp(argv[i], "--fflags=", 9) == 0)
{ {
setLuauFlags(argv[i] + 9); setLuauFlags(argv[i] + 9);
@ -463,7 +507,7 @@ int main(int argc, char** argv)
if (recordStats != RecordStats::None) if (recordStats != RecordStats::None)
{ {
FILE* fp = fopen("stats.json", "w"); FILE* fp = fopen(statsFile.c_str(), "w");
if (!fp) if (!fp)
{ {
@ -480,7 +524,8 @@ int main(int argc, char** argv)
fprintf(fp, "{\n"); fprintf(fp, "{\n");
for (size_t i = 0; i < fileCount; ++i) 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); fileStats[i].serializeToJson(fp);
fprintf(fp, i == (fileCount - 1) ? "\n" : ",\n"); fprintf(fp, i == (fileCount - 1) ? "\n" : ",\n");
} }

View File

@ -80,6 +80,27 @@ struct AssemblyOptions
void* annotatorContext = nullptr; 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 struct LoweringStats
{ {
unsigned totalFunctions = 0; unsigned totalFunctions = 0;
@ -94,6 +115,8 @@ struct LoweringStats
int regAllocErrors = 0; int regAllocErrors = 0;
int loweringErrors = 0; int loweringErrors = 0;
BlockLinearizationStats blockLinearizationStats;
LoweringStats operator+(const LoweringStats& other) const LoweringStats operator+(const LoweringStats& other) const
{ {
LoweringStats result(*this); LoweringStats result(*this);
@ -113,6 +136,7 @@ struct LoweringStats
this->maxBlockInstructions = std::max(this->maxBlockInstructions, that.maxBlockInstructions); this->maxBlockInstructions = std::max(this->maxBlockInstructions, that.maxBlockInstructions);
this->regAllocErrors += that.regAllocErrors; this->regAllocErrors += that.regAllocErrors;
this->loweringErrors += that.loweringErrors; this->loweringErrors += that.loweringErrors;
this->blockLinearizationStats += that.blockLinearizationStats;
return *this; return *this;
} }
}; };

View File

@ -600,6 +600,10 @@ enum class IrCmd : uint8_t
BITCOUNTLZ_UINT, BITCOUNTLZ_UINT,
BITCOUNTRZ_UINT, BITCOUNTRZ_UINT,
// Swap byte order in A
// A: int
BYTESWAP_UINT,
// Calls native libm function with 1 or 2 arguments // Calls native libm function with 1 or 2 arguments
// A: builtin function ID // A: builtin function ID
// B: double // B: double

View File

@ -50,6 +50,13 @@ inline void gatherFunctions(std::vector<Proto*>& results, Proto* proto, unsigned
gatherFunctions(results, proto->p[i], flags); 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> template<typename AssemblyBuilder, typename IrLowering>
inline bool lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction& function, const std::vector<uint32_t>& sortedBlocks, int bytecodeid, inline bool lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction& function, const std::vector<uint32_t>& sortedBlocks, int bytecodeid,
AssemblyOptions options) AssemblyOptions options)
@ -269,7 +276,25 @@ inline bool lowerFunction(IrBuilder& ir, AssemblyBuilder& build, ModuleHelpers&
constPropInBlockChains(ir, useValueNumbering); constPropInBlockChains(ir, useValueNumbering);
if (!FFlag::DebugCodegenOptSize) 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); 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); 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) const Instruction* executeNAMECALL(lua_State* L, const Instruction* pc, StkId base, TValue* k)
{ {
[[maybe_unused]] Closure* cl = clvalue(L->ci->func); [[maybe_unused]] Closure* cl = clvalue(L->ci->func);
@ -587,43 +543,19 @@ const Instruction* executeNAMECALL(lua_State* L, const Instruction* pc, StkId ba
if (ttistable(rb)) if (ttistable(rb))
{ {
Table* h = hvalue(rb); // note: lvmexecute.cpp version of NAMECALL has two fast paths, but both fast paths are inlined into IR
// note: we can't use nodemask8 here because we need to query the main position of the table, and 8-bit nodemask8 only works // as such, if we get here we can just use the generic path which makes the fallback path a little faster
// for predictive lookups
LuaNode* n = &h->node[tsvalue(kv)->hash & (sizenode(h) - 1)];
const TValue* mt = 0; // slow-path: handles full table lookup
const LuaNode* mtn = 0; setobj2s(L, ra + 1, rb);
L->cachedslot = LUAU_INSN_C(insn);
// fast-path: key is in the table in expected slot VM_PROTECT(luaV_gettable(L, rb, kv, ra));
if (ttisstring(gkey(n)) && tsvalue(gkey(n)) == tsvalue(kv) && !ttisnil(gval(n))) // save cachedslot to accelerate future lookups; patches currently executing instruction since pc-2 rolls back two pc++
{ VM_PATCH_C(pc - 2, L->cachedslot);
// note: order of copies allows rb to alias ra+1 or ra // recompute ra since stack might have been reallocated
setobj2s(L, ra + 1, rb); ra = VM_REG(LUAU_INSN_A(insn));
setobj2s(L, ra, gval(n)); if (ttisnil(ra))
} luaG_methoderror(L, ra + 1, tsvalue(kv));
// fast-path: key is absent from the base, table has an __index table, and it has the result in the expected slot
else if (gnext(n) == 0 && (mt = fasttm(L, hvalue(rb)->metatable, TM_INDEX)) && ttistable(mt) &&
(mtn = &hvalue(mt)->node[LUAU_INSN_C(insn) & hvalue(mt)->nodemask8]) && ttisstring(gkey(mtn)) && tsvalue(gkey(mtn)) == tsvalue(kv) &&
!ttisnil(gval(mtn)))
{
// note: order of copies allows rb to alias ra+1 or ra
setobj2s(L, ra + 1, rb);
setobj2s(L, ra, gval(mtn));
}
else
{
// slow-path: handles full table lookup
setobj2s(L, ra + 1, rb);
L->cachedslot = LUAU_INSN_C(insn);
VM_PROTECT(luaV_gettable(L, rb, kv, ra));
// save cachedslot to accelerate future lookups; patches currently executing instruction since pc-2 rolls back two pc++
VM_PATCH_C(pc - 2, L->cachedslot);
// recompute ra since stack might have been reallocated
ra = VM_REG(LUAU_INSN_A(insn));
if (ttisnil(ra))
luaG_methoderror(L, ra + 1, tsvalue(kv));
}
} }
else else
{ {

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* 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* 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* 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* 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* executeSETLIST(lua_State* L, const Instruction* pc, StkId base, TValue* k);
const Instruction* executeFORGPREP(lua_State* L, const Instruction* pc, StkId base, TValue* k); const Instruction* 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"; return "BITCOUNTLZ_UINT";
case IrCmd::BITCOUNTRZ_UINT: case IrCmd::BITCOUNTRZ_UINT:
return "BITCOUNTRZ_UINT"; return "BITCOUNTRZ_UINT";
case IrCmd::BYTESWAP_UINT:
return "BYTESWAP_UINT";
case IrCmd::INVOKE_LIBM: case IrCmd::INVOKE_LIBM:
return "INVOKE_LIBM"; return "INVOKE_LIBM";
case IrCmd::GET_TYPE: 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); build.clz(inst.regA64, inst.regA64);
break; 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: case IrCmd::INVOKE_LIBM:
{ {
if (inst.c.kind != IrOpKind::None) 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: case IrCmd::UINT_TO_NUM:
inst.regX64 = regs.allocReg(SizeX64::xmmword, index); inst.regX64 = regs.allocReg(SizeX64::xmmword, index);
build.vcvtsi2sd(inst.regX64, inst.regX64, qwordReg(regOp(inst.a))); // AVX has no uint->double conversion; the source must come from UINT op and they all should clear top 32 bits so we can usually
// use 64-bit reg; the one exception is NUM_TO_UINT which doesn't clear top bits
if (IrCmd source = function.instOp(inst.a).cmd; source == IrCmd::NUM_TO_UINT)
{
ScopedRegX64 tmp{regs, SizeX64::dword};
build.mov(tmp.reg, regOp(inst.a));
build.vcvtsi2sd(inst.regX64, inst.regX64, qwordReg(tmp.reg));
}
else
{
LUAU_ASSERT(source != IrCmd::SUBSTITUTE); // we don't process substitutions
build.vcvtsi2sd(inst.regX64, inst.regX64, qwordReg(regOp(inst.a)));
}
break; break;
case IrCmd::NUM_TO_INT: case IrCmd::NUM_TO_INT:
inst.regX64 = regs.allocReg(SizeX64::dword, index); 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); build.setLabel(exit);
break; 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: case IrCmd::INVOKE_LIBM:
{ {
IrCallWrapperX64 callWrap(regs, build, index); IrCallWrapperX64 callWrap(regs, build, index);

View File

@ -583,8 +583,8 @@ static BuiltinImplResult translateBuiltinBit32ExtractK(
return {BuiltinImplType::Full, 1}; return {BuiltinImplType::Full, 1};
} }
static BuiltinImplResult translateBuiltinBit32Countz( static BuiltinImplResult translateBuiltinBit32Unary(
IrBuilder& build, LuauBuiltinFunction bfid, int nparams, int ra, int arg, IrOp args, int nresults, int pcpos) IrBuilder& build, IrCmd cmd, int nparams, int ra, int arg, IrOp args, int nresults, int pcpos)
{ {
if (nparams < 1 || nresults > 1) if (nparams < 1 || nresults > 1)
return {BuiltinImplType::None, -1}; return {BuiltinImplType::None, -1};
@ -594,7 +594,6 @@ static BuiltinImplResult translateBuiltinBit32Countz(
IrOp vaui = build.inst(IrCmd::NUM_TO_UINT, va); 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 bin = build.inst(cmd, vaui);
IrOp value = build.inst(IrCmd::UINT_TO_NUM, bin); 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: case LBF_BIT32_EXTRACTK:
return translateBuiltinBit32ExtractK(build, LuauBuiltinFunction(bfid), nparams, ra, arg, args, nresults, pcpos); return translateBuiltinBit32ExtractK(build, LuauBuiltinFunction(bfid), nparams, ra, arg, args, nresults, pcpos);
case LBF_BIT32_COUNTLZ: case LBF_BIT32_COUNTLZ:
return translateBuiltinBit32Unary(build, IrCmd::BITCOUNTLZ_UINT, nparams, ra, arg, args, nresults, pcpos);
case LBF_BIT32_COUNTRZ: 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: case LBF_BIT32_REPLACE:
return translateBuiltinBit32Replace(build, LuauBuiltinFunction(bfid), nparams, ra, arg, args, nresults, fallback, pcpos); return translateBuiltinBit32Replace(build, LuauBuiltinFunction(bfid), nparams, ra, arg, args, nresults, fallback, pcpos);
case LBF_TYPE: 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); return translateBuiltinTableInsert(build, nparams, ra, arg, args, nresults, pcpos);
case LBF_STRING_LEN: case LBF_STRING_LEN:
return translateBuiltinStringLen(build, nparams, ra, arg, args, nresults, pcpos); 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: default:
return {BuiltinImplType::None, -1}; return {BuiltinImplType::None, -1};
} }

View File

@ -163,6 +163,7 @@ IrValueKind getCmdValueKind(IrCmd cmd)
case IrCmd::BITRROTATE_UINT: case IrCmd::BITRROTATE_UINT:
case IrCmd::BITCOUNTLZ_UINT: case IrCmd::BITCOUNTLZ_UINT:
case IrCmd::BITCOUNTRZ_UINT: case IrCmd::BITCOUNTRZ_UINT:
case IrCmd::BYTESWAP_UINT:
return IrValueKind::Int; return IrValueKind::Int;
case IrCmd::INVOKE_LIBM: case IrCmd::INVOKE_LIBM:
return IrValueKind::Double; return IrValueKind::Double;

View File

@ -103,7 +103,6 @@ void initFunctions(NativeState& data)
data.context.executeGETTABLEKS = executeGETTABLEKS; data.context.executeGETTABLEKS = executeGETTABLEKS;
data.context.executeSETTABLEKS = executeSETTABLEKS; data.context.executeSETTABLEKS = executeSETTABLEKS;
data.context.executeNEWCLOSURE = executeNEWCLOSURE;
data.context.executeNAMECALL = executeNAMECALL; data.context.executeNAMECALL = executeNAMECALL;
data.context.executeFORGPREP = executeFORGPREP; data.context.executeFORGPREP = executeFORGPREP;
data.context.executeGETVARARGSMultRet = executeGETVARARGSMultRet; 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* (*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* (*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* (*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* (*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* (*executeSETLIST)(lua_State* L, const Instruction* pc, StkId base, TValue* k) = nullptr;
const Instruction* (*executeFORGPREP)(lua_State* L, const Instruction* pc, StkId base, TValue* k) = nullptr; const Instruction* (*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::BITLROTATE_UINT:
case IrCmd::BITCOUNTLZ_UINT: case IrCmd::BITCOUNTLZ_UINT:
case IrCmd::BITCOUNTRZ_UINT: case IrCmd::BITCOUNTRZ_UINT:
case IrCmd::BYTESWAP_UINT:
case IrCmd::INVOKE_LIBM: case IrCmd::INVOKE_LIBM:
case IrCmd::GET_TYPE: case IrCmd::GET_TYPE:
case IrCmd::GET_TYPEOF: case IrCmd::GET_TYPEOF:

View File

@ -83,6 +83,7 @@ public:
void pushDebugUpval(StringRef name); void pushDebugUpval(StringRef name);
size_t getInstructionCount() const; size_t getInstructionCount() const;
size_t getTotalInstructionCount() const;
uint32_t getDebugPC() const; uint32_t getDebugPC() const;
void addDebugRemark(const char* format, ...) LUAU_PRINTF_ATTR(2, 3); void addDebugRemark(const char* format, ...) LUAU_PRINTF_ATTR(2, 3);
@ -232,6 +233,7 @@ private:
uint32_t currentFunction = ~0u; uint32_t currentFunction = ~0u;
uint32_t mainFunction = ~0u; uint32_t mainFunction = ~0u;
size_t totalInstructionCount = 0;
std::vector<uint32_t> insns; std::vector<uint32_t> insns;
std::vector<int> lines; std::vector<int> lines;
std::vector<Constant> constants; std::vector<Constant> constants;

View File

@ -244,6 +244,7 @@ void BytecodeBuilder::endFunction(uint8_t maxstacksize, uint8_t numupvalues, uin
currentFunction = ~0u; currentFunction = ~0u;
totalInstructionCount += insns.size();
insns.clear(); insns.clear();
lines.clear(); lines.clear();
constants.clear(); constants.clear();
@ -539,6 +540,11 @@ size_t BytecodeBuilder::getInstructionCount() const
return insns.size(); return insns.size();
} }
size_t BytecodeBuilder::getTotalInstructionCount() const
{
return totalInstructionCount;
}
uint32_t BytecodeBuilder::getDebugPC() const uint32_t BytecodeBuilder::getDebugPC() const
{ {
return uint32_t(insns.size()); return uint32_t(insns.size());

View File

@ -27,7 +27,6 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineThresholdMaxBoost, 300)
LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5) LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5)
LUAU_FASTFLAG(LuauFloorDivision) LUAU_FASTFLAG(LuauFloorDivision)
LUAU_FASTFLAGVARIABLE(LuauCompileFixContinueValidation2, false)
LUAU_FASTFLAGVARIABLE(LuauCompileIfElseAndOr, false) LUAU_FASTFLAGVARIABLE(LuauCompileIfElseAndOr, false)
namespace Luau namespace Luau
@ -2519,14 +2518,9 @@ struct Compiler
// Optimization: body is a "continue" statement with no "else" => we can directly continue in "then" case // Optimization: body is a "continue" statement with no "else" => we can directly continue in "then" case
if (!stat->elsebody && continueStatement != nullptr && !areLocalsCaptured(loops.back().localOffsetContinue)) if (!stat->elsebody && continueStatement != nullptr && !areLocalsCaptured(loops.back().localOffsetContinue))
{ {
if (FFlag::LuauCompileFixContinueValidation2) // track continue statement for repeat..until validation (validateContinueUntil)
{ if (!loops.back().continueUsed)
// track continue statement for repeat..until validation (validateContinueUntil) loops.back().continueUsed = continueStatement;
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 // fallthrough = proceed with the loop body as usual
std::vector<size_t> elseJump; std::vector<size_t> elseJump;
@ -2587,7 +2581,7 @@ struct Compiler
size_t oldJumps = loopJumps.size(); size_t oldJumps = loopJumps.size();
size_t oldLocals = localStack.size(); size_t oldLocals = localStack.size();
loops.push_back({oldLocals, oldLocals, nullptr, nullptr}); loops.push_back({oldLocals, oldLocals, nullptr});
hasLoops = true; hasLoops = true;
size_t loopLabel = bytecode.emitLabel(); size_t loopLabel = bytecode.emitLabel();
@ -2623,7 +2617,7 @@ struct Compiler
size_t oldJumps = loopJumps.size(); size_t oldJumps = loopJumps.size();
size_t oldLocals = localStack.size(); size_t oldLocals = localStack.size();
loops.push_back({oldLocals, oldLocals, stat->condition, nullptr}); loops.push_back({oldLocals, oldLocals, nullptr});
hasLoops = true; hasLoops = true;
size_t loopLabel = bytecode.emitLabel(); 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 // 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. // 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); validateContinueUntil(loops.back().continueUsed, stat->condition, body, i + 1);
continueValidated = true; continueValidated = true;
@ -2870,7 +2864,7 @@ struct Compiler
size_t oldLocals = localStack.size(); size_t oldLocals = localStack.size();
size_t oldJumps = loopJumps.size(); size_t oldJumps = loopJumps.size();
loops.push_back({oldLocals, oldLocals, nullptr, nullptr}); loops.push_back({oldLocals, oldLocals, nullptr});
for (int iv = 0; iv < tripCount; ++iv) for (int iv = 0; iv < tripCount; ++iv)
{ {
@ -2921,7 +2915,7 @@ struct Compiler
size_t oldLocals = localStack.size(); size_t oldLocals = localStack.size();
size_t oldJumps = loopJumps.size(); size_t oldJumps = loopJumps.size();
loops.push_back({oldLocals, oldLocals, nullptr, nullptr}); loops.push_back({oldLocals, oldLocals, nullptr});
hasLoops = true; hasLoops = true;
// register layout: limit, step, index // register layout: limit, step, index
@ -2986,7 +2980,7 @@ struct Compiler
size_t oldLocals = localStack.size(); size_t oldLocals = localStack.size();
size_t oldJumps = loopJumps.size(); size_t oldJumps = loopJumps.size();
loops.push_back({oldLocals, oldLocals, nullptr, nullptr}); loops.push_back({oldLocals, oldLocals, nullptr});
hasLoops = true; hasLoops = true;
// register layout: generator, state, index, variables... // register layout: generator, state, index, variables...
@ -3398,14 +3392,9 @@ struct Compiler
{ {
LUAU_ASSERT(!loops.empty()); LUAU_ASSERT(!loops.empty());
if (FFlag::LuauCompileFixContinueValidation2) // track continue statement for repeat..until validation (validateContinueUntil)
{ if (!loops.back().continueUsed)
// track continue statement for repeat..until validation (validateContinueUntil) loops.back().continueUsed = stat;
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 // before continuing, we need to close all local variables that were captured in closures since loop start
// normally they are closed by the enclosing blocks, including the loop block, but we're skipping that here // normally they are closed by the enclosing blocks, including the loop block, but we're skipping that here
@ -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) void validateContinueUntil(AstStat* cont, AstExpr* condition, AstStatBlock* body, size_t start)
{ {
LUAU_ASSERT(FFlag::LuauCompileFixContinueValidation2);
UndefinedLocalVisitor visitor(this); UndefinedLocalVisitor visitor(this);
for (size_t i = start; i < body->body.size; ++i) for (size_t i = start; i < body->body.size; ++i)
@ -3748,18 +3724,8 @@ struct Compiler
void check(AstLocal* local) void check(AstLocal* local)
{ {
if (FFlag::LuauCompileFixContinueValidation2) if (!undef && locals.contains(local))
{ undef = local;
if (!undef && locals.contains(local))
undef = local;
}
else
{
Local& l = self->locals[local];
if (!l.allocated && !undef)
undef = local;
}
} }
bool visit(AstExprLocal* node) override bool visit(AstExprLocal* node) override
@ -3904,9 +3870,6 @@ struct Compiler
size_t localOffset; size_t localOffset;
size_t localOffsetContinue; size_t localOffsetContinue;
// TODO: Remove with LuauCompileFixContinueValidation2
AstExpr* untilCondition;
AstStatContinue* continueUsed; AstStatContinue* continueUsed;
}; };

View File

@ -156,7 +156,7 @@ target_sources(Luau.Analysis PRIVATE
Analysis/include/Luau/Cancellation.h Analysis/include/Luau/Cancellation.h
Analysis/include/Luau/Clone.h Analysis/include/Luau/Clone.h
Analysis/include/Luau/Constraint.h Analysis/include/Luau/Constraint.h
Analysis/include/Luau/ConstraintGraphBuilder.h Analysis/include/Luau/ConstraintGenerator.h
Analysis/include/Luau/ConstraintSolver.h Analysis/include/Luau/ConstraintSolver.h
Analysis/include/Luau/ControlFlow.h Analysis/include/Luau/ControlFlow.h
Analysis/include/Luau/DataFlowGraph.h Analysis/include/Luau/DataFlowGraph.h
@ -223,7 +223,7 @@ target_sources(Luau.Analysis PRIVATE
Analysis/src/BuiltinDefinitions.cpp Analysis/src/BuiltinDefinitions.cpp
Analysis/src/Clone.cpp Analysis/src/Clone.cpp
Analysis/src/Constraint.cpp Analysis/src/Constraint.cpp
Analysis/src/ConstraintGraphBuilder.cpp Analysis/src/ConstraintGenerator.cpp
Analysis/src/ConstraintSolver.cpp Analysis/src/ConstraintSolver.cpp
Analysis/src/DataFlowGraph.cpp Analysis/src/DataFlowGraph.cpp
Analysis/src/DcrLogger.cpp Analysis/src/DcrLogger.cpp
@ -385,8 +385,8 @@ if(TARGET Luau.UnitTest)
tests/CodeAllocator.test.cpp tests/CodeAllocator.test.cpp
tests/Compiler.test.cpp tests/Compiler.test.cpp
tests/Config.test.cpp tests/Config.test.cpp
tests/ConstraintGraphBuilderFixture.cpp tests/ConstraintGeneratorFixture.cpp
tests/ConstraintGraphBuilderFixture.h tests/ConstraintGeneratorFixture.h
tests/ConstraintSolver.test.cpp tests/ConstraintSolver.test.cpp
tests/CostModel.test.cpp tests/CostModel.test.cpp
tests/DataFlowGraph.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; return -1;
T val; 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)); setnvalue(res, double(val));
return 1; return 1;
} }
@ -1378,7 +1378,7 @@ static int luauF_writeinteger(lua_State* L, StkId res, TValue* arg0, int nresult
luai_num2unsigned(value, incoming); luai_num2unsigned(value, incoming);
T val = T(value); 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; return 0;
} }
#endif #endif
@ -1398,7 +1398,12 @@ static int luauF_readfp(lua_State* L, StkId res, TValue* arg0, int nresults, Stk
return -1; return -1;
T val; 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)); setnvalue(res, double(val));
return 1; return 1;
} }
@ -1419,7 +1424,12 @@ static int luauF_writefp(lua_State* L, StkId res, TValue* arg0, int nresults, St
return -1; return -1;
T val = T(nvalue(args + 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; return 0;
} }
#endif #endif

View File

@ -17,8 +17,6 @@
#include <string.h> #include <string.h>
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauHandlerClose, false)
/* /*
** {====================================================== ** {======================================================
** Error-recovery functions ** Error-recovery functions
@ -409,7 +407,7 @@ static void resume_handle(lua_State* L, void* ud)
L->ci = restoreci(L, old_ci); L->ci = restoreci(L, old_ci);
// close eventual pending closures; this means it's now safe to restore stack // 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 // finish cont call and restore stack to previous ci top
luau_poscall(L, L->top - n); luau_poscall(L, L->top - n);

View File

@ -132,7 +132,8 @@ function test()
local ts0 = os.clock() local ts0 = os.clock()
for i = 1, 100 do for i = 1, 100 do
sha256(input) local res = sha256(input)
assert(res == "45849646c50337988ccc877d23fcc0de50d1df7490fdc3b9333aed0de8ab492a")
end end
local ts1 = os.clock() local ts1 = os.clock()

View File

@ -1909,8 +1909,6 @@ RETURN R0 0
TEST_CASE("LoopContinueIgnoresImplicitConstant") TEST_CASE("LoopContinueIgnoresImplicitConstant")
{ {
ScopedFastFlag luauCompileFixContinueValidation{"LuauCompileFixContinueValidation2", true};
// this used to crash the compiler :( // this used to crash the compiler :(
CHECK_EQ("\n" + compileFunction0(R"( CHECK_EQ("\n" + compileFunction0(R"(
local _ local _
@ -1926,8 +1924,6 @@ RETURN R0 0
TEST_CASE("LoopContinueIgnoresExplicitConstant") TEST_CASE("LoopContinueIgnoresExplicitConstant")
{ {
ScopedFastFlag luauCompileFixContinueValidation{"LuauCompileFixContinueValidation2", true};
// Constants do not allocate locals and 'continue' validation should skip them if their lifetime already started // Constants do not allocate locals and 'continue' validation should skip them if their lifetime already started
CHECK_EQ("\n" + compileFunction0(R"( CHECK_EQ("\n" + compileFunction0(R"(
local c = true local c = true
@ -1943,8 +1939,6 @@ RETURN R0 0
TEST_CASE("LoopContinueRespectsExplicitConstant") 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 // If local lifetime hasn't started, even if it's a constant that will not receive an allocation, it cannot be jumped over
try try
{ {
@ -1969,8 +1963,6 @@ until c
TEST_CASE("LoopContinueIgnoresImplicitConstantAfterInline") TEST_CASE("LoopContinueIgnoresImplicitConstantAfterInline")
{ {
ScopedFastFlag luauCompileFixContinueValidation{"LuauCompileFixContinueValidation2", true};
// Inlining might also replace some locals with constants instead of allocating them // Inlining might also replace some locals with constants instead of allocating them
CHECK_EQ("\n" + compileFunction(R"( CHECK_EQ("\n" + compileFunction(R"(
local function inline(f) local function inline(f)
@ -1994,7 +1986,6 @@ RETURN R0 0
TEST_CASE("LoopContinueCorrectlyHandlesImplicitConstantAfterUnroll") TEST_CASE("LoopContinueCorrectlyHandlesImplicitConstantAfterUnroll")
{ {
ScopedFastFlag sff{"LuauCompileFixContinueValidation2", true};
ScopedFastInt sfi("LuauCompileLoopUnrollThreshold", 200); 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 // 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") TEST_CASE("PCall")
{ {
ScopedFastFlag sff("LuauHandlerClose", true);
runConformance( runConformance(
"pcall.lua", "pcall.lua",
[](lua_State* L) { [](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 // 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 namespace Luau
{ {
ConstraintGraphBuilderFixture::ConstraintGraphBuilderFixture() ConstraintGeneratorFixture::ConstraintGeneratorFixture()
: Fixture() : Fixture()
, mainModule(new Module) , mainModule(new Module)
, forceTheFlag{"DebugLuauDeferredConstraintResolution", true} , forceTheFlag{"DebugLuauDeferredConstraintResolution", true}
@ -15,18 +15,18 @@ ConstraintGraphBuilderFixture::ConstraintGraphBuilderFixture()
BlockedTypePack::nextIndex = 0; BlockedTypePack::nextIndex = 0;
} }
void ConstraintGraphBuilderFixture::generateConstraints(const std::string& code) void ConstraintGeneratorFixture::generateConstraints(const std::string& code)
{ {
AstStatBlock* root = parse(code); AstStatBlock* root = parse(code);
dfg = std::make_unique<DataFlowGraph>(DataFlowGraphBuilder::build(root, NotNull{&ice})); 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>()); frontend.globals.globalScope, /*prepareModuleScope*/ nullptr, &logger, NotNull{dfg.get()}, std::vector<RequireCycle>());
cgb->visitModuleRoot(root); cg->visitModuleRoot(root);
rootScope = cgb->rootScope; rootScope = cg->rootScope;
constraints = Luau::borrowConstraints(cgb->constraints); constraints = Luau::borrowConstraints(cg->constraints);
} }
void ConstraintGraphBuilderFixture::solve(const std::string& code) void ConstraintGeneratorFixture::solve(const std::string& code)
{ {
generateConstraints(code); generateConstraints(code);
ConstraintSolver cs{NotNull{&normalizer}, NotNull{rootScope}, constraints, "MainModule", NotNull(&moduleResolver), {}, &logger, {}}; 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 // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once #pragma once
#include "Luau/ConstraintGraphBuilder.h" #include "Luau/ConstraintGenerator.h"
#include "Luau/ConstraintSolver.h" #include "Luau/ConstraintSolver.h"
#include "Luau/DcrLogger.h" #include "Luau/DcrLogger.h"
#include "Luau/TypeArena.h" #include "Luau/TypeArena.h"
@ -13,7 +13,7 @@
namespace Luau namespace Luau
{ {
struct ConstraintGraphBuilderFixture : Fixture struct ConstraintGeneratorFixture : Fixture
{ {
TypeArena arena; TypeArena arena;
ModulePtr mainModule; ModulePtr mainModule;
@ -22,14 +22,14 @@ struct ConstraintGraphBuilderFixture : Fixture
Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}}; Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}};
std::unique_ptr<DataFlowGraph> dfg; std::unique_ptr<DataFlowGraph> dfg;
std::unique_ptr<ConstraintGraphBuilder> cgb; std::unique_ptr<ConstraintGenerator> cg;
Scope* rootScope = nullptr; Scope* rootScope = nullptr;
std::vector<NotNull<Constraint>> constraints; std::vector<NotNull<Constraint>> constraints;
ScopedFastFlag forceTheFlag; ScopedFastFlag forceTheFlag;
ConstraintGraphBuilderFixture(); ConstraintGeneratorFixture();
void generateConstraints(const std::string& code); void generateConstraints(const std::string& code);
void solve(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 // 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 "Fixture.h"
#include "doctest.h" #include "doctest.h"
@ -17,7 +17,7 @@ static TypeId requireBinding(Scope* scope, const char* name)
TEST_SUITE_BEGIN("ConstraintSolver"); TEST_SUITE_BEGIN("ConstraintSolver");
TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "hello") TEST_CASE_FIXTURE(ConstraintGeneratorFixture, "hello")
{ {
solve(R"( solve(R"(
local a = 55 local a = 55
@ -29,7 +29,7 @@ TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "hello")
CHECK("number" == toString(bType)); CHECK("number" == toString(bType));
} }
TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "generic_function") TEST_CASE_FIXTURE(ConstraintGeneratorFixture, "generic_function")
{ {
solve(R"( solve(R"(
local function id(a) local function id(a)
@ -42,7 +42,7 @@ TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "generic_function")
CHECK("<a>(a) -> a" == toString(idType)); CHECK("<a>(a) -> a" == toString(idType));
} }
TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "proper_let_generalization") TEST_CASE_FIXTURE(ConstraintGeneratorFixture, "proper_let_generalization")
{ {
solve(R"( solve(R"(
local function a(c) 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") TEST_CASE_FIXTURE(BuiltinsFixture, "metatable_names_show_instead_of_tables")
{ {
frontend.options.retainFullTypeGraphs = false; frontend.options.retainFullTypeGraphs = false;
ScopedFastFlag sff{"LuauStacklessTypeClone2", true}; ScopedFastFlag sff{"LuauStacklessTypeClone3", true};
CheckResult result = check(R"( CheckResult result = check(R"(
--!strict --!strict
local Account = {} local Account = {}

View File

@ -3106,3 +3106,37 @@ bb_1:
} }
TEST_SUITE_END(); 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") TEST_CASE_FIXTURE(BuiltinsFixture, "DeprecatedApiFenv")
{ {
ScopedFastFlag sff("LuauLintDeprecatedFenv", true);
LintResult result = lint(R"( LintResult result = lint(R"(
local f, g, h = ... local f, g, h = ...
@ -1591,8 +1589,6 @@ table.create(42, {} :: {})
TEST_CASE_FIXTURE(BuiltinsFixture, "TableOperationsIndexer") TEST_CASE_FIXTURE(BuiltinsFixture, "TableOperationsIndexer")
{ {
ScopedFastFlag sff("LuauLintTableIndexer", true);
LintResult result = lint(R"( LintResult result = lint(R"(
local t1 = {} -- ok: empty local t1 = {} -- ok: empty
local t2 = {1, 2} -- ok: array local t2 = {1, 2} -- ok: array
@ -1827,8 +1823,71 @@ local _ = 0x10000000000000000
)"); )");
REQUIRE(2 == result.warnings.size()); 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[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 has been 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") TEST_CASE_FIXTURE(Fixture, "ComparisonPrecedence")

View File

@ -14,7 +14,7 @@
using namespace Luau; using namespace Luau;
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution); LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
LUAU_FASTFLAG(LuauStacklessTypeClone2) LUAU_FASTFLAG(LuauStacklessTypeClone3)
TEST_SUITE_BEGIN("ModuleTests"); TEST_SUITE_BEGIN("ModuleTests");
@ -336,7 +336,7 @@ TEST_CASE_FIXTURE(Fixture, "clone_recursion_limit")
int limit = 400; int limit = 400;
#endif #endif
ScopedFastFlag sff{"LuauStacklessTypeClone2", false}; ScopedFastFlag sff{"LuauStacklessTypeClone3", false};
ScopedFastInt luauTypeCloneRecursionLimit{"LuauTypeCloneRecursionLimit", limit}; ScopedFastInt luauTypeCloneRecursionLimit{"LuauTypeCloneRecursionLimit", limit};
TypeArena src; TypeArena src;
@ -360,7 +360,7 @@ TEST_CASE_FIXTURE(Fixture, "clone_recursion_limit")
TEST_CASE_FIXTURE(Fixture, "clone_iteration_limit") TEST_CASE_FIXTURE(Fixture, "clone_iteration_limit")
{ {
ScopedFastFlag sff{"LuauStacklessTypeClone2", true}; ScopedFastFlag sff{"LuauStacklessTypeClone3", true};
ScopedFastInt sfi{"LuauTypeCloneIterationLimit", 500}; ScopedFastInt sfi{"LuauTypeCloneIterationLimit", 500};
TypeArena src; TypeArena src;
@ -534,4 +534,36 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "clone_table_bound_to_table_bound_to_table")
REQUIRE(!tableA->boundTo); 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(); TEST_SUITE_END();

View File

@ -31,6 +31,9 @@ struct IsSubtypeFixture : Fixture
bool isConsistentSubtype(TypeId a, TypeId b) bool isConsistentSubtype(TypeId a, TypeId b)
{ {
// any test that is testing isConsistentSubtype is testing the old solver exclusively!
ScopedFastFlag noDcr{"DebugLuauDeferredConstraintResolution", false};
Location location; Location location;
ModulePtr module = getMainModule(); ModulePtr module = getMainModule();
REQUIRE(module); REQUIRE(module);
@ -169,7 +172,10 @@ TEST_CASE_FIXTURE(IsSubtypeFixture, "table_with_union_prop")
TypeId a = requireType("a"); TypeId a = requireType("a");
TypeId b = requireType("b"); TypeId b = requireType("b");
CHECK(isSubtype(a, b)); if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK(!isSubtype(a, b)); // table properties are invariant
else
CHECK(isSubtype(a, b));
CHECK(!isSubtype(b, a)); CHECK(!isSubtype(b, a));
} }
@ -187,7 +193,10 @@ TEST_CASE_FIXTURE(IsSubtypeFixture, "table_with_any_prop")
TypeId a = requireType("a"); TypeId a = requireType("a");
TypeId b = requireType("b"); TypeId b = requireType("b");
CHECK(isSubtype(a, b)); if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK(!isSubtype(a, b)); // table properties are invariant
else
CHECK(isSubtype(a, b));
CHECK(!isSubtype(b, a)); CHECK(!isSubtype(b, a));
CHECK(isConsistentSubtype(b, a)); CHECK(isConsistentSubtype(b, a));
} }
@ -249,7 +258,10 @@ TEST_CASE_FIXTURE(IsSubtypeFixture, "tables")
TypeId c = requireType("c"); TypeId c = requireType("c");
TypeId d = requireType("d"); TypeId d = requireType("d");
CHECK(isSubtype(a, b)); if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK(!isSubtype(a, b)); // table properties are invariant
else
CHECK(isSubtype(a, b));
CHECK(!isSubtype(b, a)); CHECK(!isSubtype(b, a));
CHECK(isConsistentSubtype(b, a)); CHECK(isConsistentSubtype(b, a));
@ -259,7 +271,10 @@ TEST_CASE_FIXTURE(IsSubtypeFixture, "tables")
CHECK(isSubtype(d, a)); CHECK(isSubtype(d, a));
CHECK(!isSubtype(a, d)); CHECK(!isSubtype(a, d));
CHECK(isSubtype(d, b)); if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK(!isSubtype(d, b)); // table properties are invariant
else
CHECK(isSubtype(d, b));
CHECK(!isSubtype(b, d)); CHECK(!isSubtype(b, d));
} }
@ -705,6 +720,19 @@ TEST_CASE_FIXTURE(NormalizeFixture, "specific_functions_cannot_be_negated")
CHECK(nullptr == toNormalizedType("Not<(boolean) -> boolean>")); 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") TEST_CASE_FIXTURE(NormalizeFixture, "bare_negated_boolean")
{ {
// TODO: We don't yet have a way to say number | string | thread | nil | Class | Table | Function // 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()); 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(); 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); 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}") TEST_CASE_FIXTURE(SubtypeFixture, "t1 where t1 = {trim: (t1) -> string} <: t2 where t2 = {trim: (t2) -> string}")
{ {
TypeId t1 = cyclicTable([&](TypeId ty, TableType* tt) { 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); LUAU_REQUIRE_NO_ERRORS(result);
} }
#if 0
TEST_CASE_FIXTURE(Fixture, "generic_aliases") TEST_CASE_FIXTURE(Fixture, "generic_aliases")
{ {
ScopedFastFlag sff[] = { ScopedFastFlag sff[] = {
@ -200,7 +199,7 @@ TEST_CASE_FIXTURE(Fixture, "generic_aliases")
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = 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(result.errors[0].location == Location{{4, 31}, {4, 44}});
CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(expected, toString(result.errors[0]));
} }
@ -220,12 +219,11 @@ TEST_CASE_FIXTURE(Fixture, "dependent_generic_aliases")
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = 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(result.errors[0].location == Location{{4, 31}, {4, 52}});
CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(expected, toString(result.errors[0]));
} }
#endif
TEST_CASE_FIXTURE(Fixture, "mutually_recursive_generic_aliases") 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}; 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 // 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. // through, we'd invoke UB.
CheckResult result = check(R"( 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") TEST_CASE_FIXTURE(ClassFixture, "index_instance_property")
{ {
ScopedFastFlag luauAllowIndexClassParameters{"LuauAllowIndexClassParameters", true};
CheckResult result = check(R"( CheckResult result = check(R"(
local function execute(object: BaseClass, name: string) local function execute(object: BaseClass, name: string)
print(object[name]) print(object[name])
@ -440,8 +438,6 @@ TEST_CASE_FIXTURE(ClassFixture, "index_instance_property")
TEST_CASE_FIXTURE(ClassFixture, "index_instance_property_nonstrict") TEST_CASE_FIXTURE(ClassFixture, "index_instance_property_nonstrict")
{ {
ScopedFastFlag luauAllowIndexClassParameters{"LuauAllowIndexClassParameters", true};
CheckResult result = check(R"( CheckResult result = check(R"(
--!nonstrict --!nonstrict

View File

@ -976,7 +976,6 @@ local y = x["Bar"]
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
} }
#if 0
TEST_CASE_FIXTURE(Fixture, "cli_80596_simplify_degenerate_intersections") TEST_CASE_FIXTURE(Fixture, "cli_80596_simplify_degenerate_intersections")
{ {
ScopedFastFlag dcr{"DebugLuauDeferredConstraintResolution", true}; ScopedFastFlag dcr{"DebugLuauDeferredConstraintResolution", true};
@ -1026,6 +1025,5 @@ TEST_CASE_FIXTURE(Fixture, "cli_80596_simplify_more_realistic_intersections")
LUAU_REQUIRE_ERRORS(result); LUAU_REQUIRE_ERRORS(result);
} }
#endif
TEST_SUITE_END(); 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 // 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 // shared self should fix it, but there may be other mitigations possible as well
REQUIRE(!FFlag::DebugLuauDeferredConstraintResolution); REQUIRE(!FFlag::DebugLuauDeferredConstraintResolution);
ScopedFastFlag sff{"LuauStacklessTypeClone2", true}; ScopedFastFlag sff{"LuauStacklessTypeClone3", true};
frontend.options.retainFullTypeGraphs = false; 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])); CHECK_EQ(expected, toString(result.errors[0]));
} }
#if 0
TEST_CASE_FIXTURE(Fixture, "parametric_tagged_union_alias") TEST_CASE_FIXTURE(Fixture, "parametric_tagged_union_alias")
{ {
ScopedFastFlag sff[] = { ScopedFastFlag sff[] = {
@ -372,7 +371,6 @@ TEST_CASE_FIXTURE(Fixture, "parametric_tagged_union_alias")
CHECK(toString(result.errors[0]) == expectedError); CHECK(toString(result.errors[0]) == expectedError);
} }
#endif
TEST_CASE_FIXTURE(Fixture, "if_then_else_expression_singleton_options") 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") 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); LUAU_REQUIRE_NO_ERRORS(result);
const TableType* tType = get<TableType>(requireType("t")); 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})); 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") TEST_CASE_FIXTURE(Fixture, "cannot_augment_sealed_table")
{ {
CheckResult result = check(R"( 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") TEST_CASE_FIXTURE(BuiltinsFixture, "be_sure_to_use_active_txnlog_when_evaluating_a_variadic_overload")
{ {
ScopedFastFlag sff{"LuauVariadicOverloadFix", true};
CheckResult result = check(R"( CheckResult result = check(R"(
local function concat<T>(target: {T}, ...: {T} | T): {T} local function concat<T>(target: {T}, ...: {T} | T): {T}
return (nil :: any) :: {T} return (nil :: any) :: {T}

View File

@ -15,6 +15,9 @@ LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
struct TryUnifyFixture : Fixture struct TryUnifyFixture : Fixture
{ {
// Cannot use `TryUnifyFixture` under DCR.
ScopedFastFlag noDcr{"DebugLuauDeferredConstraintResolution", false};
TypeArena arena; TypeArena arena;
ScopePtr globalScope{new Scope{arena.addTypePack({TypeId{}})}}; ScopePtr globalScope{new Scope{arena.addTypePack({TypeId{}})}};
InternalErrorReporter iceHandler; 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()); 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"( CheckResult result = check(R"(
function f(arg : string & number) : never function f(arg : string & number) : never
@ -149,7 +152,7 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "uninhabited_intersection_sub_never")
LUAU_REQUIRE_NO_ERRORS(result); 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"( CheckResult result = check(R"(
function f(arg : string & number) : boolean function f(arg : string & number) : boolean
@ -159,7 +162,7 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "uninhabited_intersection_sub_anything")
LUAU_REQUIRE_NO_ERRORS(result); 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"( CheckResult result = check(R"(
function f(arg : { prop : string & number }) : never function f(arg : { prop : string & number }) : never
@ -169,7 +172,7 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "uninhabited_table_sub_never")
LUAU_REQUIRE_NO_ERRORS(result); 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"( CheckResult result = check(R"(
function f(arg : { prop : string & number }) : boolean function f(arg : { prop : string & number }) : boolean
@ -179,9 +182,11 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "uninhabited_table_sub_anything")
LUAU_REQUIRE_NO_ERRORS(result); 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"( CheckResult result = check(R"(
function f(arg: number) end 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"))); 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"( CheckResult result = check(R"(
function f(arg: number) return arg end 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"))); 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"( CheckResult result = check(R"(
--!strict --!strict
@ -254,7 +261,7 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "variadic_tails_respect_progress")
CHECK(state.errors.empty()); 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"( CheckResult result = check(R"(
--!strict --!strict
@ -373,22 +380,12 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "metatables_unify_against_shape_of_free_table
state.log.commit(); state.log.commit();
REQUIRE_EQ(state.errors.size(), 1); REQUIRE_EQ(state.errors.size(), 1);
// clang-format off const std::string expected = R"(Type
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
'{ @metatable {| __index: {| foo: string |} |}, { } }' '{ @metatable {| __index: {| foo: string |} |}, { } }'
could not be converted into could not be converted into
'{- foo: number -}' '{- foo: number -}'
caused by: caused by:
Type 'number' could not be converted into 'string')"; Type 'number' could not be converted into 'string')";
// clang-format on
CHECK_EQ(expected, toString(state.errors[0])); CHECK_EQ(expected, toString(state.errors[0]));
} }

View File

@ -93,7 +93,6 @@ TEST_SUITE_BEGIN("TypePathTraversal");
LUAU_REQUIRE_NO_ERRORS(result); \ LUAU_REQUIRE_NO_ERRORS(result); \
} while (false); } while (false);
#if 0
TEST_CASE_FIXTURE(Fixture, "empty_traversal") TEST_CASE_FIXTURE(Fixture, "empty_traversal")
{ {
CHECK(traverseForType(builtinTypes->numberType, kEmpty, builtinTypes) == builtinTypes->numberType); CHECK(traverseForType(builtinTypes->numberType, kEmpty, builtinTypes) == builtinTypes->numberType);
@ -475,7 +474,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "complex_chains")
CHECK(*result == builtinTypes->falseType); CHECK(*result == builtinTypes->falseType);
} }
} }
#endif
TEST_SUITE_END(); // TypePathTraversal TEST_SUITE_END(); // TypePathTraversal

View File

@ -140,6 +140,11 @@ assert(bit32.byteswap(0x10203040) == 0x40302010)
assert(bit32.byteswap(0) == 0) assert(bit32.byteswap(0) == 0)
assert(bit32.byteswap(-1) == 0xffffffff) 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 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 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 -- 1234567890123456
assert(1.1 == '1.'+'.1') assert(1.1 == '1.'+'.1')
assert('1111111111111111'-'1111111111111110' == tonumber" +0.001e+3 \n\t") assert('1111111111111111'-'1111111111111110' == tonumber" +0.001e+3 \n\t")
assert(10000000000000001 == 10000000000000000)
function eq (a,b,limit) function eq (a,b,limit)
if not limit then limit = 10E-10 end if not limit then limit = 10E-10 end

View File

@ -15,20 +15,33 @@ end
local justone = "^" .. utf8.charpattern .. "$" 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", 5))
assert(not utf8.offset("alo", -4)) assert(not utf8.offset("alo", -4))
-- 'check' makes several tests over the validity of string 's'. -- 'check' makes several tests over the validity of string 's'.
-- 't' is the list of codepoints of 's'. -- 't' is the list of codepoints of 's'.
local function check (s, t, nonstrict) local function check (s, t)
local l = utf8.len(s, 1, -1, nonstrict) local l = utf8.len(s, 1, -1)
assert(#t == l and len(s) == l) assert(#t == l and len(s) == l)
assert(utf8.char(table.unpack(t)) == s) -- 't' and 's' are equivalent assert(utf8.char(table.unpack(t)) == s) -- 't' and 's' are equivalent
assert(utf8.offset(s, 0) == 1) assert(utf8.offset(s, 0) == 1)
checksyntax(s, t)
-- creates new table with all codepoints of 's' -- 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) assert(#t == #t1)
for i = 1, #t do assert(t[i] == t1[i]) end -- 't' is equal to '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(string.find(string.sub(s, pi, pi1 - 1), justone))
assert(utf8.offset(s, -1, pi1) == pi) assert(utf8.offset(s, -1, pi1) == pi)
assert(utf8.offset(s, i - l - 1) == 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 for j = pi, pi1 - 1 do
assert(utf8.offset(s, 0, j) == pi) assert(utf8.offset(s, 0, j) == pi)
end end
for j = pi + 1, pi1 - 1 do for j = pi + 1, pi1 - 1 do
assert(not utf8.len(s, j)) assert(not utf8.len(s, j))
end end
assert(utf8.len(s, pi, pi, nonstrict) == 1) assert(utf8.len(s, pi, pi) == 1)
assert(utf8.len(s, pi, pi1 - 1, nonstrict) == 1) assert(utf8.len(s, pi, pi1 - 1) == 1)
assert(utf8.len(s, pi, -1, nonstrict) == l - i + 1) assert(utf8.len(s, pi, -1) == l - i + 1)
assert(utf8.len(s, pi1, -1, nonstrict) == l - i) assert(utf8.len(s, pi1, -1) == l - i)
assert(utf8.len(s, 1, pi, nonstrict) == i) assert(utf8.len(s, 1, pi) == i)
end end
local i = 0 local i = 0
for p, c in utf8.codes(s, nonstrict) do for p, c in utf8.codes(s) do
i = i + 1 i = i + 1
assert(c == t[i] and p == utf8.offset(s, i)) 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 end
assert(i == #t) assert(i == #t)
@ -80,9 +93,15 @@ do -- error indication in utf8.len
assert(not a and b == p) assert(not a and b == p)
end end
check("abc\xE3def", 4) check("abc\xE3def", 4)
check("汉字\x80", #("汉字") + 1)
check("\xF4\x9F\xBF", 1) check("\xF4\x9F\xBF", 1)
check("\xF4\x9F\xBF\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 end
-- errors in utf8.codes -- errors in utf8.codes
@ -94,7 +113,17 @@ do
end) end)
end end
errorcodes("ab\xff") 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 end
-- error in initial position for offset -- error in initial position for offset
@ -131,16 +160,16 @@ do
-- surrogates -- surrogates
assert(utf8.codepoint("\u{D7FF}") == 0xD800 - 1) assert(utf8.codepoint("\u{D7FF}") == 0xD800 - 1)
assert(utf8.codepoint("\u{E000}") == 0xDFFF + 1) assert(utf8.codepoint("\u{E000}") == 0xDFFF + 1)
assert(utf8.codepoint("\u{D800}", 1, 1, true) == 0xD800) assert(utf8.codepoint("\u{D800}", 1, 1) == 0xD800) -- TODO: this is an error in Lua 5.4
assert(utf8.codepoint("\u{DFFF}", 1, 1, true) == 0xDFFF) assert(utf8.codepoint("\u{DFFF}", 1, 1) == 0xDFFF) -- TODO: this is an error in Lua 5.4
-- assert(utf8.codepoint("\u{7FFFFFFF}", 1, 1, true) == 0x7FFFFFFF) assert(pcall(utf8.codepoint, "\253\191\191\191\191\191") == false) -- 0x7FFFFFFF in Lua 5.4 when called with lax=true
end end
assert(utf8.char() == "") assert(utf8.char() == "")
assert(utf8.char(0, 97, 98, 99, 1) == "\0abc\1") assert(utf8.char(0, 97, 98, 99, 1) == "\0abc\1")
assert(utf8.codepoint(utf8.char(0x10FFFF)) == 0x10FFFF) 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, 0x7FFFFFFF + 1)
checkerror("value out of range", utf8.char, -1) checkerror("value out of range", utf8.char, -1)
@ -154,8 +183,8 @@ end
invalid("\xF4\x9F\xBF\xBF") invalid("\xF4\x9F\xBF\xBF")
-- surrogates -- surrogates
-- invalid("\u{D800}") -- invalid("\u{D800}") TODO: this is an error in Lua 5.4
-- invalid("\u{DFFF}") -- invalid("\u{DFFF}") TODO: this is an error in Lua 5.4
-- overlong sequences -- overlong sequences
invalid("\xC0\x80") -- zero invalid("\xC0\x80") -- zero
@ -182,7 +211,7 @@ s = "\0 \x7F\z
s = string.gsub(s, " ", "") s = string.gsub(s, " ", "")
check(s, {0,0x7F, 0x80,0x7FF, 0x800,0xFFFF, 0x10000,0x10FFFF}) 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}) check(x, {26085, 26412, 35486, 97, 45, 52, 0, 233, 243})

View File

@ -28,6 +28,7 @@
#endif #endif
#include <optional> #include <optional>
#include <stdio.h>
// Indicates if verbose output is enabled; can be overridden via --verbose // 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. // 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 {} 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> template<typename T>
using FValueResult = std::pair<std::string, 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_on_argument_type_pack_vararg
AutocompleteTest.anonymous_autofilled_generic_type_pack_vararg AutocompleteTest.anonymous_autofilled_generic_type_pack_vararg
AutocompleteTest.autocomplete_interpolated_string_as_singleton AutocompleteTest.autocomplete_interpolated_string_as_singleton
AutocompleteTest.autocomplete_oop_implicit_self
AutocompleteTest.autocomplete_response_perf1
AutocompleteTest.autocomplete_string_singleton_equality AutocompleteTest.autocomplete_string_singleton_equality
AutocompleteTest.autocomplete_string_singleton_escape AutocompleteTest.autocomplete_string_singleton_escape
AutocompleteTest.autocomplete_string_singletons AutocompleteTest.autocomplete_string_singletons
@ -230,6 +228,7 @@ IntersectionTypes.table_intersection_write_sealed
IntersectionTypes.table_intersection_write_sealed_indirect IntersectionTypes.table_intersection_write_sealed_indirect
IntersectionTypes.table_write_sealed_indirect IntersectionTypes.table_write_sealed_indirect
IntersectionTypes.union_saturate_overloaded_functions IntersectionTypes.union_saturate_overloaded_functions
isSubtype.any_is_unknown_union_error
Linter.DeprecatedApiFenv Linter.DeprecatedApiFenv
Linter.FormatStringTyped Linter.FormatStringTyped
Linter.TableOperationsIndexer 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.any_when_indexing_into_an_unsealed_table_with_no_indexer_in_nonstrict_mode
TableTests.array_factory_function TableTests.array_factory_function
TableTests.call_method 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_indexer2
TableTests.casting_tables_with_props_into_table_with_indexer3 TableTests.casting_tables_with_props_into_table_with_indexer3
TableTests.casting_tables_with_props_into_table_with_indexer4 TableTests.casting_tables_with_props_into_table_with_indexer4
@ -333,9 +332,7 @@ TableTests.cyclic_shifted_tables
TableTests.disallow_indexing_into_an_unsealed_table_with_no_indexer_in_strict_mode TableTests.disallow_indexing_into_an_unsealed_table_with_no_indexer_in_strict_mode
TableTests.dont_crash_when_setmetatable_does_not_produce_a_metatabletypevar TableTests.dont_crash_when_setmetatable_does_not_produce_a_metatabletypevar
TableTests.dont_extend_unsealed_tables_in_rvalue_position 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_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_seal_an_unsealed_table_by_passing_it_to_a_function_that_takes_a_sealed_table
TableTests.dont_suggest_exact_match_keys TableTests.dont_suggest_exact_match_keys
TableTests.error_detailed_indexer_key TableTests.error_detailed_indexer_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_assignment
TableTests.okay_to_add_property_to_unsealed_tables_by_function_call TableTests.okay_to_add_property_to_unsealed_tables_by_function_call
TableTests.only_ascribe_synthetic_names_at_module_scope TableTests.only_ascribe_synthetic_names_at_module_scope
TableTests.oop_indexer_works
TableTests.oop_polymorphic TableTests.oop_polymorphic
TableTests.open_table_unification_2 TableTests.open_table_unification_2
TableTests.pass_a_union_of_tables_to_a_function_that_requires_a_table TableTests.pass_a_union_of_tables_to_a_function_that_requires_a_table
@ -424,6 +420,8 @@ TableTests.unification_of_unions_in_a_self_referential_type
TableTests.unifying_tables_shouldnt_uaf1 TableTests.unifying_tables_shouldnt_uaf1
TableTests.used_colon_instead_of_dot TableTests.used_colon_instead_of_dot
TableTests.used_dot_instead_of_colon 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 TableTests.wrong_assign_does_hit_indexer
ToDot.function ToDot.function
ToString.exhaustive_toString_of_cyclic_table 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.function_statement_sealed_table_assignment_through_indexer
TypeInferFunctions.generic_packs_are_not_variadic TypeInferFunctions.generic_packs_are_not_variadic
TypeInferFunctions.higher_order_function_2 TypeInferFunctions.higher_order_function_2
TypeInferFunctions.higher_order_function_3
TypeInferFunctions.higher_order_function_4 TypeInferFunctions.higher_order_function_4
TypeInferFunctions.improved_function_arg_mismatch_error_nonstrict TypeInferFunctions.improved_function_arg_mismatch_error_nonstrict
TypeInferFunctions.improved_function_arg_mismatch_errors TypeInferFunctions.improved_function_arg_mismatch_errors
@ -596,10 +593,6 @@ TypeInferFunctions.too_many_return_values_no_function
TypeInferFunctions.vararg_function_is_quantified TypeInferFunctions.vararg_function_is_quantified
TypeInferLoops.cli_68448_iterators_need_not_accept_nil TypeInferLoops.cli_68448_iterators_need_not_accept_nil
TypeInferLoops.dcr_iteration_explore_raycast_minimization TypeInferLoops.dcr_iteration_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_iteration_on_never_gives_never
TypeInferLoops.dcr_xpath_candidates TypeInferLoops.dcr_xpath_candidates
TypeInferLoops.for_in_loop TypeInferLoops.for_in_loop
@ -647,7 +640,6 @@ TypeInferOOP.methods_are_topologically_sorted
TypeInferOOP.object_constructor_can_refer_to_method_of_self TypeInferOOP.object_constructor_can_refer_to_method_of_self
TypeInferOOP.promise_type_error_too_complex TypeInferOOP.promise_type_error_too_complex
TypeInferOOP.react_style_oo TypeInferOOP.react_style_oo
TypeInferOOP.table_oop
TypeInferOperators.add_type_family_works TypeInferOperators.add_type_family_works
TypeInferOperators.and_binexps_dont_unify TypeInferOperators.and_binexps_dont_unify
TypeInferOperators.cli_38355_recursive_union TypeInferOperators.cli_38355_recursive_union
@ -694,7 +686,6 @@ TypePackTests.type_alias_type_packs_import
TypePackTests.type_packs_with_tails_in_vararg_adjustment TypePackTests.type_packs_with_tails_in_vararg_adjustment
TypePackTests.unify_variadic_tails_in_arguments TypePackTests.unify_variadic_tails_in_arguments
TypePackTests.unify_variadic_tails_in_arguments_free TypePackTests.unify_variadic_tails_in_arguments_free
TypePackTests.variadic_argument_tail
TypeSingletons.enums_using_singletons_mismatch TypeSingletons.enums_using_singletons_mismatch
TypeSingletons.error_detailed_tagged_union_mismatch_bool TypeSingletons.error_detailed_tagged_union_mismatch_bool
TypeSingletons.error_detailed_tagged_union_mismatch_string TypeSingletons.error_detailed_tagged_union_mismatch_string