Sync to upstream/release/665

This commit is contained in:
Hunter Goldstein 2025-03-14 08:44:35 -07:00
parent 7568957f10
commit af2025306a
32 changed files with 1056 additions and 409 deletions

View file

@ -1,8 +1,9 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include <string>
#include <memory>
#include <optional>
#include <string>
#include <vector>
namespace Luau
@ -32,15 +33,71 @@ struct ModuleInfo
bool optional = false;
};
struct RequireAlias
{
std::string alias; // Unprefixed alias name (no leading `@`).
std::vector<std::string> tags = {};
};
struct RequireNode
{
virtual ~RequireNode() {}
// Get the path component representing this node.
virtual std::string getPathComponent() const = 0;
// Get the displayed user-facing label for this node, defaults to getPathComponent()
virtual std::string getLabel() const
{
return getPathComponent();
}
// Get tags to attach to this node's RequireSuggestion (defaults to none).
virtual std::vector<std::string> getTags() const
{
return {};
}
// TODO: resolvePathToNode() can ultimately be replaced with a call into
// require-by-string's path resolution algorithm. This will first require
// generalizing that algorithm to work with a virtual file system.
virtual std::unique_ptr<RequireNode> resolvePathToNode(const std::string& path) const = 0;
// Get children of this node, if any (if this node represents a directory).
virtual std::vector<std::unique_ptr<RequireNode>> getChildren() const = 0;
// A list of the aliases available to this node.
virtual std::vector<RequireAlias> getAvailableAliases() const = 0;
};
struct RequireSuggestion
{
std::string label;
std::string fullPath;
std::vector<std::string> tags;
};
using RequireSuggestions = std::vector<RequireSuggestion>;
struct RequireSuggester
{
virtual ~RequireSuggester() {}
std::optional<RequireSuggestions> getRequireSuggestions(const ModuleName& requirer, const std::optional<std::string>& pathString) const;
protected:
virtual std::unique_ptr<RequireNode> getNode(const ModuleName& name) const = 0;
private:
std::optional<RequireSuggestions> getRequireSuggestionsImpl(const ModuleName& requirer, const std::optional<std::string>& path) const;
};
struct FileResolver
{
FileResolver() = default;
FileResolver(std::shared_ptr<RequireSuggester> requireSuggester)
: requireSuggester(std::move(requireSuggester))
{
}
virtual ~FileResolver() {}
virtual std::optional<SourceCode> readSource(const ModuleName& name) = 0;
@ -60,10 +117,10 @@ struct FileResolver
return std::nullopt;
}
virtual std::optional<RequireSuggestions> getRequireSuggestions(const ModuleName& requirer, const std::optional<std::string>& pathString) const
{
return std::nullopt;
}
// Make non-virtual when removing FFlagLuauImproveRequireByStringAutocomplete.
virtual std::optional<RequireSuggestions> getRequireSuggestions(const ModuleName& requirer, const std::optional<std::string>& pathString) const;
std::shared_ptr<RequireSuggester> requireSuggester;
};
struct NullFileResolver : FileResolver

View file

@ -15,6 +15,28 @@ namespace Luau
{
struct FrontendOptions;
enum class FragmentAutocompleteWaypoint
{
ParseFragmentEnd,
CloneModuleStart,
CloneModuleEnd,
DfgBuildEnd,
CloneAndSquashScopeStart,
CloneAndSquashScopeEnd,
ConstraintSolverStart,
ConstraintSolverEnd,
TypecheckFragmentEnd,
AutocompleteEnd,
COUNT,
};
class IFragmentAutocompleteReporter
{
public:
virtual void reportWaypoint(FragmentAutocompleteWaypoint) = 0;
virtual void reportFragmentString(std::string_view) = 0;
};
enum class FragmentTypeCheckStatus
{
SkipAutocomplete,
@ -70,7 +92,8 @@ std::pair<FragmentTypeCheckStatus, FragmentTypeCheckResult> typecheckFragment(
const Position& cursorPos,
std::optional<FrontendOptions> opts,
std::string_view src,
std::optional<Position> fragmentEndPosition
std::optional<Position> fragmentEndPosition,
IFragmentAutocompleteReporter* reporter = nullptr
);
FragmentAutocompleteResult fragmentAutocomplete(
@ -80,7 +103,8 @@ FragmentAutocompleteResult fragmentAutocomplete(
Position cursorPosition,
std::optional<FrontendOptions> opts,
StringCompletionCallback callback,
std::optional<Position> fragmentEndPosition = std::nullopt
std::optional<Position> fragmentEndPosition = std::nullopt,
IFragmentAutocompleteReporter* reporter = nullptr
);
enum class FragmentAutocompleteStatus
@ -102,6 +126,7 @@ struct FragmentContext
const ParseResult& freshParse;
std::optional<FrontendOptions> opts;
std::optional<Position> DEPRECATED_fragmentEndPosition;
IFragmentAutocompleteReporter* reporter = nullptr;
};
/**

View file

@ -31,13 +31,4 @@ struct OrderedMap
}
};
struct QuantifierResult
{
TypeId result;
OrderedMap<TypeId, TypeId> insertedGenerics;
OrderedMap<TypePackId, TypePackId> insertedGenericPacks;
};
std::optional<QuantifierResult> quantify(TypeArena* arena, TypeId ty, Scope* scope);
} // namespace Luau

View file

@ -25,6 +25,8 @@ LUAU_FASTINT(LuauTypeInferIterationLimit)
LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAGVARIABLE(DebugLuauMagicVariableNames)
LUAU_FASTFLAG(LuauExposeRequireByStringAutocomplete)
LUAU_FASTFLAGVARIABLE(LuauAutocompleteRefactorsForIncrementalAutocomplete)
LUAU_FASTFLAGVARIABLE(LuauAutocompleteUsesModuleForTypeCompatibility)
@ -1519,10 +1521,14 @@ static std::optional<AutocompleteEntryMap> convertRequireSuggestionsToAutocomple
return std::nullopt;
AutocompleteEntryMap result;
for (const RequireSuggestion& suggestion : *suggestions)
for (RequireSuggestion& suggestion : *suggestions)
{
AutocompleteEntry entry = {AutocompleteEntryKind::RequirePath};
entry.insertText = std::move(suggestion.fullPath);
if (FFlag::LuauExposeRequireByStringAutocomplete)
{
entry.tags = std::move(suggestion.tags);
}
result[std::move(suggestion.label)] = std::move(entry);
}
return result;

View file

@ -1,6 +1,7 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Clone.h"
#include "Luau/Common.h"
#include "Luau/NotNull.h"
#include "Luau/Type.h"
#include "Luau/TypePack.h"
@ -12,6 +13,8 @@ LUAU_FASTFLAG(LuauFreezeIgnorePersistent)
// For each `Luau::clone` call, we will clone only up to N amount of types _and_ packs, as controlled by this limit.
LUAU_FASTINTVARIABLE(LuauTypeCloneIterationLimit, 100'000)
LUAU_FASTFLAGVARIABLE(LuauClonedTableAndFunctionTypesMustHaveScopes)
LUAU_FASTFLAGVARIABLE(LuauDoNotClonePersistentBindings)
namespace Luau
{
@ -477,7 +480,7 @@ private:
class FragmentAutocompleteTypeCloner final : public TypeCloner
{
Scope* freeTypeReplacementScope = nullptr;
Scope* replacementForNullScope = nullptr;
public:
FragmentAutocompleteTypeCloner(
@ -487,12 +490,12 @@ public:
NotNull<SeenTypePacks> packs,
TypeId forceTy,
TypePackId forceTp,
Scope* freeTypeReplacementScope
Scope* replacementForNullScope
)
: TypeCloner(arena, builtinTypes, types, packs, forceTy, forceTp)
, freeTypeReplacementScope(freeTypeReplacementScope)
, replacementForNullScope(replacementForNullScope)
{
LUAU_ASSERT(freeTypeReplacementScope);
LUAU_ASSERT(replacementForNullScope);
}
TypeId shallowClone(TypeId ty) override
@ -512,12 +515,18 @@ public:
generic->scope = nullptr;
else if (auto free = getMutable<FreeType>(target))
{
free->scope = freeTypeReplacementScope;
free->scope = replacementForNullScope;
}
else if (auto tt = getMutable<TableType>(target))
{
if (FFlag::LuauClonedTableAndFunctionTypesMustHaveScopes)
tt->scope = replacementForNullScope;
}
else if (auto fn = getMutable<FunctionType>(target))
fn->scope = nullptr;
else if (auto table = getMutable<TableType>(target))
table->scope = nullptr;
{
if (FFlag::LuauClonedTableAndFunctionTypesMustHaveScopes)
fn->scope = replacementForNullScope;
}
(*types)[ty] = target;
queue.emplace_back(target);
@ -538,7 +547,7 @@ public:
if (auto generic = getMutable<GenericTypePack>(target))
generic->scope = nullptr;
else if (auto free = getMutable<FreeTypePack>(target))
free->scope = freeTypeReplacementScope;
free->scope = replacementForNullScope;
(*packs)[tp] = target;
queue.emplace_back(target);
@ -728,7 +737,7 @@ Binding cloneIncremental(const Binding& binding, TypeArena& dest, CloneState& cl
b.deprecatedSuggestion = binding.deprecatedSuggestion;
b.documentationSymbol = binding.documentationSymbol;
b.location = binding.location;
b.typeId = cloner.clone(binding.typeId);
b.typeId = FFlag::LuauDoNotClonePersistentBindings && binding.typeId->persistent ? binding.typeId : cloner.clone(binding.typeId);
return b;
}

View file

@ -38,10 +38,12 @@ LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAGVARIABLE(LuauTrackInteriorFreeTypesOnScope)
LUAU_FASTFLAGVARIABLE(LuauDeferBidirectionalInferenceForTableAssignment)
LUAU_FASTFLAGVARIABLE(LuauUngeneralizedTypesForRecursiveFunctions)
LUAU_FASTFLAGVARIABLE(LuauGlobalSelfAssignmentCycle)
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
LUAU_FASTFLAGVARIABLE(LuauInferLocalTypesInMultipleAssignments)
LUAU_FASTFLAGVARIABLE(LuauDoNotLeakNilInRefinement)
LUAU_FASTFLAGVARIABLE(LuauExtraFollows)
namespace Luau
{
@ -2082,7 +2084,7 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall*
{
std::vector<TypeId> unpackedTypes;
if (args.size() > 0)
target = args[0];
target = FFlag::LuauExtraFollows ? follow(args[0]) : args[0];
else
{
target = arena->addType(BlockedType{});
@ -2891,6 +2893,13 @@ void ConstraintGenerator::visitLValue(const ScopePtr& scope, AstExprGlobal* glob
DefId def = dfg->getDef(global);
rootScope->lvalueTypes[def] = rhsType;
if (FFlag::LuauGlobalSelfAssignmentCycle)
{
// Ignore possible self-assignment, it doesn't create a new constraint
if (annotatedTy == follow(rhsType))
return;
}
// Sketchy: We're specifically looking for BlockedTypes that were
// initially created by ConstraintGenerator::prepopulateGlobalScope.
if (auto bt = get<BlockedType>(follow(*annotatedTy)); bt && !bt->getOwner())

View file

@ -37,6 +37,7 @@ LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
LUAU_FASTFLAGVARIABLE(LuauTrackInteriorFreeTablesOnScope)
LUAU_FASTFLAGVARIABLE(LuauPrecalculateMutatedFreeTypes2)
LUAU_FASTFLAGVARIABLE(DebugLuauGreedyGeneralization)
LUAU_FASTFLAG(LuauSearchForRefineableType)
namespace Luau
{
@ -907,26 +908,16 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
else if (get<PendingExpansionType>(generalizedType))
return block(generalizedType, constraint);
std::optional<QuantifierResult> generalized;
std::optional<TypeId> generalizedTy = generalize(NotNull{arena}, builtinTypes, constraint->scope, generalizedTypes, c.sourceType);
if (generalizedTy)
generalized = QuantifierResult{*generalizedTy}; // FIXME insertedGenerics and insertedGenericPacks
else
if (!generalizedTy)
reportError(CodeTooComplex{}, constraint->location);
if (generalized)
if (generalizedTy)
{
if (get<BlockedType>(generalizedType))
bind(constraint, generalizedType, generalized->result);
bind(constraint, generalizedType, *generalizedTy);
else
unify(constraint, generalizedType, generalized->result);
for (auto [free, gen] : generalized->insertedGenerics.pairings)
unify(constraint, free, gen);
for (auto [free, gen] : generalized->insertedGenericPacks.pairings)
unify(constraint, free, gen);
unify(constraint, generalizedType, *generalizedTy);
}
else
{
@ -1356,15 +1347,29 @@ void ConstraintSolver::fillInDiscriminantTypes(NotNull<const Constraint> constra
if (!ty)
continue;
// If the discriminant type has been transmuted, we need to unblock them.
if (!isBlocked(*ty))
if (FFlag::LuauSearchForRefineableType)
{
if (isBlocked(*ty))
// We bind any unused discriminants to the `*no-refine*` type indicating that it can be safely ignored.
emplaceType<BoundType>(asMutable(follow(*ty)), builtinTypes->noRefineType);
// We also need to unconditionally unblock these types, otherwise
// you end up with funky looking "Blocked on *no-refine*."
unblock(*ty, constraint->location);
continue;
}
else
{
// We bind any unused discriminants to the `*no-refine*` type indicating that it can be safely ignored.
emplaceType<BoundType>(asMutable(follow(*ty)), builtinTypes->noRefineType);
// If the discriminant type has been transmuted, we need to unblock them.
if (!isBlocked(*ty))
{
unblock(*ty, constraint->location);
continue;
}
// We bind any unused discriminants to the `*no-refine*` type indicating that it can be safely ignored.
emplaceType<BoundType>(asMutable(follow(*ty)), builtinTypes->noRefineType);
}
}
}

View file

@ -0,0 +1,172 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/FileResolver.h"
#include "Luau/Common.h"
#include "Luau/StringUtils.h"
#include <algorithm>
#include <memory>
#include <optional>
#include <string_view>
#include <utility>
LUAU_FASTFLAGVARIABLE(LuauExposeRequireByStringAutocomplete)
LUAU_FASTFLAGVARIABLE(LuauEscapeCharactersInRequireSuggestions)
LUAU_FASTFLAGVARIABLE(LuauHideImpossibleRequireSuggestions)
namespace Luau
{
static std::optional<RequireSuggestions> processRequireSuggestions(std::optional<RequireSuggestions> suggestions)
{
if (!suggestions)
return suggestions;
if (FFlag::LuauEscapeCharactersInRequireSuggestions)
{
for (RequireSuggestion& suggestion : *suggestions)
{
suggestion.fullPath = escape(suggestion.fullPath);
}
}
return suggestions;
}
static RequireSuggestions makeSuggestionsFromAliases(std::vector<RequireAlias> aliases)
{
RequireSuggestions result;
for (RequireAlias& alias : aliases)
{
RequireSuggestion suggestion;
suggestion.label = "@" + std::move(alias.alias);
suggestion.fullPath = suggestion.label;
suggestion.tags = std::move(alias.tags);
result.push_back(std::move(suggestion));
}
return result;
}
static RequireSuggestions makeSuggestionsForFirstComponent(std::unique_ptr<RequireNode> node)
{
RequireSuggestions result = makeSuggestionsFromAliases(node->getAvailableAliases());
result.push_back(RequireSuggestion{"./", "./", {}});
result.push_back(RequireSuggestion{"../", "../", {}});
return result;
}
static RequireSuggestions makeSuggestionsFromNode(std::unique_ptr<RequireNode> node, const std::string_view path, bool isPartialPath)
{
LUAU_ASSERT(!path.empty());
RequireSuggestions result;
const size_t lastSlashInPath = path.find_last_of('/');
if (lastSlashInPath != std::string_view::npos)
{
// Add a suggestion for the parent directory
RequireSuggestion parentSuggestion;
parentSuggestion.label = "..";
// TODO: after exposing require-by-string's path normalization API, this
// if-else can be replaced. Instead, we can simply normalize the result
// of inserting ".." at the end of the current path.
if (lastSlashInPath >= 2 && path.substr(lastSlashInPath - 2, 3) == "../")
{
parentSuggestion.fullPath = path.substr(0, lastSlashInPath + 1);
parentSuggestion.fullPath += "..";
}
else
{
parentSuggestion.fullPath = path.substr(0, lastSlashInPath);
}
result.push_back(std::move(parentSuggestion));
}
std::string fullPathPrefix;
if (isPartialPath)
{
// ./path/to/chi -> ./path/to/
fullPathPrefix += path.substr(0, lastSlashInPath + 1);
}
else
{
if (path.back() == '/')
{
// ./path/to/ -> ./path/to/
fullPathPrefix += path;
}
else
{
// ./path/to -> ./path/to/
fullPathPrefix += path;
fullPathPrefix += "/";
}
}
for (const std::unique_ptr<RequireNode>& child : node->getChildren())
{
if (!child)
continue;
std::string pathComponent = child->getPathComponent();
if (FFlag::LuauHideImpossibleRequireSuggestions)
{
// If path component contains a slash, it cannot be required by string.
// There's no point suggesting it.
if (pathComponent.find('/') != std::string::npos)
continue;
}
RequireSuggestion suggestion;
suggestion.label = isPartialPath || path.back() == '/' ? child->getLabel() : "/" + child->getLabel();
suggestion.fullPath = fullPathPrefix + std::move(pathComponent);
suggestion.tags = child->getTags();
result.push_back(std::move(suggestion));
}
return result;
}
std::optional<RequireSuggestions> RequireSuggester::getRequireSuggestionsImpl(const ModuleName& requirer, const std::optional<std::string>& path)
const
{
if (!path)
return std::nullopt;
std::unique_ptr<RequireNode> requirerNode = getNode(requirer);
if (!requirerNode)
return std::nullopt;
const size_t slashPos = path->find_last_of('/');
if (slashPos == std::string::npos)
return makeSuggestionsForFirstComponent(std::move(requirerNode));
// If path already points at a Node, return the Node's children as paths.
if (std::unique_ptr<RequireNode> node = requirerNode->resolvePathToNode(*path))
return makeSuggestionsFromNode(std::move(node), *path, /* isPartialPath = */ false);
// Otherwise, recover a partial path and use this to generate suggestions.
if (std::unique_ptr<RequireNode> partialNode = requirerNode->resolvePathToNode(path->substr(0, slashPos)))
return makeSuggestionsFromNode(std::move(partialNode), *path, /* isPartialPath = */ true);
return std::nullopt;
}
std::optional<RequireSuggestions> RequireSuggester::getRequireSuggestions(const ModuleName& requirer, const std::optional<std::string>& path) const
{
return processRequireSuggestions(getRequireSuggestionsImpl(requirer, path));
}
std::optional<RequireSuggestions> FileResolver::getRequireSuggestions(const ModuleName& requirer, const std::optional<std::string>& path) const
{
if (!FFlag::LuauExposeRequireByStringAutocomplete)
return std::nullopt;
return requireSuggester ? requireSuggester->getRequireSuggestions(requirer, path) : std::nullopt;
}
} // namespace Luau

