mirror of
https://github.com/luau-lang/luau.git
synced 2025-04-01 17:30:53 +01:00
Merge branch 'upstream' into merge
This commit is contained in:
commit
de3f3d2de5
32 changed files with 1056 additions and 409 deletions
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
172
Analysis/src/FileResolver.cpp
Normal file
172
Analysis/src/FileResolver.cpp
Normal 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
|
|
@ -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)};
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)->(''))");
|
||||
}
|
||||
|
|
|
@ -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})";
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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}";
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 )";
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Add table
Reference in a new issue