diff --git a/Analysis/include/Luau/Frontend.h b/Analysis/include/Luau/Frontend.h index 37e3cfdc..d7c9ca40 100644 --- a/Analysis/include/Luau/Frontend.h +++ b/Analysis/include/Luau/Frontend.h @@ -12,9 +12,6 @@ #include #include -LUAU_FASTFLAG(LuauSeparateTypechecks) -LUAU_FASTFLAG(LuauDirtySourceModule) - namespace Luau { @@ -60,17 +57,12 @@ struct SourceNode { bool hasDirtySourceModule() const { - LUAU_ASSERT(FFlag::LuauDirtySourceModule); - return dirtySourceModule; } bool hasDirtyModule(bool forAutocomplete) const { - if (FFlag::LuauSeparateTypechecks) - return forAutocomplete ? dirtyModuleForAutocomplete : dirtyModule; - else - return dirtyModule; + return forAutocomplete ? dirtyModuleForAutocomplete : dirtyModule; } ModuleName name; @@ -90,10 +82,6 @@ struct FrontendOptions // is complete. bool retainFullTypeGraphs = false; - // When true, we run typechecking twice, once in the regular mode, and once in strict mode - // in order to get more precise type information (e.g. for autocomplete). - bool typecheckTwice_DEPRECATED = false; - // Run typechecking only in mode required for autocomplete (strict mode in order to get more precise type information) bool forAutocomplete = false; }; @@ -171,7 +159,7 @@ struct Frontend void applyBuiltinDefinitionToEnvironment(const std::string& environmentName, const std::string& definitionName); private: - std::pair getSourceNode(CheckResult& checkResult, const ModuleName& name, bool forAutocomplete_DEPRECATED); + std::pair getSourceNode(CheckResult& checkResult, const ModuleName& name); SourceModule parse(const ModuleName& name, std::string_view src, const ParseOptions& parseOptions); bool parseGraph(std::vector& buildQueue, CheckResult& checkResult, const ModuleName& root, bool forAutocomplete); diff --git a/Analysis/include/Luau/Instantiation.h b/Analysis/include/Luau/Instantiation.h new file mode 100644 index 00000000..e05ceebe --- /dev/null +++ b/Analysis/include/Luau/Instantiation.h @@ -0,0 +1,53 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#pragma once + +#include "Luau/Substitution.h" +#include "Luau/TypeVar.h" +#include "Luau/Unifiable.h" + +namespace Luau +{ + +struct TypeArena; +struct TxnLog; + +// A substitution which replaces generic types in a given set by free types. +struct ReplaceGenerics : Substitution +{ + ReplaceGenerics( + const TxnLog* log, TypeArena* arena, TypeLevel level, const std::vector& generics, const std::vector& genericPacks) + : Substitution(log, arena) + , level(level) + , generics(generics) + , genericPacks(genericPacks) + { + } + + TypeLevel level; + std::vector generics; + std::vector genericPacks; + bool ignoreChildren(TypeId ty) override; + bool isDirty(TypeId ty) override; + bool isDirty(TypePackId tp) override; + TypeId clean(TypeId ty) override; + TypePackId clean(TypePackId tp) override; +}; + +// A substitution which replaces generic functions by monomorphic functions +struct Instantiation : Substitution +{ + Instantiation(const TxnLog* log, TypeArena* arena, TypeLevel level) + : Substitution(log, arena) + , level(level) + { + } + + TypeLevel level; + bool ignoreChildren(TypeId ty) override; + bool isDirty(TypeId ty) override; + bool isDirty(TypePackId tp) override; + TypeId clean(TypeId ty) override; + TypePackId clean(TypePackId tp) override; +}; + +} // namespace Luau diff --git a/Analysis/include/Luau/TypeArena.h b/Analysis/include/Luau/TypeArena.h index 7c74158b..559c55c8 100644 --- a/Analysis/include/Luau/TypeArena.h +++ b/Analysis/include/Luau/TypeArena.h @@ -39,4 +39,4 @@ struct TypeArena void freeze(TypeArena& arena); void unfreeze(TypeArena& arena); -} +} // namespace Luau diff --git a/Analysis/include/Luau/TypeInfer.h b/Analysis/include/Luau/TypeInfer.h index fcaf5baa..183cc053 100644 --- a/Analysis/include/Luau/TypeInfer.h +++ b/Analysis/include/Luau/TypeInfer.h @@ -34,45 +34,6 @@ const AstStat* getFallthrough(const AstStat* node); struct UnifierOptions; struct Unifier; -// A substitution which replaces generic types in a given set by free types. -struct ReplaceGenerics : Substitution -{ - ReplaceGenerics( - const TxnLog* log, TypeArena* arena, TypeLevel level, const std::vector& generics, const std::vector& genericPacks) - : Substitution(log, arena) - , level(level) - , generics(generics) - , genericPacks(genericPacks) - { - } - - TypeLevel level; - std::vector generics; - std::vector genericPacks; - bool ignoreChildren(TypeId ty) override; - bool isDirty(TypeId ty) override; - bool isDirty(TypePackId tp) override; - TypeId clean(TypeId ty) override; - TypePackId clean(TypePackId tp) override; -}; - -// A substitution which replaces generic functions by monomorphic functions -struct Instantiation : Substitution -{ - Instantiation(const TxnLog* log, TypeArena* arena, TypeLevel level) - : Substitution(log, arena) - , level(level) - { - } - - TypeLevel level; - bool ignoreChildren(TypeId ty) override; - bool isDirty(TypeId ty) override; - bool isDirty(TypePackId tp) override; - TypeId clean(TypeId ty) override; - TypePackId clean(TypePackId tp) override; -}; - // A substitution which replaces free types by any struct Anyification : Substitution { diff --git a/Analysis/include/Luau/Unifier.h b/Analysis/include/Luau/Unifier.h index 0e24c8b0..627b52ca 100644 --- a/Analysis/include/Luau/Unifier.h +++ b/Analysis/include/Luau/Unifier.h @@ -32,6 +32,9 @@ struct Widen : Substitution TypeId clean(TypeId ty) override; TypePackId clean(TypePackId ty) override; bool ignoreChildren(TypeId ty) override; + + TypeId operator()(TypeId ty); + TypePackId operator()(TypePackId ty); }; // TODO: Use this more widely. diff --git a/Analysis/include/Luau/UnifierSharedState.h b/Analysis/include/Luau/UnifierSharedState.h index 1a0b8b76..d4315d47 100644 --- a/Analysis/include/Luau/UnifierSharedState.h +++ b/Analysis/include/Luau/UnifierSharedState.h @@ -42,7 +42,6 @@ struct UnifierSharedState InternalErrorReporter* iceHandler; - DenseHashSet seenAny{nullptr}; DenseHashMap skipCacheForType{nullptr}; DenseHashSet, TypeIdPairHash> cachedUnify{{nullptr, nullptr}}; DenseHashMap, TypeErrorData, TypeIdPairHash> cachedUnifyError{{nullptr, nullptr}}; diff --git a/Analysis/include/Luau/VisitTypeVar.h b/Analysis/include/Luau/VisitTypeVar.h index 2e98f526..f3839915 100644 --- a/Analysis/include/Luau/VisitTypeVar.h +++ b/Analysis/include/Luau/VisitTypeVar.h @@ -8,7 +8,6 @@ #include "Luau/TypePack.h" #include "Luau/TypeVar.h" -LUAU_FASTFLAG(LuauUseVisitRecursionLimit) LUAU_FASTINT(LuauVisitRecursionLimit) LUAU_FASTFLAG(LuauNormalizeFlagIsConservative) @@ -62,168 +61,6 @@ inline void unsee(DenseHashSet& seen, const void* tv) // When DenseHashSet is used for 'visitTypeVarOnce', where don't forget visited elements } -template -void visit(TypePackId tp, F& f, Set& seen); - -template -void visit(TypeId ty, F& f, Set& seen) -{ - if (visit_detail::hasSeen(seen, ty)) - { - f.cycle(ty); - return; - } - - if (auto btv = get(ty)) - { - if (apply(ty, *btv, seen, f)) - visit(btv->boundTo, f, seen); - } - - else if (auto ftv = get(ty)) - apply(ty, *ftv, seen, f); - - else if (auto gtv = get(ty)) - apply(ty, *gtv, seen, f); - - else if (auto etv = get(ty)) - apply(ty, *etv, seen, f); - - else if (auto ctv = get(ty)) - { - if (apply(ty, *ctv, seen, f)) - { - for (TypeId part : ctv->parts) - visit(part, f, seen); - } - } - - else if (auto ptv = get(ty)) - apply(ty, *ptv, seen, f); - - else if (auto ftv = get(ty)) - { - if (apply(ty, *ftv, seen, f)) - { - visit(ftv->argTypes, f, seen); - visit(ftv->retType, f, seen); - } - } - - else if (auto ttv = get(ty)) - { - // Some visitors want to see bound tables, that's why we visit the original type - if (apply(ty, *ttv, seen, f)) - { - if (ttv->boundTo) - { - visit(*ttv->boundTo, f, seen); - } - else - { - for (auto& [_name, prop] : ttv->props) - visit(prop.type, f, seen); - - if (ttv->indexer) - { - visit(ttv->indexer->indexType, f, seen); - visit(ttv->indexer->indexResultType, f, seen); - } - } - } - } - - else if (auto mtv = get(ty)) - { - if (apply(ty, *mtv, seen, f)) - { - visit(mtv->table, f, seen); - visit(mtv->metatable, f, seen); - } - } - - else if (auto ctv = get(ty)) - { - if (apply(ty, *ctv, seen, f)) - { - for (const auto& [name, prop] : ctv->props) - visit(prop.type, f, seen); - - if (ctv->parent) - visit(*ctv->parent, f, seen); - - if (ctv->metatable) - visit(*ctv->metatable, f, seen); - } - } - - else if (auto atv = get(ty)) - apply(ty, *atv, seen, f); - - else if (auto utv = get(ty)) - { - if (apply(ty, *utv, seen, f)) - { - for (TypeId optTy : utv->options) - visit(optTy, f, seen); - } - } - - else if (auto itv = get(ty)) - { - if (apply(ty, *itv, seen, f)) - { - for (TypeId partTy : itv->parts) - visit(partTy, f, seen); - } - } - - visit_detail::unsee(seen, ty); -} - -template -void visit(TypePackId tp, F& f, Set& seen) -{ - if (visit_detail::hasSeen(seen, tp)) - { - f.cycle(tp); - return; - } - - if (auto btv = get(tp)) - { - if (apply(tp, *btv, seen, f)) - visit(btv->boundTo, f, seen); - } - - else if (auto ftv = get(tp)) - apply(tp, *ftv, seen, f); - - else if (auto gtv = get(tp)) - apply(tp, *gtv, seen, f); - - else if (auto etv = get(tp)) - apply(tp, *etv, seen, f); - - else if (auto pack = get(tp)) - { - apply(tp, *pack, seen, f); - - for (TypeId ty : pack->head) - visit(ty, f, seen); - - if (pack->tail) - visit(*pack->tail, f, seen); - } - else if (auto pack = get(tp)) - { - apply(tp, *pack, seen, f); - visit(pack->ty, f, seen); - } - - visit_detail::unsee(seen, tp); -} - } // namespace visit_detail template @@ -513,37 +350,4 @@ struct TypeVarOnceVisitor : GenericTypeVarVisitor> } }; -// Clip with FFlagLuauUseVisitRecursionLimit -template -void DEPRECATED_visitTypeVar(TID ty, F& f, std::unordered_set& seen) -{ - visit_detail::visit(ty, f, seen); -} - -// Delete and inline when clipping FFlagLuauUseVisitRecursionLimit -template -void DEPRECATED_visitTypeVar(TID ty, F& f) -{ - if (FFlag::LuauUseVisitRecursionLimit) - f.traverse(ty); - else - { - std::unordered_set seen; - visit_detail::visit(ty, f, seen); - } -} - -// Delete and inline when clipping FFlagLuauUseVisitRecursionLimit -template -void DEPRECATED_visitTypeVarOnce(TID ty, F& f, DenseHashSet& seen) -{ - if (FFlag::LuauUseVisitRecursionLimit) - f.traverse(ty); - else - { - seen.clear(); - visit_detail::visit(ty, f, seen); - } -} - } // namespace Luau diff --git a/Analysis/src/Autocomplete.cpp b/Analysis/src/Autocomplete.cpp index 19d06cfc..b988ed35 100644 --- a/Analysis/src/Autocomplete.cpp +++ b/Analysis/src/Autocomplete.cpp @@ -1700,31 +1700,18 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M AutocompleteResult autocomplete(Frontend& frontend, const ModuleName& moduleName, Position position, StringCompletionCallback callback) { - if (FFlag::LuauSeparateTypechecks) - { - // FIXME: We can improve performance here by parsing without checking. - // The old type graph is probably fine. (famous last words!) - FrontendOptions opts; - opts.forAutocomplete = true; - frontend.check(moduleName, opts); - } - else - { - // FIXME: We can improve performance here by parsing without checking. - // The old type graph is probably fine. (famous last words!) - // FIXME: We don't need to typecheck for script analysis here, just for autocomplete. - frontend.check(moduleName); - } + // FIXME: We can improve performance here by parsing without checking. + // The old type graph is probably fine. (famous last words!) + FrontendOptions opts; + opts.forAutocomplete = true; + frontend.check(moduleName, opts); const SourceModule* sourceModule = frontend.getSourceModule(moduleName); if (!sourceModule) return {}; - TypeChecker& typeChecker = - (frontend.options.typecheckTwice_DEPRECATED || FFlag::LuauSeparateTypechecks ? frontend.typeCheckerForAutocomplete : frontend.typeChecker); - ModulePtr module = - (frontend.options.typecheckTwice_DEPRECATED || FFlag::LuauSeparateTypechecks ? frontend.moduleResolverForAutocomplete.getModule(moduleName) - : frontend.moduleResolver.getModule(moduleName)); + TypeChecker& typeChecker = frontend.typeCheckerForAutocomplete; + ModulePtr module = frontend.moduleResolverForAutocomplete.getModule(moduleName); if (!module) return {}; @@ -1752,9 +1739,7 @@ OwningAutocompleteResult autocompleteSource(Frontend& frontend, std::string_view sourceModule->mode = Mode::Strict; sourceModule->commentLocations = std::move(result.commentLocations); - TypeChecker& typeChecker = - (frontend.options.typecheckTwice_DEPRECATED || FFlag::LuauSeparateTypechecks ? frontend.typeCheckerForAutocomplete : frontend.typeChecker); - + TypeChecker& typeChecker = frontend.typeCheckerForAutocomplete; ModulePtr module = typeChecker.check(*sourceModule, Mode::Strict); OwningAutocompleteResult autocompleteResult = { diff --git a/Analysis/src/Frontend.cpp b/Analysis/src/Frontend.cpp index 56c0ac2c..1d33f131 100644 --- a/Analysis/src/Frontend.cpp +++ b/Analysis/src/Frontend.cpp @@ -20,9 +20,7 @@ LUAU_FASTINT(LuauTypeInferIterationLimit) LUAU_FASTINT(LuauTarjanChildLimit) LUAU_FASTFLAG(LuauInferInNoCheckMode) LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3, false) -LUAU_FASTFLAGVARIABLE(LuauSeparateTypechecks, false) LUAU_FASTFLAGVARIABLE(LuauAutocompleteDynamicLimits, false) -LUAU_FASTFLAGVARIABLE(LuauDirtySourceModule, false) LUAU_FASTINTVARIABLE(LuauAutocompleteCheckTimeoutMs, 100) namespace Luau @@ -361,32 +359,21 @@ CheckResult Frontend::check(const ModuleName& name, std::optionalsecond.hasDirtyModule(frontendOptions.forAutocomplete)) { // No recheck required. - if (FFlag::LuauSeparateTypechecks) + if (frontendOptions.forAutocomplete) { - if (frontendOptions.forAutocomplete) - { - auto it2 = moduleResolverForAutocomplete.modules.find(name); - if (it2 == moduleResolverForAutocomplete.modules.end() || it2->second == nullptr) - throw std::runtime_error("Frontend::modules does not have data for " + name); - } - else - { - auto it2 = moduleResolver.modules.find(name); - if (it2 == moduleResolver.modules.end() || it2->second == nullptr) - throw std::runtime_error("Frontend::modules does not have data for " + name); - } - - return CheckResult{accumulateErrors( - sourceNodes, frontendOptions.forAutocomplete ? moduleResolverForAutocomplete.modules : moduleResolver.modules, name)}; + auto it2 = moduleResolverForAutocomplete.modules.find(name); + if (it2 == moduleResolverForAutocomplete.modules.end() || it2->second == nullptr) + throw std::runtime_error("Frontend::modules does not have data for " + name); } else { auto it2 = moduleResolver.modules.find(name); if (it2 == moduleResolver.modules.end() || it2->second == nullptr) throw std::runtime_error("Frontend::modules does not have data for " + name); - - return CheckResult{accumulateErrors(sourceNodes, moduleResolver.modules, name)}; } + + return CheckResult{ + accumulateErrors(sourceNodes, frontendOptions.forAutocomplete ? moduleResolverForAutocomplete.modules : moduleResolver.modules, name)}; } std::vector buildQueue; @@ -428,7 +415,7 @@ CheckResult Frontend::check(const ModuleName& name, std::optional& buildQueue, CheckResult& chec bool cyclic = false; { - auto [sourceNode, _] = getSourceNode(checkResult, root, forAutocomplete); + auto [sourceNode, _] = getSourceNode(checkResult, root); if (sourceNode) stack.push_back(sourceNode); } @@ -627,7 +603,7 @@ bool Frontend::parseGraph(std::vector& buildQueue, CheckResult& chec } } - auto [sourceNode, _] = getSourceNode(checkResult, dep, forAutocomplete); + auto [sourceNode, _] = getSourceNode(checkResult, dep); if (sourceNode) { stack.push_back(sourceNode); @@ -671,7 +647,7 @@ LintResult Frontend::lint(const ModuleName& name, std::optional* markedDirty) { - if (FFlag::LuauSeparateTypechecks) - { - if (!moduleResolver.modules.count(name) && !moduleResolverForAutocomplete.modules.count(name)) - return; - } - else - { - if (!moduleResolver.modules.count(name)) - return; - } + if (!moduleResolver.modules.count(name) && !moduleResolverForAutocomplete.modules.count(name)) + return; std::unordered_map> reverseDeps; for (const auto& module : sourceNodes) @@ -783,32 +751,12 @@ void Frontend::markDirty(const ModuleName& name, std::vector* marked if (markedDirty) markedDirty->push_back(next); - if (FFlag::LuauDirtySourceModule) - { - LUAU_ASSERT(FFlag::LuauSeparateTypechecks); + if (sourceNode.dirtySourceModule && sourceNode.dirtyModule && sourceNode.dirtyModuleForAutocomplete) + continue; - if (sourceNode.dirtySourceModule && sourceNode.dirtyModule && sourceNode.dirtyModuleForAutocomplete) - continue; - - sourceNode.dirtySourceModule = true; - sourceNode.dirtyModule = true; - sourceNode.dirtyModuleForAutocomplete = true; - } - else if (FFlag::LuauSeparateTypechecks) - { - if (sourceNode.dirtyModule && sourceNode.dirtyModuleForAutocomplete) - continue; - - sourceNode.dirtyModule = true; - sourceNode.dirtyModuleForAutocomplete = true; - } - else - { - if (sourceNode.dirtyModule) - continue; - - sourceNode.dirtyModule = true; - } + sourceNode.dirtySourceModule = true; + sourceNode.dirtyModule = true; + sourceNode.dirtyModuleForAutocomplete = true; if (0 == reverseDeps.count(name)) continue; @@ -835,14 +783,13 @@ const SourceModule* Frontend::getSourceModule(const ModuleName& moduleName) cons } // Read AST into sourceModules if necessary. Trace require()s. Report parse errors. -std::pair Frontend::getSourceNode(CheckResult& checkResult, const ModuleName& name, bool forAutocomplete_DEPRECATED) +std::pair Frontend::getSourceNode(CheckResult& checkResult, const ModuleName& name) { LUAU_TIMETRACE_SCOPE("Frontend::getSourceNode", "Frontend"); LUAU_TIMETRACE_ARGUMENT("name", name.c_str()); auto it = sourceNodes.find(name); - if (it != sourceNodes.end() && - (FFlag::LuauDirtySourceModule ? !it->second.hasDirtySourceModule() : !it->second.hasDirtyModule(forAutocomplete_DEPRECATED))) + if (it != sourceNodes.end() && !it->second.hasDirtySourceModule()) { auto moduleIt = sourceModules.find(name); if (moduleIt != sourceModules.end()) @@ -885,21 +832,12 @@ std::pair Frontend::getSourceNode(CheckResult& check sourceNode.name = name; sourceNode.requires.clear(); sourceNode.requireLocations.clear(); + sourceNode.dirtySourceModule = false; - if (FFlag::LuauDirtySourceModule) - sourceNode.dirtySourceModule = false; - - if (FFlag::LuauSeparateTypechecks) - { - if (it == sourceNodes.end()) - { - sourceNode.dirtyModule = true; - sourceNode.dirtyModuleForAutocomplete = true; - } - } - else + if (it == sourceNodes.end()) { sourceNode.dirtyModule = true; + sourceNode.dirtyModuleForAutocomplete = true; } for (const auto& [moduleName, location] : requireTrace.requires) diff --git a/Analysis/src/Instantiation.cpp b/Analysis/src/Instantiation.cpp new file mode 100644 index 00000000..4a12027d --- /dev/null +++ b/Analysis/src/Instantiation.cpp @@ -0,0 +1,128 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#include "Luau/Common.h" +#include "Luau/Instantiation.h" +#include "Luau/TxnLog.h" +#include "Luau/TypeArena.h" + +LUAU_FASTFLAG(LuauNoMethodLocations) + +namespace Luau +{ + +bool Instantiation::isDirty(TypeId ty) +{ + if (const FunctionTypeVar* ftv = log->getMutable(ty)) + { + if (ftv->hasNoGenerics) + return false; + + return true; + } + else + { + return false; + } +} + +bool Instantiation::isDirty(TypePackId tp) +{ + return false; +} + +bool Instantiation::ignoreChildren(TypeId ty) +{ + if (log->getMutable(ty)) + return true; + else + return false; +} + +TypeId Instantiation::clean(TypeId ty) +{ + const FunctionTypeVar* ftv = log->getMutable(ty); + LUAU_ASSERT(ftv); + + FunctionTypeVar clone = FunctionTypeVar{level, ftv->argTypes, ftv->retType, ftv->definition, ftv->hasSelf}; + clone.magicFunction = ftv->magicFunction; + clone.tags = ftv->tags; + clone.argNames = ftv->argNames; + TypeId result = addType(std::move(clone)); + + // Annoyingly, we have to do this even if there are no generics, + // to replace any generic tables. + ReplaceGenerics replaceGenerics{log, arena, level, ftv->generics, ftv->genericPacks}; + + // TODO: What to do if this returns nullopt? + // We don't have access to the error-reporting machinery + result = replaceGenerics.substitute(result).value_or(result); + + asMutable(result)->documentationSymbol = ty->documentationSymbol; + return result; +} + +TypePackId Instantiation::clean(TypePackId tp) +{ + LUAU_ASSERT(false); + return tp; +} + +bool ReplaceGenerics::ignoreChildren(TypeId ty) +{ + if (const FunctionTypeVar* ftv = log->getMutable(ty)) + { + if (ftv->hasNoGenerics) + return true; + + // We aren't recursing in the case of a generic function which + // binds the same generics. This can happen if, for example, there's recursive types. + // If T = (a,T)->T then instantiating T should produce T' = (X,T)->T not T' = (X,T')->T'. + // It's OK to use vector equality here, since we always generate fresh generics + // whenever we quantify, so the vectors overlap if and only if they are equal. + return (!generics.empty() || !genericPacks.empty()) && (ftv->generics == generics) && (ftv->genericPacks == genericPacks); + } + else + { + return false; + } +} + +bool ReplaceGenerics::isDirty(TypeId ty) +{ + if (const TableTypeVar* ttv = log->getMutable(ty)) + return ttv->state == TableState::Generic; + else if (log->getMutable(ty)) + return std::find(generics.begin(), generics.end(), ty) != generics.end(); + else + return false; +} + +bool ReplaceGenerics::isDirty(TypePackId tp) +{ + if (log->getMutable(tp)) + return std::find(genericPacks.begin(), genericPacks.end(), tp) != genericPacks.end(); + else + return false; +} + +TypeId ReplaceGenerics::clean(TypeId ty) +{ + LUAU_ASSERT(isDirty(ty)); + if (const TableTypeVar* ttv = log->getMutable(ty)) + { + TableTypeVar clone = TableTypeVar{ttv->props, ttv->indexer, level, TableState::Free}; + if (!FFlag::LuauNoMethodLocations) + clone.methodDefinitionLocations = ttv->methodDefinitionLocations; + clone.definitionModuleName = ttv->definitionModuleName; + return addType(std::move(clone)); + } + else + return addType(FreeTypeVar{level}); +} + +TypePackId ReplaceGenerics::clean(TypePackId tp) +{ + LUAU_ASSERT(isDirty(tp)); + return addTypePack(TypePackVar(FreeTypePack{level})); +} + +} // namespace Luau diff --git a/Analysis/src/Module.cpp b/Analysis/src/Module.cpp index 074a41e6..6591d60a 100644 --- a/Analysis/src/Module.cpp +++ b/Analysis/src/Module.cpp @@ -56,8 +56,18 @@ bool isWithinComment(const SourceModule& sourceModule, Position pos) struct ForceNormal : TypeVarOnceVisitor { + const TypeArena* typeArena = nullptr; + + ForceNormal(const TypeArena* typeArena) + : typeArena(typeArena) + { + } + bool visit(TypeId ty) override { + if (ty->owningArena != typeArena) + return false; + asMutable(ty)->normal = true; return true; } @@ -100,7 +110,7 @@ void Module::clonePublicInterface(InternalErrorReporter& ice) normalize(*moduleScope->varargPack, interfaceTypes, ice); } - ForceNormal forceNormal; + ForceNormal forceNormal{&interfaceTypes}; for (auto& [name, tf] : moduleScope->exportedTypeBindings) { diff --git a/Analysis/src/Normalize.cpp b/Analysis/src/Normalize.cpp index 30fd4af2..fb31df1e 100644 --- a/Analysis/src/Normalize.cpp +++ b/Analysis/src/Normalize.cpp @@ -15,6 +15,7 @@ LUAU_FASTFLAGVARIABLE(DebugLuauCopyBeforeNormalizing, false) LUAU_FASTINTVARIABLE(LuauNormalizeIterationLimit, 1200); LUAU_FASTFLAGVARIABLE(LuauNormalizeCombineTableFix, false); LUAU_FASTFLAGVARIABLE(LuauNormalizeFlagIsConservative, false); +LUAU_FASTFLAGVARIABLE(LuauNormalizeCombineEqFix, false); namespace Luau { @@ -325,245 +326,6 @@ struct Normalize final : TypeVarVisitor int iterationLimit = 0; bool limitExceeded = false; - // TODO: Clip with FFlag::LuauUseVisitRecursionLimit - bool operator()(TypeId ty, const BoundTypeVar& btv, std::unordered_set& seen) - { - // A type could be considered normal when it is in the stack, but we will eventually find out it is not normal as normalization progresses. - // So we need to avoid eagerly saying that this bound type is normal if the thing it is bound to is in the stack. - if (seen.find(asMutable(btv.boundTo)) != seen.end()) - return false; - - // It should never be the case that this TypeVar is normal, but is bound to a non-normal type, except in nontrivial cases. - LUAU_ASSERT(!ty->normal || ty->normal == btv.boundTo->normal); - - asMutable(ty)->normal = btv.boundTo->normal; - return !ty->normal; - } - - bool operator()(TypeId ty, const FreeTypeVar& ftv) - { - return visit(ty, ftv); - } - bool operator()(TypeId ty, const PrimitiveTypeVar& ptv) - { - return visit(ty, ptv); - } - bool operator()(TypeId ty, const GenericTypeVar& gtv) - { - return visit(ty, gtv); - } - bool operator()(TypeId ty, const ErrorTypeVar& etv) - { - return visit(ty, etv); - } - bool operator()(TypeId ty, const ConstrainedTypeVar& ctvRef, std::unordered_set& seen) - { - CHECK_ITERATION_LIMIT(false); - - ConstrainedTypeVar* ctv = const_cast(&ctvRef); - - std::vector parts = std::move(ctv->parts); - - // We might transmute, so it's not safe to rely on the builtin traversal logic of visitTypeVar - for (TypeId part : parts) - visit_detail::visit(part, *this, seen); - - std::vector newParts = normalizeUnion(parts); - - const bool normal = areNormal(newParts, seen, ice); - - if (newParts.size() == 1) - *asMutable(ty) = BoundTypeVar{newParts[0]}; - else - *asMutable(ty) = UnionTypeVar{std::move(newParts)}; - - asMutable(ty)->normal = normal; - - return false; - } - - bool operator()(TypeId ty, const FunctionTypeVar& ftv, std::unordered_set& seen) - { - CHECK_ITERATION_LIMIT(false); - - if (ty->normal) - return false; - - visit_detail::visit(ftv.argTypes, *this, seen); - visit_detail::visit(ftv.retType, *this, seen); - - asMutable(ty)->normal = areNormal(ftv.argTypes, seen, ice) && areNormal(ftv.retType, seen, ice); - - return false; - } - - bool operator()(TypeId ty, const TableTypeVar& ttv, std::unordered_set& seen) - { - CHECK_ITERATION_LIMIT(false); - - if (ty->normal) - return false; - - bool normal = true; - - auto checkNormal = [&](TypeId t) { - // if t is on the stack, it is possible that this type is normal. - // If t is not normal and it is not on the stack, this type is definitely not normal. - if (!t->normal && seen.find(asMutable(t)) == seen.end()) - normal = false; - }; - - if (ttv.boundTo) - { - visit_detail::visit(*ttv.boundTo, *this, seen); - asMutable(ty)->normal = (*ttv.boundTo)->normal; - return false; - } - - for (const auto& [_name, prop] : ttv.props) - { - visit_detail::visit(prop.type, *this, seen); - checkNormal(prop.type); - } - - if (ttv.indexer) - { - visit_detail::visit(ttv.indexer->indexType, *this, seen); - checkNormal(ttv.indexer->indexType); - visit_detail::visit(ttv.indexer->indexResultType, *this, seen); - checkNormal(ttv.indexer->indexResultType); - } - - asMutable(ty)->normal = normal; - - return false; - } - - bool operator()(TypeId ty, const MetatableTypeVar& mtv, std::unordered_set& seen) - { - CHECK_ITERATION_LIMIT(false); - - if (ty->normal) - return false; - - visit_detail::visit(mtv.table, *this, seen); - visit_detail::visit(mtv.metatable, *this, seen); - - asMutable(ty)->normal = mtv.table->normal && mtv.metatable->normal; - - return false; - } - - bool operator()(TypeId ty, const ClassTypeVar& ctv) - { - return visit(ty, ctv); - } - bool operator()(TypeId ty, const AnyTypeVar& atv) - { - return visit(ty, atv); - } - bool operator()(TypeId ty, const UnionTypeVar& utvRef, std::unordered_set& seen) - { - CHECK_ITERATION_LIMIT(false); - - if (ty->normal) - return false; - - UnionTypeVar* utv = &const_cast(utvRef); - std::vector options = std::move(utv->options); - - // We might transmute, so it's not safe to rely on the builtin traversal logic of visitTypeVar - for (TypeId option : options) - visit_detail::visit(option, *this, seen); - - std::vector newOptions = normalizeUnion(options); - - const bool normal = areNormal(newOptions, seen, ice); - - LUAU_ASSERT(!newOptions.empty()); - - if (newOptions.size() == 1) - *asMutable(ty) = BoundTypeVar{newOptions[0]}; - else - utv->options = std::move(newOptions); - - asMutable(ty)->normal = normal; - - return false; - } - - bool operator()(TypeId ty, const IntersectionTypeVar& itvRef, std::unordered_set& seen) - { - CHECK_ITERATION_LIMIT(false); - - if (ty->normal) - return false; - - IntersectionTypeVar* itv = &const_cast(itvRef); - - std::vector oldParts = std::move(itv->parts); - - for (TypeId part : oldParts) - visit_detail::visit(part, *this, seen); - - std::vector tables; - for (TypeId part : oldParts) - { - part = follow(part); - if (get(part)) - tables.push_back(part); - else - { - Replacer replacer{&arena, nullptr, nullptr}; // FIXME this is super super WEIRD - combineIntoIntersection(replacer, itv, part); - } - } - - // Don't allocate a new table if there's just one in the intersection. - if (tables.size() == 1) - itv->parts.push_back(tables[0]); - else if (!tables.empty()) - { - const TableTypeVar* first = get(tables[0]); - LUAU_ASSERT(first); - - TypeId newTable = arena.addType(TableTypeVar{first->state, first->level}); - TableTypeVar* ttv = getMutable(newTable); - for (TypeId part : tables) - { - // Intuition: If combineIntoTable() needs to clone a table, any references to 'part' are cyclic and need - // to be rewritten to point at 'newTable' in the clone. - Replacer replacer{&arena, part, newTable}; - combineIntoTable(replacer, ttv, part); - } - - itv->parts.push_back(newTable); - } - - asMutable(ty)->normal = areNormal(itv->parts, seen, ice); - - if (itv->parts.size() == 1) - { - TypeId part = itv->parts[0]; - *asMutable(ty) = BoundTypeVar{part}; - } - - return false; - } - - // TODO: Clip with FFlag::LuauUseVisitRecursionLimit - template - bool operator()(TypePackId, const T&) - { - return true; - } - - // TODO: Clip with FFlag::LuauUseVisitRecursionLimit - template - void cycle(TID) - { - } - bool visit(TypeId ty, const FreeTypeVar&) override { LUAU_ASSERT(!ty->normal); @@ -968,6 +730,9 @@ struct Normalize final : TypeVarVisitor */ TypeId combine(Replacer& replacer, TypeId a, TypeId b) { + if (FFlag::LuauNormalizeCombineEqFix) + b = follow(b); + if (FFlag::LuauNormalizeCombineTableFix && a == b) return a; @@ -986,7 +751,7 @@ struct Normalize final : TypeVarVisitor } else if (auto ttv = getMutable(a)) { - if (FFlag::LuauNormalizeCombineTableFix && !get(follow(b))) + if (FFlag::LuauNormalizeCombineTableFix && !get(FFlag::LuauNormalizeCombineEqFix ? b : follow(b))) return arena.addType(IntersectionTypeVar{{a, b}}); combineIntoTable(replacer, ttv, b); return a; @@ -1009,15 +774,7 @@ std::pair normalize(TypeId ty, TypeArena& arena, InternalErrorRepo (void)clone(ty, arena, state); Normalize n{arena, ice}; - if (FFlag::LuauNormalizeFlagIsConservative) - { - DEPRECATED_visitTypeVar(ty, n); - } - else - { - std::unordered_set seen; - DEPRECATED_visitTypeVar(ty, n, seen); - } + n.traverse(ty); return {ty, !n.limitExceeded}; } @@ -1041,15 +798,7 @@ std::pair normalize(TypePackId tp, TypeArena& arena, InternalE (void)clone(tp, arena, state); Normalize n{arena, ice}; - if (FFlag::LuauNormalizeFlagIsConservative) - { - DEPRECATED_visitTypeVar(tp, n); - } - else - { - std::unordered_set seen; - DEPRECATED_visitTypeVar(tp, n, seen); - } + n.traverse(tp); return {tp, !n.limitExceeded}; } diff --git a/Analysis/src/Quantify.cpp b/Analysis/src/Quantify.cpp index 018d5632..c0f677d7 100644 --- a/Analysis/src/Quantify.cpp +++ b/Analysis/src/Quantify.cpp @@ -119,8 +119,7 @@ struct Quantifier final : TypeVarOnceVisitor void quantify(TypeId ty, TypeLevel level) { Quantifier q{level}; - DenseHashSet seen{nullptr}; - DEPRECATED_visitTypeVarOnce(ty, q, seen); + q.traverse(ty); FunctionTypeVar* ftv = getMutable(ty); LUAU_ASSERT(ftv); diff --git a/Analysis/src/ToString.cpp b/Analysis/src/ToString.cpp index f90f7019..a4a3ec49 100644 --- a/Analysis/src/ToString.cpp +++ b/Analysis/src/ToString.cpp @@ -48,46 +48,6 @@ struct FindCyclicTypes final : TypeVarVisitor cycleTPs.insert(tp); } - // TODO: Clip all the operator()s when we clip FFlagLuauUseVisitRecursionLimit - - template - bool operator()(TypeId ty, const T&) - { - return visit(ty); - } - - bool operator()(TypeId ty, const TableTypeVar& ttv) = delete; - - bool operator()(TypeId ty, const TableTypeVar& ttv, std::unordered_set& seen) - { - if (!visited.insert(ty).second) - return false; - - if (ttv.name || ttv.syntheticName) - { - for (TypeId itp : ttv.instantiatedTypeParams) - DEPRECATED_visitTypeVar(itp, *this, seen); - - for (TypePackId itp : ttv.instantiatedTypePackParams) - DEPRECATED_visitTypeVar(itp, *this, seen); - - return exhaustive; - } - - return true; - } - - bool operator()(TypeId, const ClassTypeVar&) - { - return false; - } - - template - bool operator()(TypePackId tp, const T&) - { - return visit(tp); - } - bool visit(TypeId ty) override { return visited.insert(ty).second; @@ -128,7 +88,7 @@ void findCyclicTypes(std::set& cycles, std::set& cycleTPs, T { FindCyclicTypes fct; fct.exhaustive = exhaustive; - DEPRECATED_visitTypeVar(ty, fct); + fct.traverse(ty); cycles = std::move(fct.cycles); cycleTPs = std::move(fct.cycleTPs); diff --git a/Analysis/src/TypeArena.cpp b/Analysis/src/TypeArena.cpp index 673b002d..0c89d130 100644 --- a/Analysis/src/TypeArena.cpp +++ b/Analysis/src/TypeArena.cpp @@ -85,4 +85,4 @@ void unfreeze(TypeArena& arena) arena.typePacks.unfreeze(); } -} +} // namespace Luau diff --git a/Analysis/src/TypeInfer.cpp b/Analysis/src/TypeInfer.cpp index 208b3f2f..11813c76 100644 --- a/Analysis/src/TypeInfer.cpp +++ b/Analysis/src/TypeInfer.cpp @@ -3,6 +3,7 @@ #include "Luau/Clone.h" #include "Luau/Common.h" +#include "Luau/Instantiation.h" #include "Luau/ModuleResolver.h" #include "Luau/Normalize.h" #include "Luau/Parser.h" @@ -10,13 +11,13 @@ #include "Luau/RecursionCounter.h" #include "Luau/Scope.h" #include "Luau/Substitution.h" -#include "Luau/TopoSortStatements.h" -#include "Luau/TypePack.h" -#include "Luau/ToString.h" -#include "Luau/TypeUtils.h" -#include "Luau/ToString.h" -#include "Luau/TypeVar.h" #include "Luau/TimeTrace.h" +#include "Luau/TopoSortStatements.h" +#include "Luau/ToString.h" +#include "Luau/ToString.h" +#include "Luau/TypePack.h" +#include "Luau/TypeUtils.h" +#include "Luau/TypeVar.h" #include #include @@ -26,14 +27,11 @@ LUAU_FASTINTVARIABLE(LuauTypeInferRecursionLimit, 165) LUAU_FASTINTVARIABLE(LuauTypeInferIterationLimit, 20000) LUAU_FASTINTVARIABLE(LuauTypeInferTypePackLoopLimit, 5000) LUAU_FASTINTVARIABLE(LuauCheckRecursionLimit, 300) -LUAU_FASTFLAGVARIABLE(LuauUseVisitRecursionLimit, false) LUAU_FASTINTVARIABLE(LuauVisitRecursionLimit, 500) LUAU_FASTFLAG(LuauKnowsTheDataModel3) -LUAU_FASTFLAG(LuauSeparateTypechecks) LUAU_FASTFLAG(LuauAutocompleteDynamicLimits) LUAU_FASTFLAGVARIABLE(LuauDoNotRelyOnNextBinding, false) LUAU_FASTFLAGVARIABLE(LuauExpectedPropTypeFromIndexer, false) -LUAU_FASTFLAGVARIABLE(LuauWeakEqConstraint, false) // Eventually removed as false. LUAU_FASTFLAGVARIABLE(LuauLowerBoundsCalculation, false) LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false) LUAU_FASTFLAGVARIABLE(LuauSelfCallAutocompleteFix, false) @@ -43,7 +41,6 @@ LUAU_FASTFLAGVARIABLE(LuauUnsealedTableLiteral, false) LUAU_FASTFLAGVARIABLE(LuauTwoPassAliasDefinitionFix, false) LUAU_FASTFLAGVARIABLE(LuauReturnAnyInsteadOfICE, false) // Eventually removed as false. LUAU_FASTFLAG(LuauNormalizeFlagIsConservative) -LUAU_FASTFLAG(LuauWidenIfSupertypeIsFree2) LUAU_FASTFLAGVARIABLE(LuauReturnTypeInferenceInNonstrict, false) LUAU_FASTFLAGVARIABLE(LuauRecursionLimitException, false); LUAU_FASTFLAGVARIABLE(LuauApplyTypeFunctionFix, false); @@ -51,6 +48,7 @@ LUAU_FASTFLAGVARIABLE(LuauTypecheckIter, false); LUAU_FASTFLAGVARIABLE(LuauSuccessTypingForEqualityOperations, false) LUAU_FASTFLAGVARIABLE(LuauNoMethodLocations, false); LUAU_FASTFLAGVARIABLE(LuauAlwaysQuantify, false); +LUAU_FASTFLAGVARIABLE(LuauReportErrorsOnIndexerKeyMismatch, false) namespace Luau { @@ -305,12 +303,8 @@ ModulePtr TypeChecker::checkWithoutRecursionCheck(const SourceModule& module, Mo currentModule.reset(new Module()); currentModule->type = module.type; - - if (FFlag::LuauSeparateTypechecks) - { - currentModule->allocator = module.allocator; - currentModule->names = module.names; - } + currentModule->allocator = module.allocator; + currentModule->names = module.names; iceHandler->moduleName = module.name; @@ -338,21 +332,14 @@ ModulePtr TypeChecker::checkWithoutRecursionCheck(const SourceModule& module, Mo if (prepareModuleScope) prepareModuleScope(module.name, currentModule->getModuleScope()); - if (FFlag::LuauSeparateTypechecks) - { - try - { - checkBlock(moduleScope, *module.root); - } - catch (const TimeLimitError&) - { - currentModule->timeout = true; - } - } - else + try { checkBlock(moduleScope, *module.root); } + catch (const TimeLimitError&) + { + currentModule->timeout = true; + } if (get(follow(moduleScope->returnType))) moduleScope->returnType = addTypePack(TypePack{{}, std::nullopt}); @@ -443,7 +430,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStat& program) else ice("Unknown AstStat"); - if (FFlag::LuauSeparateTypechecks && finishTime && TimeTrace::getClock() > *finishTime) + if (finishTime && TimeTrace::getClock() > *finishTime) throw TimeLimitError(); } @@ -868,9 +855,9 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatAssign& assign) TypeId right = nullptr; - Location loc = 0 == assign.values.size - ? assign.location - : i < assign.values.size ? assign.values.data[i]->location : assign.values.data[assign.values.size - 1]->location; + Location loc = 0 == assign.values.size ? assign.location + : i < assign.values.size ? assign.values.data[i]->location + : assign.values.data[assign.values.size - 1]->location; if (valueIter != valueEnd) { @@ -1825,7 +1812,7 @@ std::optional TypeChecker::findMetatableEntry(TypeId type, std::string e } std::optional TypeChecker::getIndexTypeFromType( - const ScopePtr& scope, TypeId type, const Name& name, const Location& location, bool addErrors) + const ScopePtr& scope, TypeId type, const std::string& name, const Location& location, bool addErrors) { type = follow(type); @@ -1843,13 +1830,25 @@ std::optional TypeChecker::getIndexTypeFromType( if (TableTypeVar* tableType = getMutableTableType(type)) { - const auto& it = tableType->props.find(name); - if (it != tableType->props.end()) + if (auto it = tableType->props.find(name); it != tableType->props.end()) return it->second.type; else if (auto indexer = tableType->indexer) { - tryUnify(stringType, indexer->indexType, location); - return indexer->indexResultType; + // TODO: Property lookup should work with string singletons or unions thereof as the indexer key type. + ErrorVec errors = tryUnify(stringType, indexer->indexType, location); + + if (FFlag::LuauReportErrorsOnIndexerKeyMismatch) + { + if (errors.empty()) + return indexer->indexResultType; + + if (addErrors) + reportError(location, UnknownProperty{type, name}); + + return std::nullopt; + } + else + return indexer->indexResultType; } else if (tableType->state == TableState::Free) { @@ -1858,8 +1857,7 @@ std::optional TypeChecker::getIndexTypeFromType( return result; } - auto found = findTablePropertyRespectingMeta(type, name, location); - if (found) + if (auto found = findTablePropertyRespectingMeta(type, name, location)) return *found; } else if (const ClassTypeVar* cls = get(type)) @@ -2512,8 +2510,9 @@ TypeId TypeChecker::checkRelationalOperation( if (!matches) { - reportError(expr.location, GenericError{format("Types %s and %s cannot be compared with %s because they do not have the same metatable", - toString(lhsType).c_str(), toString(rhsType).c_str(), toString(expr.op).c_str())}); + reportError( + expr.location, GenericError{format("Types %s and %s cannot be compared with %s because they do not have the same metatable", + toString(lhsType).c_str(), toString(rhsType).c_str(), toString(expr.op).c_str())}); return errorRecoveryType(booleanType); } } @@ -2522,8 +2521,9 @@ TypeId TypeChecker::checkRelationalOperation( { if (bool(leftMetatable) != bool(rightMetatable) && leftMetatable != rightMetatable) { - reportError(expr.location, GenericError{format("Types %s and %s cannot be compared with %s because they do not have the same metatable", - toString(lhsType).c_str(), toString(rhsType).c_str(), toString(expr.op).c_str())}); + reportError( + expr.location, GenericError{format("Types %s and %s cannot be compared with %s because they do not have the same metatable", + toString(lhsType).c_str(), toString(rhsType).c_str(), toString(expr.op).c_str())}); return errorRecoveryType(booleanType); } } @@ -3636,10 +3636,7 @@ void TypeChecker::checkArgumentList( } TypePackId varPack = addTypePack(TypePackVar{TypePack{rest, argIter.tail()}}); - if (FFlag::LuauWidenIfSupertypeIsFree2) - state.tryUnify(varPack, tail); - else - state.tryUnify(tail, varPack); + state.tryUnify(varPack, tail); return; } @@ -3707,7 +3704,7 @@ ExprResult TypeChecker::checkExprPack(const ScopePtr& scope, const A } TypePackId retPack; - if (FFlag::LuauLowerBoundsCalculation || !FFlag::LuauWidenIfSupertypeIsFree2) + if (FFlag::LuauLowerBoundsCalculation) { retPack = freshTypePack(scope->level); } @@ -3868,9 +3865,7 @@ std::optional> TypeChecker::checkCallOverload(const Scope Widen widen{¤tModule->internalTypes}; for (; it != endIt; ++it) { - TypeId t = *it; - TypeId widened = widen.substitute(t).value_or(t); // Surely widening is infallible - adjustedArgTypes.push_back(addType(ConstrainedTypeVar{level, {widened}})); + adjustedArgTypes.push_back(addType(ConstrainedTypeVar{level, {widen(*it)}})); } TypePackId adjustedArgPack = addTypePack(TypePack{std::move(adjustedArgTypes), it.tail()}); @@ -3885,14 +3880,11 @@ std::optional> TypeChecker::checkCallOverload(const Scope else { TypeId r = addType(FunctionTypeVar(scope->level, argPack, retPack)); - if (FFlag::LuauWidenIfSupertypeIsFree2) - { - UnifierOptions options; - options.isFunctionCall = true; - unify(r, fn, expr.location, options); - } - else - unify(fn, r, expr.location); + + UnifierOptions options; + options.isFunctionCall = true; + unify(r, fn, expr.location, options); + return {{retPack}}; } } @@ -4375,122 +4367,6 @@ void TypeChecker::unifyWithInstantiationIfNeeded(const ScopePtr& scope, TypeId s } } -bool Instantiation::isDirty(TypeId ty) -{ - if (const FunctionTypeVar* ftv = log->getMutable(ty)) - { - if (ftv->hasNoGenerics) - return false; - - return true; - } - else - { - return false; - } -} - -bool Instantiation::isDirty(TypePackId tp) -{ - return false; -} - -bool Instantiation::ignoreChildren(TypeId ty) -{ - if (log->getMutable(ty)) - return true; - else - return false; -} - -TypeId Instantiation::clean(TypeId ty) -{ - const FunctionTypeVar* ftv = log->getMutable(ty); - LUAU_ASSERT(ftv); - - FunctionTypeVar clone = FunctionTypeVar{level, ftv->argTypes, ftv->retType, ftv->definition, ftv->hasSelf}; - clone.magicFunction = ftv->magicFunction; - clone.tags = ftv->tags; - clone.argNames = ftv->argNames; - TypeId result = addType(std::move(clone)); - - // Annoyingly, we have to do this even if there are no generics, - // to replace any generic tables. - ReplaceGenerics replaceGenerics{log, arena, level, ftv->generics, ftv->genericPacks}; - - // TODO: What to do if this returns nullopt? - // We don't have access to the error-reporting machinery - result = replaceGenerics.substitute(result).value_or(result); - - asMutable(result)->documentationSymbol = ty->documentationSymbol; - return result; -} - -TypePackId Instantiation::clean(TypePackId tp) -{ - LUAU_ASSERT(false); - return tp; -} - -bool ReplaceGenerics::ignoreChildren(TypeId ty) -{ - if (const FunctionTypeVar* ftv = log->getMutable(ty)) - { - if (ftv->hasNoGenerics) - return true; - - // We aren't recursing in the case of a generic function which - // binds the same generics. This can happen if, for example, there's recursive types. - // If T = (a,T)->T then instantiating T should produce T' = (X,T)->T not T' = (X,T')->T'. - // It's OK to use vector equality here, since we always generate fresh generics - // whenever we quantify, so the vectors overlap if and only if they are equal. - return (!generics.empty() || !genericPacks.empty()) && (ftv->generics == generics) && (ftv->genericPacks == genericPacks); - } - else - { - return false; - } -} - -bool ReplaceGenerics::isDirty(TypeId ty) -{ - if (const TableTypeVar* ttv = log->getMutable(ty)) - return ttv->state == TableState::Generic; - else if (log->getMutable(ty)) - return std::find(generics.begin(), generics.end(), ty) != generics.end(); - else - return false; -} - -bool ReplaceGenerics::isDirty(TypePackId tp) -{ - if (log->getMutable(tp)) - return std::find(genericPacks.begin(), genericPacks.end(), tp) != genericPacks.end(); - else - return false; -} - -TypeId ReplaceGenerics::clean(TypeId ty) -{ - LUAU_ASSERT(isDirty(ty)); - if (const TableTypeVar* ttv = log->getMutable(ty)) - { - TableTypeVar clone = TableTypeVar{ttv->props, ttv->indexer, level, TableState::Free}; - if (!FFlag::LuauNoMethodLocations) - clone.methodDefinitionLocations = ttv->methodDefinitionLocations; - clone.definitionModuleName = ttv->definitionModuleName; - return addType(std::move(clone)); - } - else - return addType(FreeTypeVar{level}); -} - -TypePackId ReplaceGenerics::clean(TypePackId tp) -{ - LUAU_ASSERT(isDirty(tp)); - return addTypePack(TypePackVar(FreeTypePack{level})); -} - bool Anyification::isDirty(TypeId ty) { if (ty->persistent) @@ -5295,7 +5171,7 @@ TypeId ApplyTypeFunction::clean(TypeId ty) { TypeId& arg = typeArguments[ty]; if (FFlag::LuauApplyTypeFunctionFix) - { + { LUAU_ASSERT(arg); return arg; } @@ -5309,7 +5185,7 @@ TypePackId ApplyTypeFunction::clean(TypePackId tp) { TypePackId& arg = typePackArguments[tp]; if (FFlag::LuauApplyTypeFunctionFix) - { + { LUAU_ASSERT(arg); return arg; } @@ -5837,9 +5713,6 @@ void TypeChecker::resolve(const EqPredicate& eqP, RefinementMap& refis, const Sc return; // Optimization: the other side has unknown types, so there's probably an overlap. Refining is no-op here. auto predicate = [&](TypeId option) -> std::optional { - if (sense && isUndecidable(option)) - return FFlag::LuauWeakEqConstraint ? option : eqP.type; - if (!sense && isNil(eqP.type)) return (isUndecidable(option) || !isNil(option)) ? std::optional(option) : std::nullopt; diff --git a/Analysis/src/Unifier.cpp b/Analysis/src/Unifier.cpp index 9308e9ff..414b05f4 100644 --- a/Analysis/src/Unifier.cpp +++ b/Analysis/src/Unifier.cpp @@ -21,8 +21,6 @@ LUAU_FASTFLAGVARIABLE(LuauTableSubtypingVariance2, false); LUAU_FASTFLAG(LuauLowerBoundsCalculation); LUAU_FASTFLAG(LuauErrorRecoveryType); LUAU_FASTFLAGVARIABLE(LuauSubtypingAddOptPropsToUnsealedTables, false) -LUAU_FASTFLAGVARIABLE(LuauWidenIfSupertypeIsFree2, false) -LUAU_FASTFLAGVARIABLE(LuauDifferentOrderOfUnificationDoesntMatter2, false) LUAU_FASTFLAGVARIABLE(LuauTxnLogRefreshFunctionPointers, false) namespace Luau @@ -149,8 +147,7 @@ static void promoteTypeLevels(TxnLog& log, const TypeArena* typeArena, TypeLevel return; PromoteTypeLevels ptl{log, typeArena, minLevel}; - DenseHashSet seen{nullptr}; - DEPRECATED_visitTypeVarOnce(ty, ptl, seen); + ptl.traverse(ty); } void promoteTypeLevels(TxnLog& log, const TypeArena* typeArena, TypeLevel minLevel, TypePackId tp) @@ -160,8 +157,7 @@ void promoteTypeLevels(TxnLog& log, const TypeArena* typeArena, TypeLevel minLev return; PromoteTypeLevels ptl{log, typeArena, minLevel}; - DenseHashSet seen{nullptr}; - DEPRECATED_visitTypeVarOnce(tp, ptl, seen); + ptl.traverse(tp); } struct SkipCacheForType final : TypeVarOnceVisitor @@ -172,49 +168,6 @@ struct SkipCacheForType final : TypeVarOnceVisitor { } - // TODO cycle() and operator() can be clipped with FFlagLuauUseVisitRecursionLimit - void cycle(TypeId) override {} - void cycle(TypePackId) override {} - - bool operator()(TypeId ty, const FreeTypeVar& ftv) - { - return visit(ty, ftv); - } - bool operator()(TypeId ty, const BoundTypeVar& btv) - { - return visit(ty, btv); - } - bool operator()(TypeId ty, const GenericTypeVar& gtv) - { - return visit(ty, gtv); - } - bool operator()(TypeId ty, const TableTypeVar& ttv) - { - return visit(ty, ttv); - } - bool operator()(TypePackId tp, const FreeTypePack& ftp) - { - return visit(tp, ftp); - } - bool operator()(TypePackId tp, const BoundTypePack& ftp) - { - return visit(tp, ftp); - } - bool operator()(TypePackId tp, const GenericTypePack& ftp) - { - return visit(tp, ftp); - } - template - bool operator()(TypeId ty, const T& t) - { - return visit(ty); - } - template - bool operator()(TypePackId tp, const T&) - { - return visit(tp); - } - bool visit(TypeId, const FreeTypeVar&) override { result = true; @@ -341,6 +294,16 @@ bool Widen::ignoreChildren(TypeId ty) return !log->is(ty); } +TypeId Widen::operator()(TypeId ty) +{ + return substitute(ty).value_or(ty); +} + +TypePackId Widen::operator()(TypePackId tp) +{ + return substitute(tp).value_or(tp); +} + static std::optional hasUnificationTooComplex(const ErrorVec& errors) { auto isUnificationTooComplex = [](const TypeError& te) { @@ -475,6 +438,8 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool if (!occursFailed) { promoteTypeLevels(log, types, superLevel, subTy); + + Widen widen{types}; log.replace(superTy, BoundTypeVar(widen(subTy))); } @@ -612,9 +577,6 @@ void Unifier::tryUnifyUnionWithType(TypeId subTy, const UnionTypeVar* uv, TypeId std::optional unificationTooComplex; std::optional firstFailedOption; - size_t count = uv->options.size(); - size_t i = 0; - for (TypeId type : uv->options) { Unifier innerState = makeChildUnifier(); @@ -630,60 +592,44 @@ void Unifier::tryUnifyUnionWithType(TypeId subTy, const UnionTypeVar* uv, TypeId failed = true; } - - if (FFlag::LuauDifferentOrderOfUnificationDoesntMatter2) - { - } - else - { - if (i == count - 1) - { - log.concat(std::move(innerState.log)); - } - - ++i; - } } // even if A | B <: T fails, we want to bind some options of T with A | B iff A | B was a subtype of that option. - if (FFlag::LuauDifferentOrderOfUnificationDoesntMatter2) - { - auto tryBind = [this, subTy](TypeId superOption) { - superOption = log.follow(superOption); + auto tryBind = [this, subTy](TypeId superOption) { + superOption = log.follow(superOption); - // just skip if the superOption is not free-ish. - auto ttv = log.getMutable(superOption); - if (!log.is(superOption) && (!ttv || ttv->state != TableState::Free)) - return; + // just skip if the superOption is not free-ish. + auto ttv = log.getMutable(superOption); + if (!log.is(superOption) && (!ttv || ttv->state != TableState::Free)) + return; - // If superOption is already present in subTy, do nothing. Nothing new has been learned, but the subtype - // test is successful. - if (auto subUnion = get(subTy)) - { - if (end(subUnion) != std::find(begin(subUnion), end(subUnion), superOption)) - return; - } - - // Since we have already checked if S <: T, checking it again will not queue up the type for replacement. - // So we'll have to do it ourselves. We assume they unified cleanly if they are still in the seen set. - if (log.haveSeen(subTy, superOption)) - { - // TODO: would it be nice for TxnLog::replace to do this? - if (log.is(superOption)) - log.bindTable(superOption, subTy); - else - log.replace(superOption, *subTy); - } - }; - - if (auto utv = log.getMutable(superTy)) + // If superOption is already present in subTy, do nothing. Nothing new has been learned, but the subtype + // test is successful. + if (auto subUnion = get(subTy)) { - for (TypeId ty : utv) - tryBind(ty); + if (end(subUnion) != std::find(begin(subUnion), end(subUnion), superOption)) + return; } - else - tryBind(superTy); + + // Since we have already checked if S <: T, checking it again will not queue up the type for replacement. + // So we'll have to do it ourselves. We assume they unified cleanly if they are still in the seen set. + if (log.haveSeen(subTy, superOption)) + { + // TODO: would it be nice for TxnLog::replace to do this? + if (log.is(superOption)) + log.bindTable(superOption, subTy); + else + log.replace(superOption, *subTy); + } + }; + + if (auto utv = log.getMutable(superTy)) + { + for (TypeId ty : utv) + tryBind(ty); } + else + tryBind(superTy); if (unificationTooComplex) reportError(*unificationTooComplex); @@ -883,7 +829,7 @@ bool Unifier::canCacheResult(TypeId subTy, TypeId superTy) auto skipCacheFor = [this](TypeId ty) { SkipCacheForType visitor{sharedState.skipCacheForType, types}; - DEPRECATED_visitTypeVarOnce(ty, visitor, sharedState.seenAny); + visitor.traverse(ty); sharedState.skipCacheForType[ty] = visitor.result; @@ -1088,6 +1034,7 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal if (!log.getMutable(superTp)) { + Widen widen{types}; log.replace(superTp, Unifiable::Bound(widen(subTp))); } } @@ -1671,28 +1618,6 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection) } } -TypeId Unifier::widen(TypeId ty) -{ - if (!FFlag::LuauWidenIfSupertypeIsFree2) - return ty; - - Widen widen{types}; - std::optional result = widen.substitute(ty); - // TODO: what does it mean for substitution to fail to widen? - return result.value_or(ty); -} - -TypePackId Unifier::widen(TypePackId tp) -{ - if (!FFlag::LuauWidenIfSupertypeIsFree2) - return tp; - - Widen widen{types}; - std::optional result = widen.substitute(tp); - // TODO: what does it mean for substitution to fail to widen? - return result.value_or(tp); -} - TypeId Unifier::deeplyOptional(TypeId ty, std::unordered_map seen) { ty = follow(ty); @@ -1809,10 +1734,7 @@ void Unifier::tryUnifyFreeTable(TypeId subTy, TypeId superTy) { if (auto subProp = findTablePropertyRespectingMeta(subTy, freeName)) { - if (FFlag::LuauWidenIfSupertypeIsFree2) - tryUnify_(*subProp, freeProp.type); - else - tryUnify_(freeProp.type, *subProp); + tryUnify_(*subProp, freeProp.type); /* * TypeVars are commonly cyclic, so it is entirely possible diff --git a/Ast/include/Luau/TimeTrace.h b/Ast/include/Luau/TimeTrace.h index 9f7b2bdf..be282827 100644 --- a/Ast/include/Luau/TimeTrace.h +++ b/Ast/include/Luau/TimeTrace.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 "Common.h" +#include "Luau/Common.h" #include diff --git a/Ast/src/Parser.cpp b/Ast/src/Parser.cpp index c053e6bd..eaf19914 100644 --- a/Ast/src/Parser.cpp +++ b/Ast/src/Parser.cpp @@ -11,6 +11,8 @@ LUAU_FASTINTVARIABLE(LuauRecursionLimit, 1000) LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100) +LUAU_FASTFLAGVARIABLE(LuauParserFunctionKeywordAsTypeHelp, false) + namespace Luau { @@ -1589,6 +1591,17 @@ AstTypeOrPack Parser::parseSimpleTypeAnnotation(bool allowPack) { return parseFunctionTypeAnnotation(allowPack); } + else if (FFlag::LuauParserFunctionKeywordAsTypeHelp && lexer.current().type == Lexeme::ReservedFunction) + { + Location location = lexer.current().location; + + nextLexeme(); + + return {reportTypeAnnotationError(location, {}, /*isMissing*/ false, + "Using 'function' as a type annotation is not supported, consider replacing with a function type annotation e.g. '(...any) -> " + "...any'"), + {}}; + } else { Location location = lexer.current().location; diff --git a/CMakeLists.txt b/CMakeLists.txt index ea352309..c624a132 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,9 +19,11 @@ if(LUAU_STATIC_CRT) endif() project(Luau LANGUAGES CXX C) +add_library(Luau.Common INTERFACE) add_library(Luau.Ast STATIC) add_library(Luau.Compiler STATIC) add_library(Luau.Analysis STATIC) +add_library(Luau.CodeGen STATIC) add_library(Luau.VM STATIC) add_library(isocline STATIC) @@ -48,8 +50,11 @@ endif() include(Sources.cmake) +target_include_directories(Luau.Common INTERFACE Common/include) + target_compile_features(Luau.Ast PUBLIC cxx_std_17) target_include_directories(Luau.Ast PUBLIC Ast/include) +target_link_libraries(Luau.Ast PUBLIC Luau.Common) target_compile_features(Luau.Compiler PUBLIC cxx_std_17) target_include_directories(Luau.Compiler PUBLIC Compiler/include) @@ -59,8 +64,13 @@ target_compile_features(Luau.Analysis PUBLIC cxx_std_17) target_include_directories(Luau.Analysis PUBLIC Analysis/include) target_link_libraries(Luau.Analysis PUBLIC Luau.Ast) +target_compile_features(Luau.CodeGen PRIVATE cxx_std_17) +target_include_directories(Luau.CodeGen PUBLIC CodeGen/include) +target_link_libraries(Luau.CodeGen PUBLIC Luau.Common) + target_compile_features(Luau.VM PRIVATE cxx_std_11) target_include_directories(Luau.VM PUBLIC VM/include) +target_link_libraries(Luau.VM PUBLIC Luau.Common) target_include_directories(isocline PUBLIC extern/isocline/include) @@ -101,6 +111,7 @@ endif() target_compile_options(Luau.Ast PRIVATE ${LUAU_OPTIONS}) target_compile_options(Luau.Analysis PRIVATE ${LUAU_OPTIONS}) +target_compile_options(Luau.CodeGen PRIVATE ${LUAU_OPTIONS}) target_compile_options(Luau.VM PRIVATE ${LUAU_OPTIONS}) target_compile_options(isocline PRIVATE ${LUAU_OPTIONS} ${ISOCLINE_OPTIONS}) @@ -120,6 +131,7 @@ endif() if(MSVC) target_link_options(Luau.Ast INTERFACE /NATVIS:${CMAKE_CURRENT_SOURCE_DIR}/tools/natvis/Ast.natvis) target_link_options(Luau.Analysis INTERFACE /NATVIS:${CMAKE_CURRENT_SOURCE_DIR}/tools/natvis/Analysis.natvis) + target_link_options(Luau.CodeGen INTERFACE /NATVIS:${CMAKE_CURRENT_SOURCE_DIR}/tools/natvis/CodeGen.natvis) target_link_options(Luau.VM INTERFACE /NATVIS:${CMAKE_CURRENT_SOURCE_DIR}/tools/natvis/VM.natvis) endif() @@ -127,6 +139,7 @@ endif() if(MSVC_IDE) target_sources(Luau.Ast PRIVATE tools/natvis/Ast.natvis) target_sources(Luau.Analysis PRIVATE tools/natvis/Analysis.natvis) + target_sources(Luau.CodeGen PRIVATE tools/natvis/CodeGen.natvis) target_sources(Luau.VM PRIVATE tools/natvis/VM.natvis) endif() @@ -154,7 +167,7 @@ endif() if(LUAU_BUILD_TESTS) target_compile_options(Luau.UnitTest PRIVATE ${LUAU_OPTIONS}) target_include_directories(Luau.UnitTest PRIVATE extern) - target_link_libraries(Luau.UnitTest PRIVATE Luau.Analysis Luau.Compiler) + target_link_libraries(Luau.UnitTest PRIVATE Luau.Analysis Luau.Compiler Luau.CodeGen) target_compile_options(Luau.Conformance PRIVATE ${LUAU_OPTIONS}) target_include_directories(Luau.Conformance PRIVATE extern) diff --git a/CodeGen/include/Luau/AssemblyBuilderX64.h b/CodeGen/include/Luau/AssemblyBuilderX64.h new file mode 100644 index 00000000..c5979d3c --- /dev/null +++ b/CodeGen/include/Luau/AssemblyBuilderX64.h @@ -0,0 +1,169 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#pragma once + +#include "Luau/Common.h" +#include "Luau/Condition.h" +#include "Luau/Label.h" +#include "Luau/OperandX64.h" +#include "Luau/RegisterX64.h" + +#include +#include + +namespace Luau +{ +namespace CodeGen +{ + +class AssemblyBuilderX64 +{ +public: + explicit AssemblyBuilderX64(bool logText); + ~AssemblyBuilderX64(); + + // Base two operand instructions with 9 opcode selection + void add(OperandX64 lhs, OperandX64 rhs); + void sub(OperandX64 lhs, OperandX64 rhs); + void cmp(OperandX64 lhs, OperandX64 rhs); + void and_(OperandX64 lhs, OperandX64 rhs); + void or_(OperandX64 lhs, OperandX64 rhs); + void xor_(OperandX64 lhs, OperandX64 rhs); + + // Binary shift instructions with special rhs handling + void sal(OperandX64 lhs, OperandX64 rhs); + void sar(OperandX64 lhs, OperandX64 rhs); + void shl(OperandX64 lhs, OperandX64 rhs); + void shr(OperandX64 lhs, OperandX64 rhs); + + // Two operand mov instruction has additional specialized encodings + void mov(OperandX64 lhs, OperandX64 rhs); + void mov64(RegisterX64 lhs, int64_t imm); + + // Base one operand instruction with 2 opcode selection + void div(OperandX64 op); + void idiv(OperandX64 op); + void mul(OperandX64 op); + void neg(OperandX64 op); + void not_(OperandX64 op); + + void test(OperandX64 lhs, OperandX64 rhs); + void lea(OperandX64 lhs, OperandX64 rhs); + + void push(OperandX64 op); + void pop(OperandX64 op); + void ret(); + + // Control flow + void jcc(Condition cond, Label& label); + void jmp(Label& label); + void jmp(OperandX64 op); + + // AVX + void vaddpd(OperandX64 dst, OperandX64 src1, OperandX64 src2); + void vaddps(OperandX64 dst, OperandX64 src1, OperandX64 src2); + void vaddsd(OperandX64 dst, OperandX64 src1, OperandX64 src2); + void vaddss(OperandX64 dst, OperandX64 src1, OperandX64 src2); + + void vsqrtpd(OperandX64 dst, OperandX64 src); + void vsqrtps(OperandX64 dst, OperandX64 src); + void vsqrtsd(OperandX64 dst, OperandX64 src1, OperandX64 src2); + void vsqrtss(OperandX64 dst, OperandX64 src1, OperandX64 src2); + + void vmovsd(OperandX64 dst, OperandX64 src); + void vmovsd(OperandX64 dst, OperandX64 src1, OperandX64 src2); + void vmovss(OperandX64 dst, OperandX64 src); + void vmovss(OperandX64 dst, OperandX64 src1, OperandX64 src2); + void vmovapd(OperandX64 dst, OperandX64 src); + void vmovaps(OperandX64 dst, OperandX64 src); + void vmovupd(OperandX64 dst, OperandX64 src); + void vmovups(OperandX64 dst, OperandX64 src); + + // Run final checks + void finalize(); + + // Places a label at current location and returns it + Label setLabel(); + + // Assigns label position to the current location + void setLabel(Label& label); + + // Constant allocation (uses rip-relative addressing) + OperandX64 i64(int64_t value); + OperandX64 f32(float value); + OperandX64 f64(double value); + OperandX64 f32x4(float x, float y, float z, float w); + + // Resulting data and code that need to be copied over one after the other + // The *end* of 'data' has to be aligned to 16 bytes, this will also align 'code' + std::vector data; + std::vector code; + + std::string text; + +private: + // Instruction archetypes + void placeBinary(const char* name, OperandX64 lhs, OperandX64 rhs, uint8_t codeimm8, uint8_t codeimm, uint8_t codeimmImm8, uint8_t code8rev, + uint8_t coderev, uint8_t code8, uint8_t code, uint8_t opreg); + void placeBinaryRegMemAndImm(OperandX64 lhs, OperandX64 rhs, uint8_t code8, uint8_t code, uint8_t codeImm8, uint8_t opreg); + void placeBinaryRegAndRegMem(OperandX64 lhs, OperandX64 rhs, uint8_t code8, uint8_t code); + void placeBinaryRegMemAndReg(OperandX64 lhs, OperandX64 rhs, uint8_t code8, uint8_t code); + + void placeUnaryModRegMem(const char* name, OperandX64 op, uint8_t code8, uint8_t code, uint8_t opreg); + + void placeShift(const char* name, OperandX64 lhs, OperandX64 rhs, uint8_t opreg); + + void placeJcc(const char* name, Label& label, uint8_t cc); + + void placeAvx(const char* name, OperandX64 dst, OperandX64 src, uint8_t code, bool setW, uint8_t mode, uint8_t prefix); + void placeAvx(const char* name, OperandX64 dst, OperandX64 src, uint8_t code, uint8_t coderev, bool setW, uint8_t mode, uint8_t prefix); + void placeAvx(const char* name, OperandX64 dst, OperandX64 src1, OperandX64 src2, uint8_t code, bool setW, uint8_t mode, uint8_t prefix); + + // Instruction components + void placeRegAndModRegMem(OperandX64 lhs, OperandX64 rhs); + void placeModRegMem(OperandX64 rhs, uint8_t regop); + void placeRex(RegisterX64 op); + void placeRex(OperandX64 op); + void placeRex(RegisterX64 lhs, OperandX64 rhs); + void placeVex(OperandX64 dst, OperandX64 src1, OperandX64 src2, bool setW, uint8_t mode, uint8_t prefix); + void placeImm8Or32(int32_t imm); + void placeImm8(int32_t imm); + void placeImm32(int32_t imm); + void placeImm64(int64_t imm); + void placeLabel(Label& label); + void place(uint8_t byte); + + void commit(); + LUAU_NOINLINE void extend(); + uint32_t getCodeSize(); + + // Data + size_t allocateData(size_t size, size_t align); + + // Logging of assembly in text form (Intel asm with VS disassembly formatting) + LUAU_NOINLINE void log(const char* opcode); + LUAU_NOINLINE void log(const char* opcode, OperandX64 op); + LUAU_NOINLINE void log(const char* opcode, OperandX64 op1, OperandX64 op2); + LUAU_NOINLINE void log(const char* opcode, OperandX64 op1, OperandX64 op2, OperandX64 op3); + LUAU_NOINLINE void log(Label label); + LUAU_NOINLINE void log(const char* opcode, Label label); + void log(OperandX64 op); + void logAppend(const char* fmt, ...); + + const char* getSizeName(SizeX64 size); + const char* getRegisterName(RegisterX64 reg); + + uint32_t nextLabel = 1; + std::vector