View file

@ -30,13 +30,15 @@ LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete)
LUAU_FASTFLAGVARIABLE(LuauIncrementalAutocompleteBugfixes)
LUAU_FASTFLAGVARIABLE(LuauMixedModeDefFinderTraversesTypeOf)
LUAU_FASTFLAG(LuauBetterReverseDependencyTracking)
LUAU_FASTFLAGVARIABLE(LuauCloneIncrementalModule)
LUAU_FASTFLAGVARIABLE(LogFragmentsFromAutocomplete)
LUAU_FASTFLAGVARIABLE(LuauBetterCursorInCommentDetection)
LUAU_FASTFLAGVARIABLE(LuauAllFreeTypesHaveScopes)
LUAU_FASTFLAGVARIABLE(LuauFragmentAcSupportsReporter)
LUAU_FASTFLAGVARIABLE(LuauPersistConstraintGenerationScopes)
LUAU_FASTFLAG(LuauModuleHoldsAstRoot)
LUAU_FASTFLAGVARIABLE(LuauCloneTypeAliasBindings)
LUAU_FASTFLAGVARIABLE(LuauCloneReturnTypePack)
namespace
{
@ -100,11 +102,19 @@ struct MixedModeIncrementalTCDefFinder : public AstVisitor
return FFlag::LuauMixedModeDefFinderTraversesTypeOf;
}
bool visit(AstStatTypeAlias* alias) override
{
if (FFlag::LuauCloneTypeAliasBindings)
declaredAliases.insert(std::string(alias->name.value));
return true;
}
// ast defs is just a mapping from expr -> def in general
// will get built up by the dfg builder
// localDefs, we need to copy over
std::vector<std::pair<AstLocal*, AstExpr*>> referencedLocalDefs;
DenseHashSet<Name> declaredAliases{""};
};
void cloneAndSquashScopes_DEPRECATED(
@ -181,6 +191,10 @@ void cloneAndSquashScopes(
scopes.emplace_back(current);
}
MixedModeIncrementalTCDefFinder finder;
if (FFlag::LuauCloneTypeAliasBindings)
program->visit(&finder);
// in reverse order (we need to clone the parents and override defs as we go down the list)
for (auto it = scopes.rbegin(); it != scopes.rend(); ++it)
{
@ -191,6 +205,21 @@ void cloneAndSquashScopes(
// Clone the rvalueRefinements
for (const auto& [def, ty] : curr->rvalueRefinements)
destScope->rvalueRefinements[def] = Luau::cloneIncremental(ty, *destArena, cloneState, destScope);
if (FFlag::LuauCloneTypeAliasBindings)
{
for (const auto& [n, tf] : curr->exportedTypeBindings)
{
if (!finder.declaredAliases.contains(n))
destScope->exportedTypeBindings[n] = Luau::cloneIncremental(tf, *destArena, cloneState, destScope);
}
for (const auto& [n, tf] : curr->privateTypeBindings)
{
if (!finder.declaredAliases.contains(n))
destScope->privateTypeBindings[n] = Luau::cloneIncremental(tf, *destArena, cloneState, destScope);
}
}
for (const auto& [n, m] : curr->importedTypeBindings)
{
std::unordered_map<Name, TypeFun> importedBindingTypes;
@ -206,10 +235,11 @@ void cloneAndSquashScopes(
}
}
if (!FFlag::LuauCloneTypeAliasBindings)
program->visit(&finder);
// The above code associates defs with TypeId's in the scope
// so that lookup to locals will succeed.
MixedModeIncrementalTCDefFinder finder;
program->visit(&finder);
std::vector<std::pair<AstLocal*, AstExpr*>> locals = std::move(finder.referencedLocalDefs);
for (auto [loc, expr] : locals)
{
@ -218,6 +248,10 @@ void cloneAndSquashScopes(
destScope->lvalueTypes[dfg->getDef(expr)] = Luau::cloneIncremental(binding->typeId, *destArena, cloneState, destScope);
}
}
if (FFlag::LuauCloneReturnTypePack && destScope->returnType)
destScope->returnType = Luau::cloneIncremental(destScope->returnType, *destArena, cloneState, destScope);
return;
}
@ -572,6 +606,22 @@ void mixedModeCompatibility(
}
}
static void reportWaypoint(IFragmentAutocompleteReporter* reporter, FragmentAutocompleteWaypoint type)
{
if (!FFlag::LuauFragmentAcSupportsReporter || !reporter)
return;
reporter->reportWaypoint(type);
}
static void reportFragmentString(IFragmentAutocompleteReporter* reporter, std::string_view fragment)
{
if (!FFlag::LuauFragmentAcSupportsReporter || !reporter)
return;
reporter->reportFragmentString(fragment);
}
FragmentTypeCheckResult typecheckFragment_(
Frontend& frontend,
AstStatBlock* root,
@ -579,13 +629,15 @@ FragmentTypeCheckResult typecheckFragment_(
const ScopePtr& closestScope,
const Position& cursorPos,
std::unique_ptr<Allocator> astAllocator,
const FrontendOptions& opts
const FrontendOptions& opts,
IFragmentAutocompleteReporter* reporter
)
{
LUAU_TIMETRACE_SCOPE("Luau::typecheckFragment_", "FragmentAutocomplete");
freeze(stale->internalTypes);
freeze(stale->interfaceTypes);
reportWaypoint(reporter, FragmentAutocompleteWaypoint::CloneModuleStart);
CloneState cloneState{frontend.builtinTypes};
std::shared_ptr<Scope> freshChildOfNearestScope = std::make_shared<Scope>(closestScope);
ModulePtr incrementalModule = nullptr;
@ -596,6 +648,7 @@ FragmentTypeCheckResult typecheckFragment_(
else
incrementalModule = copyModule(stale, std::move(astAllocator));
reportWaypoint(reporter, FragmentAutocompleteWaypoint::CloneModuleEnd);
incrementalModule->checkedInNewSolver = true;
unfreeze(incrementalModule->internalTypes);
unfreeze(incrementalModule->interfaceTypes);
@ -623,6 +676,7 @@ FragmentTypeCheckResult typecheckFragment_(
/// Create a DataFlowGraph just for the surrounding context
DataFlowGraph dfg = DataFlowGraphBuilder::build(root, NotNull{&incrementalModule->defArena}, NotNull{&incrementalModule->keyArena}, iceHandler);
reportWaypoint(reporter, FragmentAutocompleteWaypoint::DfgBuildEnd);
SimplifierPtr simplifier = newSimplifier(NotNull{&incrementalModule->internalTypes}, frontend.builtinTypes);
@ -644,6 +698,7 @@ FragmentTypeCheckResult typecheckFragment_(
{}
};
reportWaypoint(reporter, FragmentAutocompleteWaypoint::CloneAndSquashScopeStart);
if (FFlag::LuauCloneIncrementalModule)
{
incrementalModule->scopes.emplace_back(root->location, freshChildOfNearestScope);
@ -657,6 +712,8 @@ FragmentTypeCheckResult typecheckFragment_(
cloneAndSquashScopes_DEPRECATED(
cloneState, closestScope.get(), stale, NotNull{&incrementalModule->internalTypes}, NotNull{&dfg}, root, freshChildOfNearestScope.get()
);
reportWaypoint(reporter, FragmentAutocompleteWaypoint::CloneAndSquashScopeEnd);
cg.visitFragmentRoot(freshChildOfNearestScope, root);
if (FFlag::LuauPersistConstraintGenerationScopes)
@ -680,6 +737,7 @@ FragmentTypeCheckResult typecheckFragment_(
LUAU_ASSERT(back == freshChildOfNearestScope.get());
closestScope->children.pop_back();
}
reportWaypoint(reporter, FragmentAutocompleteWaypoint::ConstraintSolverStart);
if (FFlag::LuauAllFreeTypesHaveScopes)
{
@ -719,6 +777,8 @@ FragmentTypeCheckResult typecheckFragment_(
stale->cancelled = true;
}
reportWaypoint(reporter, FragmentAutocompleteWaypoint::ConstraintSolverEnd);
// In frontend we would forbid internal types
// because this is just for autocomplete, we don't actually care
// We also don't even need to typecheck - just synthesize types as best as we can
@ -735,17 +795,15 @@ std::pair<FragmentTypeCheckStatus, FragmentTypeCheckResult> typecheckFragment(
const Position& cursorPos,
std::optional<FrontendOptions> opts,
std::string_view src,
std::optional<Position> fragmentEndPosition
std::optional<Position> fragmentEndPosition,
IFragmentAutocompleteReporter* reporter
)
{
LUAU_TIMETRACE_SCOPE("Luau::typecheckFragment", "FragmentAutocomplete");
LUAU_TIMETRACE_ARGUMENT("name", moduleName.c_str());
if (FFlag::LuauBetterReverseDependencyTracking)
{
if (!frontend.allModuleDependenciesValid(moduleName, opts && opts->forAutocomplete))
return {FragmentTypeCheckStatus::SkipAutocomplete, {}};
}
if (!frontend.allModuleDependenciesValid(moduleName, opts && opts->forAutocomplete))
return {FragmentTypeCheckStatus::SkipAutocomplete, {}};
FrontendModuleResolver& resolver = getModuleResolver(frontend, opts);
ModulePtr module = resolver.getModule(moduleName);
@ -778,6 +836,7 @@ std::pair<FragmentTypeCheckStatus, FragmentTypeCheckResult> typecheckFragment(
}
tryParse = parseFragment(sourceModule->root, sourceModule->names.get(), src, cursorPos, fragmentEndPosition);
reportWaypoint(reporter, FragmentAutocompleteWaypoint::ParseFragmentEnd);
}
if (!tryParse)
@ -791,8 +850,9 @@ std::pair<FragmentTypeCheckStatus, FragmentTypeCheckResult> typecheckFragment(
FrontendOptions frontendOptions = opts.value_or(frontend.options);
const ScopePtr& closestScope = findClosestScope(module, parseResult.nearestStatement);
FragmentTypeCheckResult result =
typecheckFragment_(frontend, parseResult.root, module, closestScope, cursorPos, std::move(parseResult.alloc), frontendOptions);
typecheckFragment_(frontend, parseResult.root, module, closestScope, cursorPos, std::move(parseResult.alloc), frontendOptions, reporter);
result.ancestry = std::move(parseResult.ancestry);
reportFragmentString(reporter, tryParse->fragmentToParse);
return {FragmentTypeCheckStatus::Success, result};
}
@ -813,7 +873,14 @@ FragmentAutocompleteStatusResult tryFragmentAutocomplete(
try
{
Luau::FragmentAutocompleteResult fragmentAutocomplete = Luau::fragmentAutocomplete(
frontend, context.newSrc, moduleName, cursorPosition, context.opts, std::move(stringCompletionCB), context.DEPRECATED_fragmentEndPosition
frontend,
context.newSrc,
moduleName,
cursorPosition,
context.opts,
std::move(stringCompletionCB),
context.DEPRECATED_fragmentEndPosition,
FFlag::LuauFragmentAcSupportsReporter ? context.reporter : nullptr
);
return {FragmentAutocompleteStatus::Success, std::move(fragmentAutocomplete)};
}
@ -832,7 +899,8 @@ FragmentAutocompleteResult fragmentAutocomplete(
Position cursorPosition,
std::optional<FrontendOptions> opts,
StringCompletionCallback callback,
std::optional<Position> fragmentEndPosition
std::optional<Position> fragmentEndPosition,
IFragmentAutocompleteReporter* reporter
)
{
LUAU_ASSERT(FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete);
@ -853,10 +921,11 @@ FragmentAutocompleteResult fragmentAutocomplete(
return {};
}
auto [tcStatus, tcResult] = typecheckFragment(frontend, moduleName, cursorPosition, opts, src, fragmentEndPosition);
auto [tcStatus, tcResult] = typecheckFragment(frontend, moduleName, cursorPosition, opts, src, fragmentEndPosition, reporter);
if (tcStatus == FragmentTypeCheckStatus::SkipAutocomplete)
return {};
reportWaypoint(reporter, FragmentAutocompleteWaypoint::TypecheckFragmentEnd);
auto globalScope = (opts && opts->forAutocomplete) ? frontend.globalsForAutocomplete.globalScope.get() : frontend.globals.globalScope.get();
if (FFlag::LogFragmentsFromAutocomplete)
logLuau(src);
@ -873,6 +942,7 @@ FragmentAutocompleteResult fragmentAutocomplete(
callback
);
reportWaypoint(reporter, FragmentAutocompleteWaypoint::AutocompleteEnd);
return {std::move(tcResult.incrementalModule), tcResult.freshScope.get(), std::move(arenaForFragmentAutocomplete), std::move(result)};
}

View file

@ -49,7 +49,6 @@ LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauRunCustomModuleChecks, false)
LUAU_FASTFLAGVARIABLE(LuauModuleHoldsAstRoot)
LUAU_FASTFLAGVARIABLE(LuauBetterReverseDependencyTracking)
LUAU_FASTFLAGVARIABLE(LuauFixMultithreadTypecheck)
LUAU_FASTFLAG(StudioReportLuauAny2)
@ -1035,14 +1034,11 @@ bool Frontend::parseGraph(
buildQueue.push_back(top->name);
if (FFlag::LuauBetterReverseDependencyTracking)
// at this point we know all valid dependencies are processed into SourceNodes
for (const ModuleName& dep : top->requireSet)
{
// at this point we know all valid dependencies are processed into SourceNodes
for (const ModuleName& dep : top->requireSet)
{
if (auto it = sourceNodes.find(dep); it != sourceNodes.end())
it->second->dependents.insert(top->name);
}
if (auto it = sourceNodes.find(dep); it != sourceNodes.end())
it->second->dependents.insert(top->name);
}
}
else
@ -1331,51 +1327,35 @@ void Frontend::recordItemResult(const BuildQueueItem& item)
if (item.exception)
std::rethrow_exception(item.exception);
if (FFlag::LuauBetterReverseDependencyTracking)
bool replacedModule = false;
if (item.options.forAutocomplete)
{
bool replacedModule = false;
if (item.options.forAutocomplete)
{
replacedModule = moduleResolverForAutocomplete.setModule(item.name, item.module);
item.sourceNode->dirtyModuleForAutocomplete = false;
}
else
{
replacedModule = moduleResolver.setModule(item.name, item.module);
item.sourceNode->dirtyModule = false;
}
if (replacedModule)
{
LUAU_TIMETRACE_SCOPE("Frontend::invalidateDependentModules", "Frontend");
LUAU_TIMETRACE_ARGUMENT("name", item.name.c_str());
traverseDependents(
item.name,
[forAutocomplete = item.options.forAutocomplete](SourceNode& sourceNode)
{
bool traverseSubtree = !sourceNode.hasInvalidModuleDependency(forAutocomplete);
sourceNode.setInvalidModuleDependency(true, forAutocomplete);
return traverseSubtree;
}
);
}
item.sourceNode->setInvalidModuleDependency(false, item.options.forAutocomplete);
replacedModule = moduleResolverForAutocomplete.setModule(item.name, item.module);
item.sourceNode->dirtyModuleForAutocomplete = false;
}
else
{
if (item.options.forAutocomplete)
{
moduleResolverForAutocomplete.setModule(item.name, item.module);
item.sourceNode->dirtyModuleForAutocomplete = false;
}
else
{
moduleResolver.setModule(item.name, item.module);
item.sourceNode->dirtyModule = false;
}
replacedModule = moduleResolver.setModule(item.name, item.module);
item.sourceNode->dirtyModule = false;
}
if (replacedModule)
{
LUAU_TIMETRACE_SCOPE("Frontend::invalidateDependentModules", "Frontend");
LUAU_TIMETRACE_ARGUMENT("name", item.name.c_str());
traverseDependents(
item.name,
[forAutocomplete = item.options.forAutocomplete](SourceNode& sourceNode)
{
bool traverseSubtree = !sourceNode.hasInvalidModuleDependency(forAutocomplete);
sourceNode.setInvalidModuleDependency(true, forAutocomplete);
return traverseSubtree;
}
);
}
item.sourceNode->setInvalidModuleDependency(false, item.options.forAutocomplete);
stats.timeCheck += item.stats.timeCheck;
stats.timeLint += item.stats.timeLint;
@ -1464,7 +1444,6 @@ ScopePtr Frontend::getModuleEnvironment(const SourceModule& module, const Config
bool Frontend::allModuleDependenciesValid(const ModuleName& name, bool forAutocomplete) const
{
LUAU_ASSERT(FFlag::LuauBetterReverseDependencyTracking);
auto it = sourceNodes.find(name);
return it != sourceNodes.end() && !it->second->hasInvalidModuleDependency(forAutocomplete);
}
@ -1486,72 +1465,27 @@ void Frontend::markDirty(const ModuleName& name, std::vector<ModuleName>* marked
LUAU_TIMETRACE_SCOPE("Frontend::markDirty", "Frontend");
LUAU_TIMETRACE_ARGUMENT("name", name.c_str());
if (FFlag::LuauBetterReverseDependencyTracking)
{
traverseDependents(
name,
[markedDirty](SourceNode& sourceNode)
{
if (markedDirty)
markedDirty->push_back(sourceNode.name);
if (sourceNode.dirtySourceModule && sourceNode.dirtyModule && sourceNode.dirtyModuleForAutocomplete)
return false;
sourceNode.dirtySourceModule = true;
sourceNode.dirtyModule = true;
sourceNode.dirtyModuleForAutocomplete = true;
return true;
}
);
}
else
{
if (sourceNodes.count(name) == 0)
return;
std::unordered_map<ModuleName, std::vector<ModuleName>> reverseDeps;
for (const auto& module : sourceNodes)
traverseDependents(
name,
[markedDirty](SourceNode& sourceNode)
{
for (const auto& dep : module.second->requireSet)
reverseDeps[dep].push_back(module.first);
}
std::vector<ModuleName> queue{name};
while (!queue.empty())
{
ModuleName next = std::move(queue.back());
queue.pop_back();
LUAU_ASSERT(sourceNodes.count(next) > 0);
SourceNode& sourceNode = *sourceNodes[next];
if (markedDirty)
markedDirty->push_back(next);
markedDirty->push_back(sourceNode.name);
if (sourceNode.dirtySourceModule && sourceNode.dirtyModule && sourceNode.dirtyModuleForAutocomplete)
continue;
return false;
sourceNode.dirtySourceModule = true;
sourceNode.dirtyModule = true;
sourceNode.dirtyModuleForAutocomplete = true;
if (0 == reverseDeps.count(next))
continue;
sourceModules.erase(next);
const std::vector<ModuleName>& dependents = reverseDeps[next];
queue.insert(queue.end(), dependents.begin(), dependents.end());
return true;
}
}
);
}
void Frontend::traverseDependents(const ModuleName& name, std::function<bool(SourceNode&)> processSubtree)
{
LUAU_ASSERT(FFlag::LuauBetterReverseDependencyTracking);
LUAU_TIMETRACE_SCOPE("Frontend::traverseDependents", "Frontend");
if (sourceNodes.count(name) == 0)
@ -2012,14 +1946,11 @@ std::pair<SourceNode*, SourceModule*> Frontend::getSourceNode(const ModuleName&
sourceNode->name = sourceModule->name;
sourceNode->humanReadableName = sourceModule->humanReadableName;
if (FFlag::LuauBetterReverseDependencyTracking)
// clear all prior dependents. we will re-add them after parsing the rest of the graph
for (const auto& [moduleName, _] : sourceNode->requireLocations)
{
// clear all prior dependents. we will re-add them after parsing the rest of the graph
for (const auto& [moduleName, _] : sourceNode->requireLocations)
{
if (auto depIt = sourceNodes.find(moduleName); depIt != sourceNodes.end())
depIt->second->dependents.erase(sourceNode->name);
}
if (auto depIt = sourceNodes.find(moduleName); depIt != sourceNodes.end())
depIt->second->dependents.erase(sourceNode->name);
}
sourceNode->requireSet.clear();
@ -2147,17 +2078,9 @@ bool FrontendModuleResolver::setModule(const ModuleName& moduleName, ModulePtr m
{
std::scoped_lock lock(moduleMutex);
if (FFlag::LuauBetterReverseDependencyTracking)
{
bool replaced = modules.count(moduleName) > 0;
modules[moduleName] = std::move(module);
return replaced;
}
else
{
modules[moduleName] = std::move(module);
return false;
}
bool replaced = modules.count(moduleName) > 0;
modules[moduleName] = std::move(module);
return replaced;
}
void FrontendModuleResolver::clearModules()

View file

@ -107,134 +107,4 @@ void quantify(TypeId ty, TypeLevel level)
ftv->genericPacks.insert(ftv->genericPacks.end(), q.genericPacks.begin(), q.genericPacks.end());
}
struct PureQuantifier : Substitution
{
Scope* scope;
OrderedMap<TypeId, TypeId> insertedGenerics;
OrderedMap<TypePackId, TypePackId> insertedGenericPacks;
bool seenMutableType = false;
bool seenGenericType = false;
PureQuantifier(TypeArena* arena, Scope* scope)
: Substitution(TxnLog::empty(), arena)
, scope(scope)
{
}
bool isDirty(TypeId ty) override
{
LUAU_ASSERT(ty == follow(ty));
if (auto ftv = get<FreeType>(ty))
{
bool result = subsumes(scope, ftv->scope);
seenMutableType |= result;
return result;
}
else if (auto ttv = get<TableType>(ty))
{
if (ttv->state == TableState::Free)
seenMutableType = true;
else if (ttv->state == TableState::Generic)
seenGenericType = true;
return (ttv->state == TableState::Unsealed || ttv->state == TableState::Free) && subsumes(scope, ttv->scope);
}
return false;
}
bool isDirty(TypePackId tp) override
{
if (auto ftp = get<FreeTypePack>(tp))
{
return subsumes(scope, ftp->scope);
}
return false;
}
TypeId clean(TypeId ty) override
{
if (auto ftv = get<FreeType>(ty))
{
TypeId result = arena->addType(GenericType{scope});
insertedGenerics.push(ty, result);
return result;
}
else if (auto ttv = get<TableType>(ty))
{
TypeId result = arena->addType(TableType{});
TableType* resultTable = getMutable<TableType>(result);
LUAU_ASSERT(resultTable);
*resultTable = *ttv;
resultTable->level = TypeLevel{};
resultTable->scope = scope;
if (ttv->state == TableState::Free)
{
resultTable->state = TableState::Generic;
insertedGenerics.push(ty, result);
}
else if (ttv->state == TableState::Unsealed)
resultTable->state = TableState::Sealed;
return result;
}
return ty;
}
TypePackId clean(TypePackId tp) override
{
if (auto ftp = get<FreeTypePack>(tp))
{
TypePackId result = arena->addTypePack(TypePackVar{GenericTypePack{scope}});
insertedGenericPacks.push(tp, result);
return result;
}
return tp;
}
bool ignoreChildren(TypeId ty) override
{
if (get<ClassType>(ty))
return true;
return ty->persistent;
}
bool ignoreChildren(TypePackId ty) override
{
return ty->persistent;
}
};
std::optional<QuantifierResult> quantify(TypeArena* arena, TypeId ty, Scope* scope)
{
PureQuantifier quantifier{arena, scope};
std::optional<TypeId> result = quantifier.substitute(ty);
if (!result)
return std::nullopt;
FunctionType* ftv = getMutable<FunctionType>(*result);
LUAU_ASSERT(ftv);
ftv->scope = scope;
for (auto k : quantifier.insertedGenerics.keys)
{
TypeId g = quantifier.insertedGenerics.pairings[k];
if (get<GenericType>(g))
ftv->generics.push_back(g);
}
for (auto k : quantifier.insertedGenericPacks.keys)
ftv->genericPacks.push_back(quantifier.insertedGenericPacks.pairings[k]);
ftv->hasNoFreeOrGenericTypes = ftv->generics.empty() && ftv->genericPacks.empty() && !quantifier.seenGenericType && !quantifier.seenMutableType;
return std::optional<QuantifierResult>({*result, std::move(quantifier.insertedGenerics), std::move(quantifier.insertedGenericPacks)});
}
} // namespace Luau

View file

@ -12,7 +12,7 @@
LUAU_FASTFLAG(LuauStoreCSTData)
LUAU_FASTFLAG(LuauExtendStatEndPosWithSemicolon)
LUAU_FASTFLAG(LuauAstTypeGroup2)
LUAU_FASTFLAG(LuauAstTypeGroup3)
LUAU_FASTFLAG(LuauFixDoBlockEndLocation)
namespace
@ -330,7 +330,7 @@ struct Printer_DEPRECATED
else if (typeCount == 1)
{
bool shouldParenthesize = unconditionallyParenthesize && (list.types.size == 0 || !list.types.data[0]->is<AstTypeGroup>());
if (FFlag::LuauAstTypeGroup2 ? shouldParenthesize : unconditionallyParenthesize)
if (FFlag::LuauAstTypeGroup3 ? shouldParenthesize : unconditionallyParenthesize)
writer.symbol("(");
// Only variadic tail
@ -343,7 +343,7 @@ struct Printer_DEPRECATED
visualizeTypeAnnotation(*list.types.data[0]);
}
if (FFlag::LuauAstTypeGroup2 ? shouldParenthesize : unconditionallyParenthesize)
if (FFlag::LuauAstTypeGroup3 ? shouldParenthesize : unconditionallyParenthesize)
writer.symbol(")");
}
else
@ -1349,7 +1349,7 @@ struct Printer
else if (typeCount == 1)
{
bool shouldParenthesize = unconditionallyParenthesize && (list.types.size == 0 || !list.types.data[0]->is<AstTypeGroup>());
if (FFlag::LuauAstTypeGroup2 ? shouldParenthesize : unconditionallyParenthesize)
if (FFlag::LuauAstTypeGroup3 ? shouldParenthesize : unconditionallyParenthesize)
writer.symbol("(");
// Only variadic tail
@ -1362,7 +1362,7 @@ struct Printer
visualizeTypeAnnotation(*list.types.data[0]);
}
if (FFlag::LuauAstTypeGroup2 ? shouldParenthesize : unconditionallyParenthesize)
if (FFlag::LuauAstTypeGroup3 ? shouldParenthesize : unconditionallyParenthesize)
writer.symbol(")");
}
else

View file

@ -53,6 +53,8 @@ LUAU_FASTFLAGVARIABLE(LuauDoNotGeneralizeInTypeFunctions)
LUAU_FASTFLAGVARIABLE(LuauPreventReentrantTypeFunctionReduction)
LUAU_FASTFLAGVARIABLE(LuauIntersectNotNil)
LUAU_FASTFLAGVARIABLE(LuauSkipNoRefineDuringRefinement)
LUAU_FASTFLAGVARIABLE(LuauDontForgetToReduceUnionFunc)
LUAU_FASTFLAGVARIABLE(LuauSearchForRefineableType)
namespace Luau
{
@ -630,6 +632,9 @@ static std::optional<TypeFunctionReductionResult<TypeId>> tryDistributeTypeFunct
{},
});
if (FFlag::LuauDontForgetToReduceUnionFunc && ctx->solver)
ctx->pushConstraint(ReduceConstraint{resultTy});
return {{resultTy, Reduction::MaybeOk, {}, {}}};
}
@ -1934,6 +1939,33 @@ struct FindRefinementBlockers : TypeOnceVisitor
}
};
struct ContainsRefinableType : TypeOnceVisitor
{
bool found = false;
ContainsRefinableType() : TypeOnceVisitor(/* skipBoundTypes */ true) {}
bool visit(TypeId ty) override {
// Default case: if we find *some* type that's worth refining against,
// then we can claim that this type contains a refineable type.
found = true;
return false;
}
bool visit(TypeId Ty, const NoRefineType&) override {
// No refine types aren't interesting
return false;
}
bool visit(TypeId ty, const TableType&) override { return !found; }
bool visit(TypeId ty, const MetatableType&) override { return !found; }
bool visit(TypeId ty, const FunctionType&) override { return !found; }
bool visit(TypeId ty, const UnionType&) override { return !found; }
bool visit(TypeId ty, const IntersectionType&) override { return !found; }
bool visit(TypeId ty, const NegationType&) override { return !found; }
};
TypeFunctionReductionResult<TypeId> refineTypeFunction(
TypeId instance,
const std::vector<TypeId>& typeParams,
@ -2007,14 +2039,28 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
}
else
{
if (FFlag::LuauSkipNoRefineDuringRefinement)
if (get<NoRefineType>(discriminant))
return {target, {}};
if (auto nt = get<NegationType>(discriminant))
if (FFlag::LuauSearchForRefineableType)
{
if (get<NoRefineType>(follow(nt->ty)))
// If the discriminant type is only:
// - The `*no-refine*` type or,
// - tables, metatables, unions, intersections, functions, or negations _containing_ `*no-refine*`.
// There's no point in refining against it.
ContainsRefinableType crt;
crt.traverse(discriminant);
if (!crt.found)
return {target, {}};
}
else
{
if (FFlag::LuauSkipNoRefineDuringRefinement)
if (get<NoRefineType>(discriminant))
return {target, {}};
if (auto nt = get<NegationType>(discriminant))
{
if (get<NoRefineType>(follow(nt->ty)))
return {target, {}};
}
}
// If the target type is a table, then simplification already implements the logic to deal with refinements properly since the
// type of the discriminant is guaranteed to only ever be an (arbitrarily-nested) table of a single property type.

View file

@ -19,6 +19,7 @@
LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAGVARIABLE(LuauUnifyMetatableWithAny)
LUAU_FASTFLAG(LuauExtraFollows)
namespace Luau
{
@ -282,7 +283,7 @@ bool Unifier2::unifyFreeWithType(TypeId subTy, TypeId superTy)
if (superArgTail)
return doDefault();
const IntersectionType* upperBoundIntersection = get<IntersectionType>(subFree->upperBound);
const IntersectionType* upperBoundIntersection = get<IntersectionType>(FFlag::LuauExtraFollows ? upperBound : subFree->upperBound);
if (!upperBoundIntersection)
return doDefault();

View file

@ -20,12 +20,11 @@ LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
LUAU_FASTFLAGVARIABLE(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauAllowComplexTypesInGenericParams)
LUAU_FASTFLAGVARIABLE(LuauErrorRecoveryForTableTypes)
LUAU_FASTFLAGVARIABLE(LuauErrorRecoveryForClassNames)
LUAU_FASTFLAGVARIABLE(LuauFixFunctionNameStartPosition)
LUAU_FASTFLAGVARIABLE(LuauExtendStatEndPosWithSemicolon)
LUAU_FASTFLAGVARIABLE(LuauStoreCSTData)
LUAU_FASTFLAGVARIABLE(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
LUAU_FASTFLAGVARIABLE(LuauAstTypeGroup2)
LUAU_FASTFLAGVARIABLE(LuauAstTypeGroup3)
LUAU_FASTFLAGVARIABLE(ParserNoErrorLimit)
LUAU_FASTFLAGVARIABLE(LuauFixDoBlockEndLocation)
@ -1307,30 +1306,17 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
}
else
{
if (FFlag::LuauErrorRecoveryForClassNames)
{
Location propStart = lexer.current().location;
std::optional<Name> propName = parseNameOpt("property name");
Location propStart = lexer.current().location;
std::optional<Name> propName = parseNameOpt("property name");
if (!propName)
break;
if (!propName)
break;
expectAndConsume(':', "property type annotation");
AstType* propType = parseType();
props.push_back(
AstDeclaredClassProp{propName->name, propName->location, propType, false, Location(propStart, lexer.previousLocation())}
);
}
else
{
Location propStart = lexer.current().location;
Name propName = parseName("property name");
expectAndConsume(':', "property type annotation");
AstType* propType = parseType();
props.push_back(
AstDeclaredClassProp{propName.name, propName.location, propType, false, Location(propStart, lexer.previousLocation())}
);
}
expectAndConsume(':', "property type annotation");
AstType* propType = parseType();
props.push_back(
AstDeclaredClassProp{propName->name, propName->location, propType, false, Location(propStart, lexer.previousLocation())}
);
}
}
@ -1732,11 +1718,13 @@ std::pair<Location, AstTypeList> Parser::parseReturnType()
if (lexer.current().type != Lexeme::SkinnyArrow && resultNames.empty())
{
// If it turns out that it's just '(A)', it's possible that there are unions/intersections to follow, so fold over it.
if (FFlag::LuauAstTypeGroup2)
if (FFlag::LuauAstTypeGroup3)
{
if (result.size() == 1 && varargAnnotation == nullptr)
if (result.size() == 1)
{
AstType* returnType = parseTypeSuffix(allocator.alloc<AstTypeGroup>(location, result[0]), begin.location);
// TODO(CLI-140667): stop parsing type suffix when varargAnnotation != nullptr - this should be a parse error
AstType* inner = varargAnnotation == nullptr ? allocator.alloc<AstTypeGroup>(location, result[0]) : result[0];
AstType* returnType = parseTypeSuffix(inner, begin.location);
// If parseType parses nothing, then returnType->location.end only points at the last non-type-pack
// type to successfully parse. We need the span of the whole annotation.
@ -2062,7 +2050,7 @@ AstTypeOrPack Parser::parseFunctionType(bool allowPack, const AstArray<AstAttr*>
return {{}, allocator.alloc<AstTypePackExplicit>(begin.location, AstTypeList{paramTypes, nullptr})};
else
{
if (FFlag::LuauAstTypeGroup2)
if (FFlag::LuauAstTypeGroup3)
return {allocator.alloc<AstTypeGroup>(Location(parameterStart.location, closeArgsLocation), params[0]), {}};
else
return {params[0], {}};
@ -3572,7 +3560,7 @@ AstArray<AstTypeOrPack> Parser::parseTypeParams(Position* openingPosition, TempV
// the next lexeme is one that follows a type
// (&, |, ?), then assume that this was actually a
// parenthesized type.
if (FFlag::LuauAstTypeGroup2)
if (FFlag::LuauAstTypeGroup3)
{
auto parenthesizedType = explicitTypePack->typeList.types.data[0];
parameters.push_back(

View file

@ -26,6 +26,8 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineThreshold, 25)
LUAU_FASTINTVARIABLE(LuauCompileInlineThresholdMaxBoost, 300)
LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5)
LUAU_FASTFLAGVARIABLE(LuauSeparateCompilerTypeInfo)
namespace Luau
{
@ -4269,20 +4271,40 @@ void compileOrThrow(BytecodeBuilder& bytecode, const ParseResult& parseResult, c
}
// computes type information for all functions based on type annotations
if (options.typeInfoLevel >= 1)
buildTypeMap(
compiler.functionTypes,
compiler.localTypes,
compiler.exprTypes,
root,
options.vectorType,
compiler.userdataTypes,
compiler.builtinTypes,
compiler.builtins,
compiler.globals,
options.libraryMemberTypeCb,
bytecode
);
if (FFlag::LuauSeparateCompilerTypeInfo)
{
if (options.typeInfoLevel >= 1 || options.optimizationLevel >= 2)
buildTypeMap(
compiler.functionTypes,
compiler.localTypes,
compiler.exprTypes,
root,
options.vectorType,
compiler.userdataTypes,
compiler.builtinTypes,
compiler.builtins,
compiler.globals,
options.libraryMemberTypeCb,
bytecode
);
}
else
{
if (options.typeInfoLevel >= 1)
buildTypeMap(
compiler.functionTypes,
compiler.localTypes,
compiler.exprTypes,
root,
options.vectorType,
compiler.userdataTypes,
compiler.builtinTypes,
compiler.builtins,
compiler.globals,
options.libraryMemberTypeCb,
bytecode
);
}
for (AstExprFunction* expr : functions)
{

View file

@ -266,6 +266,7 @@ target_sources(Luau.Analysis PRIVATE
Analysis/src/EmbeddedBuiltinDefinitions.cpp
Analysis/src/Error.cpp
Analysis/src/EqSatSimplification.cpp
Analysis/src/FileResolver.cpp
Analysis/src/FragmentAutocomplete.cpp
Analysis/src/Frontend.cpp
Analysis/src/Generalization.cpp

View file

@ -327,9 +327,12 @@ LUA_API void lua_setuserdatadtor(lua_State* L, int tag, lua_Destructor dtor);
LUA_API lua_Destructor lua_getuserdatadtor(lua_State* L, int tag);
// alternative access for metatables already registered with luaL_newmetatable
LUA_API void lua_setuserdatametatable(lua_State* L, int tag, int idx);
// used by lua_newuserdatataggedwithmetatable to create tagged userdata with the associated metatable assigned
LUA_API void lua_setuserdatametatable(lua_State* L, int tag);
LUA_API void lua_getuserdatametatable(lua_State* L, int tag);
LUA_API void lua_setuserdatametatable_DEPRECATED(lua_State* L, int tag, int idx); // Deprecated for incorrect behavior with 'idx != -1'
LUA_API void lua_setlightuserdataname(lua_State* L, int tag, const char* name);
LUA_API const char* lua_getlightuserdataname(lua_State* L, int tag);

View file

@ -1470,7 +1470,16 @@ lua_Destructor lua_getuserdatadtor(lua_State* L, int tag)
return L->global->udatagc[tag];
}
void lua_setuserdatametatable(lua_State* L, int tag, int idx)
void lua_setuserdatametatable(lua_State* L, int tag)
{
api_check(L, unsigned(tag) < LUA_UTAG_LIMIT);
api_check(L, !L->global->udatamt[tag]); // reassignment not supported
api_check(L, ttistable(L->top - 1));
L->global->udatamt[tag] = hvalue(L->top - 1);
L->top--;
}
void lua_setuserdatametatable_DEPRECATED(lua_State* L, int tag, int idx)
{
api_check(L, unsigned(tag) < LUA_UTAG_LIMIT);
api_check(L, !L->global->udatamt[tag]); // reassignment not supported

View file

@ -20,7 +20,7 @@ LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAG(StudioReportLuauAny2)
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
LUAU_FASTFLAG(LuauStoreCSTData)
LUAU_FASTFLAG(LuauAstTypeGroup2)
LUAU_FASTFLAG(LuauAstTypeGroup3)
LUAU_FASTFLAG(LuauDeferBidirectionalInferenceForTableAssignment)
LUAU_FASTFLAG(LuauSkipNoRefineDuringRefinement)
@ -76,7 +76,7 @@ export type t8<t8> = t0 &(<t0 ...>(true | any)->(''))
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias);
if (FFlag::LuauStoreCSTData && FFlag::LuauAstTypeGroup2)
if (FFlag::LuauStoreCSTData && FFlag::LuauAstTypeGroup3)
{
LUAU_ASSERT(module->ats.typeInfo[0].node == "export type t8<t8> = t0& (<t0...>( true | any)->(''))");
}
@ -84,7 +84,7 @@ export type t8<t8> = t0 &(<t0 ...>(true | any)->(''))
{
LUAU_ASSERT(module->ats.typeInfo[0].node == "export type t8<t8> = t0 &(<t0...>( true | any)->(''))");
}
else if (FFlag::LuauAstTypeGroup2)
else if (FFlag::LuauAstTypeGroup3)
{
LUAU_ASSERT(module->ats.typeInfo[0].node == "export type t8<t8> = t0& (<t0 ...>(true | any)->(''))");
}

View file

@ -11,7 +11,7 @@
using namespace Luau;
LUAU_FASTFLAG(LuauAstTypeGroup2)
LUAU_FASTFLAG(LuauAstTypeGroup3)
struct JsonEncoderFixture
{
@ -473,7 +473,7 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_annotation")
{
AstStat* statement = expectParseStatement("type T = ((number) -> (string | nil)) & ((string) -> ())");
if (FFlag::LuauAstTypeGroup2)
if (FFlag::LuauAstTypeGroup3)
{
std::string_view expected =
R"({"type":"AstStatTypeAlias","location":"0,0 - 0,56","name":"T","generics":[],"genericPacks":[],"value":{"type":"AstTypeIntersection","location":"0,9 - 0,56","types":[{"type":"AstTypeGroup","location":"0,9 - 0,37","inner":{"type":"AstTypeFunction","location":"0,10 - 0,36","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,11 - 0,17","name":"number","nameLocation":"0,11 - 0,17","parameters":[]}]},"argNames":[],"returnTypes":{"type":"AstTypeList","types":[{"type":"AstTypeGroup","location":"0,22 - 0,36","inner":{"type":"AstTypeUnion","location":"0,23 - 0,35","types":[{"type":"AstTypeReference","location":"0,23 - 0,29","name":"string","nameLocation":"0,23 - 0,29","parameters":[]},{"type":"AstTypeReference","location":"0,32 - 0,35","name":"nil","nameLocation":"0,32 - 0,35","parameters":[]}]}}]}}},{"type":"AstTypeGroup","location":"0,40 - 0,56","inner":{"type":"AstTypeFunction","location":"0,41 - 0,55","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,42 - 0,48","name":"string","nameLocation":"0,42 - 0,48","parameters":[]}]},"argNames":[],"returnTypes":{"type":"AstTypeList","types":[]}}}]},"exported":false})";

View file

@ -1,5 +1,6 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Autocomplete.h"
#include "Luau/AutocompleteTypes.h"
#include "Luau/BuiltinDefinitions.h"
#include "Luau/TypeInfer.h"
#include "Luau/Type.h"
@ -17,6 +18,8 @@ LUAU_FASTFLAG(LuauTraceTypesInNonstrictMode2)
LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel)
LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauExposeRequireByStringAutocomplete)
using namespace Luau;
static std::optional<AutocompleteEntryMap> nullCallback(std::string tag, std::optional<const ClassType*> ptr, std::optional<std::string> contents)
@ -3752,6 +3755,73 @@ TEST_CASE_FIXTURE(ACFixture, "string_contents_is_available_to_callback")
CHECK(isCorrect);
}
TEST_CASE_FIXTURE(ACBuiltinsFixture, "require_by_string")
{
ScopedFastFlag sff{FFlag::LuauExposeRequireByStringAutocomplete, true};
fileResolver.source["MainModule"] = R"(
local info = "MainModule serves as the root directory"
)";
fileResolver.source["MainModule/Folder"] = R"(
local info = "MainModule/Folder serves as a subdirectory"
)";
fileResolver.source["MainModule/Folder/Requirer"] = R"(
local res0 = require("@")
local res1 = require(".")
local res2 = require("./")
local res3 = require("./Sib")
local res4 = require("..")
local res5 = require("../")
local res6 = require("../Sib")
)";
fileResolver.source["MainModule/Folder/SiblingDependency"] = R"(
return {"result"}
)";
fileResolver.source["MainModule/ParentDependency"] = R"(
return {"result"}
)";
struct RequireCompletion
{
std::string label;
std::string insertText;
};
auto checkEntries = [](const AutocompleteEntryMap& entryMap, const std::vector<RequireCompletion>& completions)
{
CHECK(completions.size() == entryMap.size());
for (const auto& completion : completions)
{
CHECK(entryMap.count(completion.label));
CHECK(entryMap.at(completion.label).insertText == completion.insertText);
}
};
AutocompleteResult acResult;
acResult = autocomplete("MainModule/Folder/Requirer", Position{1, 31});
checkEntries(acResult.entryMap, {{"@defaultalias", "@defaultalias"}, {"./", "./"}, {"../", "../"}});
acResult = autocomplete("MainModule/Folder/Requirer", Position{3, 31});
checkEntries(acResult.entryMap, {{"@defaultalias", "@defaultalias"}, {"./", "./"}, {"../", "../"}});
acResult = autocomplete("MainModule/Folder/Requirer", Position{4, 32});
checkEntries(acResult.entryMap, {{"..", "."}, {"Requirer", "./Requirer"}, {"SiblingDependency", "./SiblingDependency"}});
acResult = autocomplete("MainModule/Folder/Requirer", Position{5, 35});
checkEntries(acResult.entryMap, {{"..", "."}, {"Requirer", "./Requirer"}, {"SiblingDependency", "./SiblingDependency"}});
acResult = autocomplete("MainModule/Folder/Requirer", Position{7, 32});
checkEntries(acResult.entryMap, {{"@defaultalias", "@defaultalias"}, {"./", "./"}, {"../", "../"}});
acResult = autocomplete("MainModule/Folder/Requirer", Position{8, 33});
checkEntries(acResult.entryMap, {{"..", "../.."}, {"Folder", "../Folder"}, {"ParentDependency", "../ParentDependency"}});
acResult = autocomplete("MainModule/Folder/Requirer", Position{9, 36});
checkEntries(acResult.entryMap, {{"..", "../.."}, {"Folder", "../Folder"}, {"ParentDependency", "../ParentDependency"}});
}
TEST_CASE_FIXTURE(ACFixture, "autocomplete_response_perf1" * doctest::timeout(0.5))
{
if (FFlag::LuauSolverV2)

View file

@ -477,9 +477,8 @@ void setupUserdataHelpers(lua_State* L)
{
// create metatable with all the metamethods
luaL_newmetatable(L, "vec2");
luaL_getmetatable(L, "vec2");
lua_pushvalue(L, -1);
lua_setuserdatametatable(L, kTagVec2, -1);
lua_setuserdatametatable(L, kTagVec2);
lua_pushcfunction(L, lua_vec2_index, nullptr);
lua_setfield(L, -2, "__index");
@ -2255,12 +2254,12 @@ TEST_CASE("UserdataApi")
// tagged user data with fast metatable access
luaL_newmetatable(L, "udata3");
luaL_getmetatable(L, "udata3");
lua_setuserdatametatable(L, 50, -1);
lua_pushvalue(L, -1);
lua_setuserdatametatable(L, 50);
luaL_newmetatable(L, "udata4");
luaL_getmetatable(L, "udata4");
lua_setuserdatametatable(L, 51, -1);
lua_pushvalue(L, -1);
lua_setuserdatametatable(L, 51);
void* ud7 = lua_newuserdatatagged(L, 16, 50);
lua_getuserdatametatable(L, 50);

View file

@ -5,6 +5,7 @@
#include "Luau/BuiltinDefinitions.h"
#include "Luau/Common.h"
#include "Luau/Constraint.h"
#include "Luau/FileResolver.h"
#include "Luau/ModuleResolver.h"
#include "Luau/NotNull.h"
#include "Luau/Parser.h"
@ -34,6 +35,110 @@ extern std::optional<unsigned> randomSeed; // tests/main.cpp
namespace Luau
{
static std::string getNodeName(const TestRequireNode* node)
{
std::string name;
size_t lastSlash = node->moduleName.find_last_of('/');
if (lastSlash != std::string::npos)
name = node->moduleName.substr(lastSlash + 1);
else
name = node->moduleName;
return name;
}
std::string TestRequireNode::getLabel() const
{
return getNodeName(this);
}
std::string TestRequireNode::getPathComponent() const
{
return getNodeName(this);
}
static std::vector<std::string_view> splitStringBySlashes(std::string_view str)
{
std::vector<std::string_view> components;
size_t pos = 0;
size_t nextPos = str.find_first_of('/', pos);
if (nextPos == std::string::npos)
{
components.push_back(str);
return components;
}
while (nextPos != std::string::npos)
{
components.push_back(str.substr(pos, nextPos - pos));
pos = nextPos + 1;
nextPos = str.find_first_of('/', pos);
}
components.push_back(str.substr(pos));
return components;
}
std::unique_ptr<RequireNode> TestRequireNode::resolvePathToNode(const std::string& path) const
{
std::vector<std::string_view> components = splitStringBySlashes(path);
LUAU_ASSERT((components.empty() || components[0] == "." || components[0] == "..") && "Path must begin with ./ or ../ in test");
std::vector<std::string_view> normalizedComponents = splitStringBySlashes(moduleName);
normalizedComponents.pop_back();
LUAU_ASSERT(!normalizedComponents.empty() && "Must have a root module");
for (std::string_view component : components)
{
if (component == "..")
{
if (normalizedComponents.empty())
LUAU_ASSERT(!"Cannot go above root module in test");
else
normalizedComponents.pop_back();
}
else if (!component.empty() && component != ".")
{
normalizedComponents.emplace_back(component);
}
}
std::string moduleName;
for (size_t i = 0; i < normalizedComponents.size(); i++)
{
if (i > 0)
moduleName += '/';
moduleName += normalizedComponents[i];
}
if (allSources->count(moduleName) == 0)
return nullptr;
return std::make_unique<TestRequireNode>(moduleName, allSources);
}
std::vector<std::unique_ptr<RequireNode>> TestRequireNode::getChildren() const
{
std::vector<std::unique_ptr<RequireNode>> result;
for (const auto& entry : *allSources)
{
if (std::string_view(entry.first).substr(0, moduleName.size()) == moduleName && entry.first.size() > moduleName.size() &&
entry.first[moduleName.size()] == '/' && entry.first.find('/', moduleName.size() + 1) == std::string::npos)
{
result.push_back(std::make_unique<TestRequireNode>(entry.first, allSources));
}
}
return result;
}
std::vector<RequireAlias> TestRequireNode::getAvailableAliases() const
{
return {{"defaultalias"}};
}
std::unique_ptr<RequireNode> TestRequireSuggester::getNode(const ModuleName& name) const
{
return std::make_unique<TestRequireNode>(name, &resolver->source);
}
std::optional<ModuleInfo> TestFileResolver::resolveModuleInfo(const ModuleName& currentModuleName, const AstExpr& pathExpr)
{
if (auto name = pathExprToModuleName(currentModuleName, pathExpr))

View file

@ -37,10 +37,45 @@ namespace Luau
struct TypeChecker;
struct TestRequireNode : RequireNode
{
TestRequireNode(ModuleName moduleName, std::unordered_map<ModuleName, std::string>* allSources)
: moduleName(std::move(moduleName))
, allSources(allSources)
{
}
std::string getLabel() const override;
std::string getPathComponent() const override;
std::unique_ptr<RequireNode> resolvePathToNode(const std::string& path) const override;
std::vector<std::unique_ptr<RequireNode>> getChildren() const override;
std::vector<RequireAlias> getAvailableAliases() const override;
ModuleName moduleName;
std::unordered_map<ModuleName, std::string>* allSources;
};
struct TestFileResolver;
struct TestRequireSuggester : RequireSuggester
{
TestRequireSuggester(TestFileResolver* resolver)
: resolver(resolver)
{
}
std::unique_ptr<RequireNode> getNode(const ModuleName& name) const override;
TestFileResolver* resolver;
};
struct TestFileResolver
: FileResolver
, ModuleResolver
{
TestFileResolver()
: FileResolver(std::make_shared<TestRequireSuggester>(this))
{
}
std::optional<ModuleInfo> resolveModuleInfo(const ModuleName& currentModuleName, const AstExpr& pathExpr) override;
const ModulePtr getModule(const ModuleName& moduleName) const override;

View file

@ -9,6 +9,7 @@
#include "Luau/Common.h"
#include "Luau/Frontend.h"
#include "Luau/AutocompleteTypes.h"
#include "Luau/ToString.h"
#include "Luau/Type.h"
#include "ScopedFlags.h"
@ -36,6 +37,12 @@ LUAU_FASTFLAG(LuauAutocompleteUsesModuleForTypeCompatibility)
LUAU_FASTFLAG(LuauBetterCursorInCommentDetection)
LUAU_FASTFLAG(LuauAllFreeTypesHaveScopes)
LUAU_FASTFLAG(LuauModuleHoldsAstRoot)
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
LUAU_FASTFLAG(LuauClonedTableAndFunctionTypesMustHaveScopes)
LUAU_FASTFLAG(LuauDisableNewSolverAssertsInMixedMode)
LUAU_FASTFLAG(LuauCloneTypeAliasBindings)
LUAU_FASTFLAG(LuauDoNotClonePersistentBindings)
LUAU_FASTFLAG(LuauCloneReturnTypePack)
static std::optional<AutocompleteEntryMap> nullCallback(std::string tag, std::optional<const ClassType*> ptr, std::optional<std::string> contents)
{
@ -65,16 +72,17 @@ struct FragmentAutocompleteFixtureImpl : BaseType
{
static_assert(std::is_base_of_v<Fixture, BaseType>, "BaseType must be a descendant of Fixture");
ScopedFastFlag sffs[7] = {
{FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete, true},
{FFlag::LuauIncrementalAutocompleteBugfixes, true},
{FFlag::LuauBetterReverseDependencyTracking, true},
{FFlag::LuauFreeTypesMustHaveBounds, true},
{FFlag::LuauCloneIncrementalModule, true},
{FFlag::LuauAllFreeTypesHaveScopes, true},
{FFlag::LuauModuleHoldsAstRoot, true}
};
ScopedFastFlag luauAutocompleteRefactorsForIncrementalAutocomplete{FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete, true};
ScopedFastFlag luauIncrementalAutocompleteBugfixes{FFlag::LuauIncrementalAutocompleteBugfixes, true};
ScopedFastFlag luauFreeTypesMustHaveBounds{FFlag::LuauFreeTypesMustHaveBounds, true};
ScopedFastFlag luauCloneIncrementalModule{FFlag::LuauCloneIncrementalModule, true};
ScopedFastFlag luauAllFreeTypesHaveScopes{FFlag::LuauAllFreeTypesHaveScopes, true};
ScopedFastFlag luauModuleHoldsAstRoot{FFlag::LuauModuleHoldsAstRoot, true};
ScopedFastFlag luauClonedTableAndFunctionTypesMustHaveScopes{FFlag::LuauClonedTableAndFunctionTypesMustHaveScopes, true};
ScopedFastFlag luauDisableNewSolverAssertsInMixedMode{FFlag::LuauDisableNewSolverAssertsInMixedMode, true};
ScopedFastFlag luauCloneTypeAliasBindings{FFlag::LuauCloneTypeAliasBindings, true};
ScopedFastFlag luauDoNotClonePersistentBindings{FFlag::LuauDoNotClonePersistentBindings, true};
ScopedFastFlag luauCloneReturnTypePack{FFlag::LuauCloneReturnTypePack, true};
FragmentAutocompleteFixtureImpl()
: BaseType(true)
@ -2060,6 +2068,59 @@ return m
autocompleteFragmentInBothSolvers(source, updated, Position{6, 2}, [](FragmentAutocompleteStatusResult& _) {});
}
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "duped_alias")
{
const std::string source = R"(
type a = typeof({})
)";
const std::string dest = R"(
type a = typeof({})
l
)";
// Re-parsing and typechecking a type alias in the fragment that was defined in the base module will assert in ConstraintGenerator::checkAliases
// unless we don't clone it This will let the incremental pass re-generate the type binding, and we will expect to see it in the type bindings
autocompleteFragmentInBothSolvers(
source,
dest,
Position{2, 2},
[](FragmentAutocompleteStatusResult& frag)
{
REQUIRE(frag.result);
Scope* sc = frag.result->freshScope;
CHECK(1 == sc->privateTypeBindings.count("a"));
}
);
}
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "mutually_recursive_alias")
{
const std::string source = R"(
type U = {f : number, g : U}
)";
const std::string dest = R"(
type U = {f : number, g : V}
type V = {h : number, i : U?}
)";
// Re-parsing and typechecking a type alias in the fragment that was defined in the base module will assert in ConstraintGenerator::checkAliases
// unless we don't clone it This will let the incremental pass re-generate the type binding, and we will expect to see it in the type bindings
autocompleteFragmentInBothSolvers(
source,
dest,
Position{2, 30},
[](FragmentAutocompleteStatusResult& frag)
{
REQUIRE(frag.result->freshScope);
Scope* scope = frag.result->freshScope;
CHECK(1 == scope->privateTypeBindings.count("U"));
CHECK(1 == scope->privateTypeBindings.count("V"));
}
);
}
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "generalization_crash_when_old_solver_freetypes_have_no_bounds_set")
{
ScopedFastFlag sff{FFlag::LuauFreeTypesMustHaveBounds, true};
@ -2224,4 +2285,60 @@ z:a
autocompleteFragmentInBothSolvers(source, dest, Position{8, 3}, [](FragmentAutocompleteStatusResult& _) {});
}
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "interior_free_types_assertion_caused_by_free_type_inheriting_null_scope_from_table")
{
ScopedFastFlag sff{FFlag::LuauTrackInteriorFreeTypesOnScope, true};
const std::string source = R"(--!strict
local foo
local a = foo()
local e = foo().x
local f = foo().y
)";
const std::string dest = R"(--!strict
local foo
local a = foo()
local e = foo().x
local f = foo().y
z = a.P.E
)";
autocompleteFragmentInBothSolvers(source, dest, Position{8, 9}, [](FragmentAutocompleteStatusResult& _) {});
}
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "NotNull_nil_scope_assertion_caused_by_free_type_inheriting_null_scope_from_table")
{
ScopedFastFlag sff{FFlag::LuauTrackInteriorFreeTypesOnScope, false};
const std::string source = R"(--!strict
local foo
local a = foo()
local e = foo().x
local f = foo().y
)";
const std::string dest = R"(--!strict
local foo
local a = foo()
local e = foo().x
local f = foo().y
z = a.P.E
)";
autocompleteFragmentInBothSolvers(source, dest, Position{8, 9}, [](FragmentAutocompleteStatusResult& _) {});
}
TEST_SUITE_END();

View file

@ -17,7 +17,6 @@ LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAG(DebugLuauFreezeArena)
LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAG(LuauSelectivelyRetainDFGArena)
LUAU_FASTFLAG(LuauBetterReverseDependencyTracking)
LUAU_FASTFLAG(LuauModuleHoldsAstRoot)
namespace
@ -1589,8 +1588,6 @@ return {x = a, y = b, z = c}
TEST_CASE_FIXTURE(FrontendFixture, "test_traverse_dependents")
{
ScopedFastFlag dependencyTracking{FFlag::LuauBetterReverseDependencyTracking, true};
fileResolver.source["game/Gui/Modules/A"] = "return {hello=5, world=true}";
fileResolver.source["game/Gui/Modules/B"] = R"(
return require(game:GetService('Gui').Modules.A)
@ -1623,8 +1620,6 @@ TEST_CASE_FIXTURE(FrontendFixture, "test_traverse_dependents")
TEST_CASE_FIXTURE(FrontendFixture, "test_traverse_dependents_early_exit")
{
ScopedFastFlag dependencyTracking{FFlag::LuauBetterReverseDependencyTracking, true};
fileResolver.source["game/Gui/Modules/A"] = "return {hello=5, world=true}";
fileResolver.source["game/Gui/Modules/B"] = R"(
return require(game:GetService('Gui').Modules.A)
@ -1652,8 +1647,6 @@ TEST_CASE_FIXTURE(FrontendFixture, "test_traverse_dependents_early_exit")
TEST_CASE_FIXTURE(FrontendFixture, "test_dependents_stored_on_node_as_graph_updates")
{
ScopedFastFlag dependencyTracking{FFlag::LuauBetterReverseDependencyTracking, true};
auto updateSource = [&](const std::string& name, const std::string& source)
{
fileResolver.source[name] = source;
@ -1768,7 +1761,6 @@ TEST_CASE_FIXTURE(FrontendFixture, "test_dependents_stored_on_node_as_graph_upda
TEST_CASE_FIXTURE(FrontendFixture, "test_invalid_dependency_tracking_per_module_resolver")
{
ScopedFastFlag dependencyTracking{FFlag::LuauBetterReverseDependencyTracking, true};
ScopedFastFlag newSolver{FFlag::LuauSolverV2, false};
fileResolver.source["game/Gui/Modules/A"] = "return {hello=5, world=true}";

View file

@ -18,11 +18,10 @@ LUAU_FASTINT(LuauParseErrorLimit)
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauAllowComplexTypesInGenericParams)
LUAU_FASTFLAG(LuauErrorRecoveryForTableTypes)
LUAU_FASTFLAG(LuauErrorRecoveryForClassNames)
LUAU_FASTFLAG(LuauFixFunctionNameStartPosition)
LUAU_FASTFLAG(LuauExtendStatEndPosWithSemicolon)
LUAU_FASTFLAG(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
LUAU_FASTFLAG(LuauAstTypeGroup2)
LUAU_FASTFLAG(LuauAstTypeGroup3)
LUAU_FASTFLAG(LuauFixDoBlockEndLocation)
namespace
@ -372,7 +371,7 @@ TEST_CASE_FIXTURE(Fixture, "return_type_is_an_intersection_type_if_led_with_one_
AstTypeIntersection* returnAnnotation = annotation->returnTypes.types.data[0]->as<AstTypeIntersection>();
REQUIRE(returnAnnotation != nullptr);
if (FFlag::LuauAstTypeGroup2)
if (FFlag::LuauAstTypeGroup3)
CHECK(returnAnnotation->types.data[0]->as<AstTypeGroup>());
else
CHECK(returnAnnotation->types.data[0]->as<AstTypeReference>());
@ -2127,8 +2126,6 @@ TEST_CASE_FIXTURE(Fixture, "variadic_definition_parsing")
TEST_CASE_FIXTURE(Fixture, "missing_declaration_prop")
{
ScopedFastFlag luauErrorRecoveryForClassNames{FFlag::LuauErrorRecoveryForClassNames, true};
matchParseError(
R"(
declare class Foo
@ -2451,7 +2448,7 @@ TEST_CASE_FIXTURE(Fixture, "leading_union_intersection_with_single_type_preserve
TEST_CASE_FIXTURE(Fixture, "parse_simple_ast_type_group")
{
ScopedFastFlag _{FFlag::LuauAstTypeGroup2, true};
ScopedFastFlag _{FFlag::LuauAstTypeGroup3, true};
AstStatBlock* stat = parse(R"(
type Foo = (string)
@ -2469,7 +2466,7 @@ TEST_CASE_FIXTURE(Fixture, "parse_simple_ast_type_group")
TEST_CASE_FIXTURE(Fixture, "parse_nested_ast_type_group")
{
ScopedFastFlag _{FFlag::LuauAstTypeGroup2, true};
ScopedFastFlag _{FFlag::LuauAstTypeGroup3, true};
AstStatBlock* stat = parse(R"(
type Foo = ((string))
@ -2490,7 +2487,7 @@ TEST_CASE_FIXTURE(Fixture, "parse_nested_ast_type_group")
TEST_CASE_FIXTURE(Fixture, "parse_return_type_ast_type_group")
{
ScopedFastFlag _{FFlag::LuauAstTypeGroup2, true};
ScopedFastFlag _{FFlag::LuauAstTypeGroup3, true};
AstStatBlock* stat = parse(R"(
type Foo = () -> (string)
@ -3813,7 +3810,7 @@ TEST_CASE_FIXTURE(Fixture, "grouped_function_type")
auto unionTy = paramTy.type->as<AstTypeUnion>();
LUAU_ASSERT(unionTy);
CHECK_EQ(unionTy->types.size, 2);
if (FFlag::LuauAstTypeGroup2)
if (FFlag::LuauAstTypeGroup3)
{
auto groupTy = unionTy->types.data[0]->as<AstTypeGroup>(); // (() -> ())
REQUIRE(groupTy);
@ -3926,5 +3923,16 @@ TEST_CASE_FIXTURE(Fixture, "stat_end_includes_semicolon_position")
CHECK_EQ(Position{3, 22}, stat3->location.end);
}
TEST_CASE_FIXTURE(Fixture, "parsing_type_suffix_for_return_type_with_variadic")
{
ParseResult result = tryParse(R"(
function foo(): (string, ...number) | boolean
end
)");
// TODO(CLI-140667): this should produce a ParseError in future when we fix the invalid syntax
CHECK(result.errors.size() == 0);
}
TEST_SUITE_END();

View file

@ -14,7 +14,7 @@ using namespace Luau;
LUAU_FASTFLAG(LuauStoreCSTData)
LUAU_FASTFLAG(LuauExtendStatEndPosWithSemicolon)
LUAU_FASTFLAG(LuauAstTypeGroup2);
LUAU_FASTFLAG(LuauAstTypeGroup3);
LUAU_FASTFLAG(LexerFixInterpStringStart)
TEST_SUITE_BEGIN("TranspilerTests");
@ -1175,7 +1175,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_union_type_nested_3")
{
std::string code = "local a: nil | (string & number)";
if (FFlag::LuauAstTypeGroup2)
if (FFlag::LuauAstTypeGroup3)
CHECK_EQ("local a: (string & number)?", transpile(code, {}, true).code);
else
CHECK_EQ("local a: ( string & number)?", transpile(code, {}, true).code);
@ -1736,7 +1736,7 @@ TEST_CASE("transpile_types_preserve_parentheses_style")
{
ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData, true},
{FFlag::LuauAstTypeGroup2, true},
{FFlag::LuauAstTypeGroup3, true},
};
std::string code = R"( type Foo = number )";

View file

@ -12,6 +12,7 @@ using namespace Luau;
LUAU_FASTFLAG(LuauClipNestedAndRecursiveUnion)
LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauPreventReentrantTypeFunctionReduction)
LUAU_FASTFLAG(LuauDontForgetToReduceUnionFunc)
TEST_SUITE_BEGIN("DefinitionTests");
@ -557,6 +558,38 @@ TEST_CASE_FIXTURE(Fixture, "recursive_redefinition_reduces_rightfully")
)"));
}
TEST_CASE_FIXTURE(BuiltinsFixture, "cli_142285_reduce_minted_union_func")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauDontForgetToReduceUnionFunc, true}
};
CheckResult result = check(R"(
local function middle(a: number, b: number): number
return math.ceil((a + b) / 2 - 0.5)
end
local function find<T>(array: {T}, item: T): number?
local l, m, r = 1, middle(1, #array), #array
while l <= r do
if item <= array[m] then
if item == array[m] then return m end
m, r = middle(l, m-1), m-1
else
l, m = middle(m+1, r), m+1
end
end
return nil
end
)");
LUAU_REQUIRE_ERROR_COUNT(3, result);
// There are three errors in the above snippet, but they should all be where
// clause needed errors.
for (const auto& e: result.errors)
CHECK(get<WhereClauseNeeded>(e));
}
TEST_CASE_FIXTURE(Fixture, "vector3_overflow")
{
ScopedFastFlag _{FFlag::LuauPreventReentrantTypeFunctionReduction, true};

View file

@ -27,6 +27,9 @@ LUAU_FASTFLAG(LuauFollowTableFreeze)
LUAU_FASTFLAG(LuauPrecalculateMutatedFreeTypes2)
LUAU_FASTFLAG(LuauDeferBidirectionalInferenceForTableAssignment)
LUAU_FASTFLAG(LuauBidirectionalInferenceUpcast)
LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint)
LUAU_FASTFLAG(LuauSearchForRefineableType)
LUAU_FASTFLAG(LuauDoNotGeneralizeInTypeFunctions)
TEST_SUITE_BEGIN("TableTests");
@ -5338,4 +5341,31 @@ TEST_CASE_FIXTURE(Fixture, "missing_fields_bidirectional_inference")
}
TEST_CASE_FIXTURE(Fixture, "deeply_nested_classish_inference")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauDoNotGeneralizeInTypeFunctions, true},
{FFlag::LuauSearchForRefineableType, true},
{FFlag::DebugLuauAssertOnForcedConstraint, true},
};
// NOTE: This probably should be revisited after CLI-143852: we end up
// cyclic types with *tons* of overlap.
LUAU_REQUIRE_NO_ERRORS(check(R"(
local function f(part, flag, params)
local humanoid = part.Parent:FindFirstChild("Humanoid") or part.Parent.Parent:FindFirstChild("Humanoid")
if humanoid.Parent:GetAttribute("Blocking") then
if flag then
params.Found = { humanoid }
else
humanoid:Think(1)
end
else
humanoid:Think(2)
end
end
)"));
}
TEST_SUITE_END();

View file

@ -20,13 +20,15 @@ LUAU_FASTFLAG(LuauFixLocationSpanTableIndexExpr)
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTINT(LuauCheckRecursionLimit)
LUAU_FASTFLAG(LuauGlobalSelfAssignmentCycle)
LUAU_FASTINT(LuauNormalizeCacheLimit)
LUAU_FASTINT(LuauRecursionLimit)
LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauAstTypeGroup2)
LUAU_FASTFLAG(LuauAstTypeGroup3)
LUAU_FASTFLAG(LuauNewNonStrictWarnOnUnknownGlobals)
LUAU_FASTFLAG(LuauInferLocalTypesInMultipleAssignments)
LUAU_FASTFLAG(LuauUnifyMetatableWithAny)
LUAU_FASTFLAG(LuauExtraFollows)
using namespace Luau;
@ -1202,12 +1204,12 @@ TEST_CASE_FIXTURE(Fixture, "type_infer_recursion_limit_normalizer")
if (FFlag::LuauSolverV2)
{
CHECK(3 == result.errors.size());
if (FFlag::LuauAstTypeGroup2)
if (FFlag::LuauAstTypeGroup3)
CHECK(Location{{2, 22}, {2, 42}} == result.errors[0].location);
else
CHECK(Location{{2, 22}, {2, 41}} == result.errors[0].location);
CHECK(Location{{3, 22}, {3, 42}} == result.errors[1].location);
if (FFlag::LuauAstTypeGroup2)
if (FFlag::LuauAstTypeGroup3)
CHECK(Location{{3, 22}, {3, 41}} == result.errors[2].location);
else
CHECK(Location{{3, 23}, {3, 40}} == result.errors[2].location);
@ -1809,6 +1811,17 @@ TEST_CASE_FIXTURE(Fixture, "multiple_assignment")
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(Fixture, "fuzz_global_self_assignment")
{
ScopedFastFlag luauGlobalSelfAssignmentCycle{FFlag::LuauGlobalSelfAssignmentCycle, true};
// Shouldn't assert or crash
check(R"(
_ = _
)");
}
TEST_CASE_FIXTURE(BuiltinsFixture, "getmetatable_works_with_any")
{
ScopedFastFlag _{FFlag::LuauUnifyMetatableWithAny, true};
@ -1860,4 +1873,42 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "getmetatable_infer_any_param")
CHECK_EQ("({ @metatable MT, {+ +} }) -> any", toString(requireType("check")));
}
TEST_CASE_FIXTURE(BuiltinsFixture, "fuzzer_pack_check_missing_follow")
{
ScopedFastFlag luauExtraFollows{FFlag::LuauExtraFollows, true};
// Shouldn't assert or crash
check(R"(
_ = n255
function _()
setmetatable(_)[_[xpcall(_,setmetatable(_,_()))]] /= xpcall(_,_)
_.n16(_,_)[_[_]] *= _
end
)");
}
TEST_CASE_FIXTURE(Fixture, "fuzzer_unify_with_free_missing_follow")
{
ScopedFastFlag luauExtraFollows{FFlag::LuauExtraFollows, true};
// Shouldn't assert or crash
check(R"(
for _ in ... do
repeat
local function l0(l0)
end
_ = l0["aaaa"]
repeat
_ = true,_("")
_ = _[_]
until _
until _
repeat
_ = if _ then _,_()
_ = _[_]
until _
end
)");
}
TEST_SUITE_END();