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