mirror of
https://github.com/luau-lang/luau.git
synced 2025-03-13 15:34:27 +00:00
Merge branch 'upstream' into merge
This commit is contained in:
commit
23bc546e97
30 changed files with 1435 additions and 308 deletions
|
@ -40,4 +40,9 @@ TypeId clone(TypeId tp, TypeArena& dest, CloneState& cloneState);
|
|||
TypeFun clone(const TypeFun& typeFun, TypeArena& dest, CloneState& cloneState);
|
||||
Binding clone(const Binding& binding, TypeArena& dest, CloneState& cloneState);
|
||||
|
||||
TypePackId cloneIncremental(TypePackId tp, TypeArena& dest, CloneState& cloneState, Scope* freshScopeForFreeTypes);
|
||||
TypeId cloneIncremental(TypeId typeId, TypeArena& dest, CloneState& cloneState, Scope* freshScopeForFreeTypes);
|
||||
TypeFun cloneIncremental(const TypeFun& typeFun, TypeArena& dest, CloneState& cloneState, Scope* freshScopeForFreeTypes);
|
||||
Binding cloneIncremental(const Binding& binding, TypeArena& dest, CloneState& cloneState, Scope* freshScopeForFreeTypes);
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -57,7 +57,8 @@ struct FragmentAutocompleteResult
|
|||
FragmentAutocompleteAncestryResult findAncestryForFragmentParse(AstStatBlock* root, const Position& cursorPos);
|
||||
|
||||
std::optional<FragmentParseResult> parseFragment(
|
||||
const SourceModule& srcModule,
|
||||
AstStatBlock* root,
|
||||
AstNameTable* names,
|
||||
std::string_view src,
|
||||
const Position& cursorPos,
|
||||
std::optional<Position> fragmentEndPosition
|
||||
|
@ -98,7 +99,7 @@ struct FragmentAutocompleteStatusResult
|
|||
struct FragmentContext
|
||||
{
|
||||
std::string_view newSrc;
|
||||
const ParseResult& newAstRoot;
|
||||
const ParseResult& freshParse;
|
||||
std::optional<FrontendOptions> opts;
|
||||
std::optional<Position> DEPRECATED_fragmentEndPosition;
|
||||
};
|
||||
|
|
|
@ -32,6 +32,7 @@ struct ModuleResolver;
|
|||
struct ParseResult;
|
||||
struct HotComment;
|
||||
struct BuildQueueItem;
|
||||
struct BuildQueueWorkState;
|
||||
struct FrontendCancellationToken;
|
||||
struct AnyTypeSummary;
|
||||
|
||||
|
@ -216,6 +217,11 @@ struct Frontend
|
|||
std::function<void(std::function<void()> task)> executeTask = {},
|
||||
std::function<bool(size_t done, size_t total)> progress = {}
|
||||
);
|
||||
std::vector<ModuleName> checkQueuedModules_DEPRECATED(
|
||||
std::optional<FrontendOptions> optionOverride = {},
|
||||
std::function<void(std::function<void()> task)> executeTask = {},
|
||||
std::function<bool(size_t done, size_t total)> progress = {}
|
||||
);
|
||||
|
||||
std::optional<CheckResult> getCheckResult(const ModuleName& name, bool accumulateNested, bool forAutocomplete = false);
|
||||
std::vector<ModuleName> getRequiredScripts(const ModuleName& name);
|
||||
|
@ -251,6 +257,9 @@ private:
|
|||
void checkBuildQueueItem(BuildQueueItem& item);
|
||||
void checkBuildQueueItems(std::vector<BuildQueueItem>& items);
|
||||
void recordItemResult(const BuildQueueItem& item);
|
||||
void performQueueItemTask(std::shared_ptr<BuildQueueWorkState> state, size_t itemPos);
|
||||
void sendQueueItemTask(std::shared_ptr<BuildQueueWorkState> state, size_t itemPos);
|
||||
void sendQueueCycleItemTask(std::shared_ptr<BuildQueueWorkState> state);
|
||||
|
||||
static LintResult classifyLints(const std::vector<LintWarning>& warnings, const Config& config);
|
||||
|
||||
|
|
|
@ -93,6 +93,7 @@ struct Module
|
|||
// Scopes and AST types refer to parse data, so we need to keep that alive
|
||||
std::shared_ptr<Allocator> allocator;
|
||||
std::shared_ptr<AstNameTable> names;
|
||||
AstStatBlock* root = nullptr;
|
||||
|
||||
std::vector<std::pair<Location, ScopePtr>> scopes; // never empty
|
||||
|
||||
|
|
|
@ -53,6 +53,7 @@ struct Proposition
|
|||
{
|
||||
const RefinementKey* key;
|
||||
TypeId discriminantTy;
|
||||
bool implicitFromCall;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
|
@ -69,6 +70,7 @@ struct RefinementArena
|
|||
RefinementId disjunction(RefinementId lhs, RefinementId rhs);
|
||||
RefinementId equivalence(RefinementId lhs, RefinementId rhs);
|
||||
RefinementId proposition(const RefinementKey* key, TypeId discriminantTy);
|
||||
RefinementId implicitProposition(const RefinementKey* key, TypeId discriminantTy);
|
||||
|
||||
private:
|
||||
TypedAllocator<Refinement> allocator;
|
||||
|
|
|
@ -14,6 +14,7 @@ namespace Luau
|
|||
struct TypeArena;
|
||||
struct BuiltinTypes;
|
||||
struct Unifier2;
|
||||
struct Subtyping;
|
||||
class AstExpr;
|
||||
|
||||
TypeId matchLiteralType(
|
||||
|
@ -22,6 +23,7 @@ TypeId matchLiteralType(
|
|||
NotNull<BuiltinTypes> builtinTypes,
|
||||
NotNull<TypeArena> arena,
|
||||
NotNull<Unifier2> unifier,
|
||||
NotNull<Subtyping> subtyping,
|
||||
TypeId expectedType,
|
||||
TypeId exprType,
|
||||
const AstExpr* expr,
|
||||
|
|
|
@ -87,6 +87,9 @@ struct Unifier2
|
|||
bool unify(const AnyType* subAny, const TableType* superTable);
|
||||
bool unify(const TableType* subTable, const AnyType* superAny);
|
||||
|
||||
bool unify(const MetatableType* subMetatable, const AnyType*);
|
||||
bool unify(const AnyType*, const MetatableType* superMetatable);
|
||||
|
||||
// TODO think about this one carefully. We don't do unions or intersections of type packs
|
||||
bool unify(TypePackId subTp, TypePackId superTp);
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTINT(LuauTypeInferIterationLimit)
|
||||
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauMagicVariableNames)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauAutocompleteRefactorsForIncrementalAutocomplete)
|
||||
|
||||
|
@ -1343,6 +1344,15 @@ static AutocompleteContext autocompleteExpression(
|
|||
|
||||
AstNode* node = ancestry.rbegin()[0];
|
||||
|
||||
if (FFlag::DebugLuauMagicVariableNames)
|
||||
{
|
||||
InternalErrorReporter ice;
|
||||
if (auto local = node->as<AstExprLocal>(); local && local->local->name == "_luau_autocomplete_ice")
|
||||
ice.ice("_luau_autocomplete_ice encountered", local->location);
|
||||
if (auto global = node->as<AstExprGlobal>(); global && global->name == "_luau_autocomplete_ice")
|
||||
ice.ice("_luau_autocomplete_ice encountered", global->location);
|
||||
}
|
||||
|
||||
if (node->is<AstExprIndexName>())
|
||||
{
|
||||
if (auto it = module.astTypes.find(node->asExpr()))
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include "Luau/Type.h"
|
||||
#include "Luau/TypePack.h"
|
||||
#include "Luau/Unifiable.h"
|
||||
#include "Luau/VisitType.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauFreezeIgnorePersistent)
|
||||
|
@ -28,6 +29,8 @@ const T* get(const Kind& kind)
|
|||
|
||||
class TypeCloner
|
||||
{
|
||||
|
||||
protected:
|
||||
NotNull<TypeArena> arena;
|
||||
NotNull<BuiltinTypes> builtinTypes;
|
||||
|
||||
|
@ -62,6 +65,8 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
virtual ~TypeCloner() = default;
|
||||
|
||||
TypeId clone(TypeId ty)
|
||||
{
|
||||
shallowClone(ty);
|
||||
|
@ -120,6 +125,7 @@ private:
|
|||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
std::optional<TypeId> find(TypeId ty) const
|
||||
{
|
||||
ty = follow(ty, FollowOption::DisableLazyTypeThunks);
|
||||
|
@ -154,7 +160,7 @@ private:
|
|||
}
|
||||
|
||||
public:
|
||||
TypeId shallowClone(TypeId ty)
|
||||
virtual TypeId shallowClone(TypeId ty)
|
||||
{
|
||||
// We want to [`Luau::follow`] but without forcing the expansion of [`LazyType`]s.
|
||||
ty = follow(ty, FollowOption::DisableLazyTypeThunks);
|
||||
|
@ -181,7 +187,7 @@ public:
|
|||
return target;
|
||||
}
|
||||
|
||||
TypePackId shallowClone(TypePackId tp)
|
||||
virtual TypePackId shallowClone(TypePackId tp)
|
||||
{
|
||||
tp = follow(tp);
|
||||
|
||||
|
@ -469,6 +475,78 @@ private:
|
|||
}
|
||||
};
|
||||
|
||||
class FragmentAutocompleteTypeCloner final : public TypeCloner
|
||||
{
|
||||
Scope* freeTypeReplacementScope = nullptr;
|
||||
|
||||
public:
|
||||
FragmentAutocompleteTypeCloner(
|
||||
NotNull<TypeArena> arena,
|
||||
NotNull<BuiltinTypes> builtinTypes,
|
||||
NotNull<SeenTypes> types,
|
||||
NotNull<SeenTypePacks> packs,
|
||||
TypeId forceTy,
|
||||
TypePackId forceTp,
|
||||
Scope* freeTypeReplacementScope
|
||||
)
|
||||
: TypeCloner(arena, builtinTypes, types, packs, forceTy, forceTp)
|
||||
, freeTypeReplacementScope(freeTypeReplacementScope)
|
||||
{
|
||||
LUAU_ASSERT(freeTypeReplacementScope);
|
||||
}
|
||||
|
||||
TypeId shallowClone(TypeId ty) override
|
||||
{
|
||||
// We want to [`Luau::follow`] but without forcing the expansion of [`LazyType`]s.
|
||||
ty = follow(ty, FollowOption::DisableLazyTypeThunks);
|
||||
|
||||
if (auto clone = find(ty))
|
||||
return *clone;
|
||||
else if (ty->persistent && (!FFlag::LuauFreezeIgnorePersistent || ty != forceTy))
|
||||
return ty;
|
||||
|
||||
TypeId target = arena->addType(ty->ty);
|
||||
asMutable(target)->documentationSymbol = ty->documentationSymbol;
|
||||
|
||||
if (auto generic = getMutable<GenericType>(target))
|
||||
generic->scope = nullptr;
|
||||
else if (auto free = getMutable<FreeType>(target))
|
||||
{
|
||||
free->scope = freeTypeReplacementScope;
|
||||
}
|
||||
else if (auto fn = getMutable<FunctionType>(target))
|
||||
fn->scope = nullptr;
|
||||
else if (auto table = getMutable<TableType>(target))
|
||||
table->scope = nullptr;
|
||||
|
||||
(*types)[ty] = target;
|
||||
queue.emplace_back(target);
|
||||
return target;
|
||||
}
|
||||
|
||||
TypePackId shallowClone(TypePackId tp) override
|
||||
{
|
||||
tp = follow(tp);
|
||||
|
||||
if (auto clone = find(tp))
|
||||
return *clone;
|
||||
else if (tp->persistent && (!FFlag::LuauFreezeIgnorePersistent || tp != forceTp))
|
||||
return tp;
|
||||
|
||||
TypePackId target = arena->addTypePack(tp->ty);
|
||||
|
||||
if (auto generic = getMutable<GenericTypePack>(target))
|
||||
generic->scope = nullptr;
|
||||
else if (auto free = getMutable<FreeTypePack>(target))
|
||||
free->scope = freeTypeReplacementScope;
|
||||
|
||||
(*packs)[tp] = target;
|
||||
queue.emplace_back(target);
|
||||
return target;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // namespace
|
||||
|
||||
TypePackId shallowClone(TypePackId tp, TypeArena& dest, CloneState& cloneState, bool ignorePersistent)
|
||||
|
@ -564,4 +642,96 @@ Binding clone(const Binding& binding, TypeArena& dest, CloneState& cloneState)
|
|||
return b;
|
||||
}
|
||||
|
||||
TypePackId cloneIncremental(TypePackId tp, TypeArena& dest, CloneState& cloneState, Scope* freshScopeForFreeTypes)
|
||||
{
|
||||
if (tp->persistent)
|
||||
return tp;
|
||||
|
||||
FragmentAutocompleteTypeCloner cloner{
|
||||
NotNull{&dest},
|
||||
cloneState.builtinTypes,
|
||||
NotNull{&cloneState.seenTypes},
|
||||
NotNull{&cloneState.seenTypePacks},
|
||||
nullptr,
|
||||
nullptr,
|
||||
freshScopeForFreeTypes
|
||||
};
|
||||
return cloner.clone(tp);
|
||||
}
|
||||
|
||||
TypeId cloneIncremental(TypeId typeId, TypeArena& dest, CloneState& cloneState, Scope* freshScopeForFreeTypes)
|
||||
{
|
||||
if (typeId->persistent)
|
||||
return typeId;
|
||||
|
||||
FragmentAutocompleteTypeCloner cloner{
|
||||
NotNull{&dest},
|
||||
cloneState.builtinTypes,
|
||||
NotNull{&cloneState.seenTypes},
|
||||
NotNull{&cloneState.seenTypePacks},
|
||||
nullptr,
|
||||
nullptr,
|
||||
freshScopeForFreeTypes
|
||||
};
|
||||
return cloner.clone(typeId);
|
||||
}
|
||||
|
||||
TypeFun cloneIncremental(const TypeFun& typeFun, TypeArena& dest, CloneState& cloneState, Scope* freshScopeForFreeTypes)
|
||||
{
|
||||
FragmentAutocompleteTypeCloner cloner{
|
||||
NotNull{&dest},
|
||||
cloneState.builtinTypes,
|
||||
NotNull{&cloneState.seenTypes},
|
||||
NotNull{&cloneState.seenTypePacks},
|
||||
nullptr,
|
||||
nullptr,
|
||||
freshScopeForFreeTypes
|
||||
};
|
||||
|
||||
TypeFun copy = typeFun;
|
||||
|
||||
for (auto& param : copy.typeParams)
|
||||
{
|
||||
param.ty = cloner.clone(param.ty);
|
||||
|
||||
if (param.defaultValue)
|
||||
param.defaultValue = cloner.clone(*param.defaultValue);
|
||||
}
|
||||
|
||||
for (auto& param : copy.typePackParams)
|
||||
{
|
||||
param.tp = cloner.clone(param.tp);
|
||||
|
||||
if (param.defaultValue)
|
||||
param.defaultValue = cloner.clone(*param.defaultValue);
|
||||
}
|
||||
|
||||
copy.type = cloner.clone(copy.type);
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
Binding cloneIncremental(const Binding& binding, TypeArena& dest, CloneState& cloneState, Scope* freshScopeForFreeTypes)
|
||||
{
|
||||
FragmentAutocompleteTypeCloner cloner{
|
||||
NotNull{&dest},
|
||||
cloneState.builtinTypes,
|
||||
NotNull{&cloneState.seenTypes},
|
||||
NotNull{&cloneState.seenTypePacks},
|
||||
nullptr,
|
||||
nullptr,
|
||||
freshScopeForFreeTypes
|
||||
};
|
||||
|
||||
Binding b;
|
||||
b.deprecated = binding.deprecated;
|
||||
b.deprecatedSuggestion = binding.deprecatedSuggestion;
|
||||
b.documentationSymbol = binding.documentationSymbol;
|
||||
b.location = binding.location;
|
||||
b.typeId = cloner.clone(binding.typeId);
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include "Luau/Scope.h"
|
||||
#include "Luau/Simplify.h"
|
||||
#include "Luau/StringUtils.h"
|
||||
#include "Luau/Subtyping.h"
|
||||
#include "Luau/TableLiteralInference.h"
|
||||
#include "Luau/TimeTrace.h"
|
||||
#include "Luau/Type.h"
|
||||
|
@ -40,6 +41,7 @@ LUAU_FASTFLAGVARIABLE(LuauUngeneralizedTypesForRecursiveFunctions)
|
|||
|
||||
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
|
||||
LUAU_FASTFLAGVARIABLE(LuauInferLocalTypesInMultipleAssignments)
|
||||
LUAU_FASTFLAGVARIABLE(LuauDoNotLeakNilInRefinement)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -527,7 +529,15 @@ void ConstraintGenerator::computeRefinement(
|
|||
|
||||
// When the top-level expression is `t[x]`, we want to refine it into `nil`, not `never`.
|
||||
LUAU_ASSERT(refis->get(proposition->key->def));
|
||||
refis->get(proposition->key->def)->shouldAppendNilType = (sense || !eq) && containsSubscriptedDefinition(proposition->key->def);
|
||||
if (FFlag::LuauDoNotLeakNilInRefinement)
|
||||
{
|
||||
refis->get(proposition->key->def)->shouldAppendNilType =
|
||||
(sense || !eq) && containsSubscriptedDefinition(proposition->key->def) && !proposition->implicitFromCall;
|
||||
}
|
||||
else
|
||||
{
|
||||
refis->get(proposition->key->def)->shouldAppendNilType = (sense || !eq) && containsSubscriptedDefinition(proposition->key->def);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1985,7 +1995,7 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall*
|
|||
if (auto key = dfg->getRefinementKey(indexExpr->expr))
|
||||
{
|
||||
TypeId discriminantTy = arena->addType(BlockedType{});
|
||||
returnRefinements.push_back(refinementArena.proposition(key, discriminantTy));
|
||||
returnRefinements.push_back(refinementArena.implicitProposition(key, discriminantTy));
|
||||
discriminantTypes.push_back(discriminantTy);
|
||||
}
|
||||
else
|
||||
|
@ -1999,7 +2009,7 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall*
|
|||
if (auto key = dfg->getRefinementKey(arg))
|
||||
{
|
||||
TypeId discriminantTy = arena->addType(BlockedType{});
|
||||
returnRefinements.push_back(refinementArena.proposition(key, discriminantTy));
|
||||
returnRefinements.push_back(refinementArena.implicitProposition(key, discriminantTy));
|
||||
discriminantTypes.push_back(discriminantTy);
|
||||
}
|
||||
else
|
||||
|
@ -3022,6 +3032,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr,
|
|||
else
|
||||
{
|
||||
Unifier2 unifier{arena, builtinTypes, NotNull{scope.get()}, ice};
|
||||
Subtyping sp{builtinTypes, arena, simplifier, normalizer, typeFunctionRuntime, ice};
|
||||
std::vector<TypeId> toBlock;
|
||||
// This logic is incomplete as we want to re-run this
|
||||
// _after_ blocked types have resolved, but this
|
||||
|
@ -3035,6 +3046,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr,
|
|||
builtinTypes,
|
||||
arena,
|
||||
NotNull{&unifier},
|
||||
NotNull{&sp},
|
||||
*expectedType,
|
||||
ty,
|
||||
expr,
|
||||
|
|
|
@ -635,6 +635,7 @@ struct TypeSearcher : TypeVisitor
|
|||
TypeId needle;
|
||||
Polarity current = Polarity::Positive;
|
||||
|
||||
size_t count = 0;
|
||||
Polarity result = Polarity::None;
|
||||
|
||||
explicit TypeSearcher(TypeId needle)
|
||||
|
@ -649,7 +650,10 @@ struct TypeSearcher : TypeVisitor
|
|||
bool visit(TypeId ty) override
|
||||
{
|
||||
if (ty == needle)
|
||||
result = Polarity(int(result) | int(current));
|
||||
{
|
||||
++count;
|
||||
result = Polarity(size_t(result) | size_t(current));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -749,7 +753,7 @@ void ConstraintSolver::generalizeOneType(TypeId ty)
|
|||
|
||||
case TypeSearcher::Polarity::Negative:
|
||||
case TypeSearcher::Polarity::Mixed:
|
||||
if (get<UnknownType>(upperBound))
|
||||
if (get<UnknownType>(upperBound) && ts.count > 1)
|
||||
{
|
||||
asMutable(ty)->reassign(Type{GenericType{tyScope}});
|
||||
function->generics.emplace_back(ty);
|
||||
|
@ -759,7 +763,7 @@ void ConstraintSolver::generalizeOneType(TypeId ty)
|
|||
break;
|
||||
|
||||
case TypeSearcher::Polarity::Positive:
|
||||
if (get<UnknownType>(lowerBound))
|
||||
if (get<UnknownType>(lowerBound) && ts.count > 1)
|
||||
{
|
||||
asMutable(ty)->reassign(Type{GenericType{tyScope}});
|
||||
function->generics.emplace_back(ty);
|
||||
|
@ -1370,9 +1374,17 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
|
|||
TypePackId argsPack = follow(c.argsPack);
|
||||
TypePackId result = follow(c.result);
|
||||
|
||||
if (isBlocked(fn) || hasUnresolvedConstraints(fn))
|
||||
if (FFlag::DebugLuauGreedyGeneralization)
|
||||
{
|
||||
return block(c.fn, constraint);
|
||||
if (isBlocked(fn))
|
||||
return block(c.fn, constraint);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isBlocked(fn) || hasUnresolvedConstraints(fn))
|
||||
{
|
||||
return block(c.fn, constraint);
|
||||
}
|
||||
}
|
||||
|
||||
if (get<AnyType>(fn))
|
||||
|
@ -1658,8 +1670,11 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<con
|
|||
else if (expr->is<AstExprTable>())
|
||||
{
|
||||
Unifier2 u2{arena, builtinTypes, constraint->scope, NotNull{&iceReporter}};
|
||||
Subtyping sp{builtinTypes, arena, simplifier, normalizer, typeFunctionRuntime, NotNull{&iceReporter}};
|
||||
std::vector<TypeId> toBlock;
|
||||
(void)matchLiteralType(c.astTypes, c.astExpectedTypes, builtinTypes, arena, NotNull{&u2}, expectedArgTy, actualArgTy, expr, toBlock);
|
||||
(void)matchLiteralType(
|
||||
c.astTypes, c.astExpectedTypes, builtinTypes, arena, NotNull{&u2}, NotNull{&sp}, expectedArgTy, actualArgTy, expr, toBlock
|
||||
);
|
||||
LUAU_ASSERT(toBlock.empty());
|
||||
}
|
||||
}
|
||||
|
@ -1683,8 +1698,9 @@ bool ConstraintSolver::tryDispatch(const TableCheckConstraint& c, NotNull<const
|
|||
return false;
|
||||
|
||||
Unifier2 u2{arena, builtinTypes, constraint->scope, NotNull{&iceReporter}};
|
||||
Subtyping sp{builtinTypes, arena, simplifier, normalizer, typeFunctionRuntime, NotNull{&iceReporter}};
|
||||
std::vector<TypeId> toBlock;
|
||||
(void)matchLiteralType(c.astTypes, c.astExpectedTypes, builtinTypes, arena, NotNull{&u2}, c.expectedType, c.exprType, c.table, toBlock);
|
||||
(void)matchLiteralType(c.astTypes, c.astExpectedTypes, builtinTypes, arena, NotNull{&u2}, NotNull{&sp}, c.expectedType, c.exprType, c.table, toBlock);
|
||||
LUAU_ASSERT(toBlock.empty());
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -911,8 +911,17 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprCall* c)
|
|||
for (AstExpr* arg : c->args)
|
||||
visitExpr(arg);
|
||||
|
||||
// calls should be treated as subscripted.
|
||||
return {defArena->freshCell(/* subscripted */ true), nullptr};
|
||||
// We treat function calls as "subscripted" as they could potentially
|
||||
// return a subscripted value, consider:
|
||||
//
|
||||
// local function foo(tbl: {[string]: woof)
|
||||
// return tbl["foobarbaz"]
|
||||
// end
|
||||
//
|
||||
// local v = foo({})
|
||||
//
|
||||
// We want to consider `v` to be subscripted here.
|
||||
return {defArena->freshCell(/*subscripted=*/true)};
|
||||
}
|
||||
|
||||
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprIndexName* i)
|
||||
|
|
|
@ -33,6 +33,11 @@ LUAU_FASTFLAGVARIABLE(LuauMixedModeDefFinderTraversesTypeOf)
|
|||
LUAU_FASTFLAG(LuauBetterReverseDependencyTracking)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCloneIncrementalModule)
|
||||
LUAU_FASTFLAGVARIABLE(LogFragmentsFromAutocomplete)
|
||||
LUAU_FASTFLAGVARIABLE(LuauBetterCursorInCommentDetection)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAllFreeTypesHaveScopes)
|
||||
LUAU_FASTFLAGVARIABLE(LuauPersistConstraintGenerationScopes)
|
||||
LUAU_FASTFLAG(LuauModuleHoldsAstRoot)
|
||||
|
||||
namespace
|
||||
{
|
||||
template<typename T>
|
||||
|
@ -54,7 +59,7 @@ namespace Luau
|
|||
{
|
||||
|
||||
template<typename K, typename V>
|
||||
void cloneModuleMap(TypeArena& destArena, CloneState& cloneState, const Luau::DenseHashMap<K, V>& source, Luau::DenseHashMap<K, V>& dest)
|
||||
void cloneModuleMap_DEPRECATED(TypeArena& destArena, CloneState& cloneState, const Luau::DenseHashMap<K, V>& source, Luau::DenseHashMap<K, V>& dest)
|
||||
{
|
||||
for (auto [k, v] : source)
|
||||
{
|
||||
|
@ -62,6 +67,21 @@ void cloneModuleMap(TypeArena& destArena, CloneState& cloneState, const Luau::De
|
|||
}
|
||||
}
|
||||
|
||||
template<typename K, typename V>
|
||||
void cloneModuleMap(
|
||||
TypeArena& destArena,
|
||||
CloneState& cloneState,
|
||||
const Luau::DenseHashMap<K, V>& source,
|
||||
Luau::DenseHashMap<K, V>& dest,
|
||||
Scope* freshScopeForFreeType
|
||||
)
|
||||
{
|
||||
for (auto [k, v] : source)
|
||||
{
|
||||
dest[k] = Luau::cloneIncremental(v, destArena, cloneState, freshScopeForFreeType);
|
||||
}
|
||||
}
|
||||
|
||||
struct MixedModeIncrementalTCDefFinder : public AstVisitor
|
||||
{
|
||||
bool visit(AstExprLocal* local) override
|
||||
|
@ -87,7 +107,7 @@ struct MixedModeIncrementalTCDefFinder : public AstVisitor
|
|||
std::vector<std::pair<AstLocal*, AstExpr*>> referencedLocalDefs;
|
||||
};
|
||||
|
||||
void cloneAndSquashScopes(
|
||||
void cloneAndSquashScopes_DEPRECATED(
|
||||
CloneState& cloneState,
|
||||
const Scope* staleScope,
|
||||
const ModulePtr& staleModule,
|
||||
|
@ -144,6 +164,63 @@ void cloneAndSquashScopes(
|
|||
return;
|
||||
}
|
||||
|
||||
void cloneAndSquashScopes(
|
||||
CloneState& cloneState,
|
||||
const Scope* staleScope,
|
||||
const ModulePtr& staleModule,
|
||||
NotNull<TypeArena> destArena,
|
||||
NotNull<DataFlowGraph> dfg,
|
||||
AstStatBlock* program,
|
||||
Scope* destScope
|
||||
)
|
||||
{
|
||||
LUAU_TIMETRACE_SCOPE("Luau::cloneAndSquashScopes", "FragmentAutocomplete");
|
||||
std::vector<const Scope*> scopes;
|
||||
for (const Scope* current = staleScope; current; current = current->parent.get())
|
||||
{
|
||||
scopes.emplace_back(current);
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
const Scope* curr = *it;
|
||||
// Clone the lvalue types
|
||||
for (const auto& [def, ty] : curr->lvalueTypes)
|
||||
destScope->lvalueTypes[def] = Luau::cloneIncremental(ty, *destArena, cloneState, destScope);
|
||||
// Clone the rvalueRefinements
|
||||
for (const auto& [def, ty] : curr->rvalueRefinements)
|
||||
destScope->rvalueRefinements[def] = Luau::cloneIncremental(ty, *destArena, cloneState, destScope);
|
||||
for (const auto& [n, m] : curr->importedTypeBindings)
|
||||
{
|
||||
std::unordered_map<Name, TypeFun> importedBindingTypes;
|
||||
for (const auto& [v, tf] : m)
|
||||
importedBindingTypes[v] = Luau::cloneIncremental(tf, *destArena, cloneState, destScope);
|
||||
destScope->importedTypeBindings[n] = std::move(importedBindingTypes);
|
||||
}
|
||||
|
||||
// Finally, clone up the bindings
|
||||
for (const auto& [s, b] : curr->bindings)
|
||||
{
|
||||
destScope->bindings[s] = Luau::cloneIncremental(b, *destArena, cloneState, destScope);
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
if (std::optional<Binding> binding = staleScope->linearSearchForBinding(loc->name.value, true))
|
||||
{
|
||||
destScope->lvalueTypes[dfg->getDef(expr)] = Luau::cloneIncremental(binding->typeId, *destArena, cloneState, destScope);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
static FrontendModuleResolver& getModuleResolver(Frontend& frontend, std::optional<FrontendOptions> options)
|
||||
{
|
||||
if (FFlag::LuauSolverV2 || !options)
|
||||
|
@ -152,6 +229,16 @@ static FrontendModuleResolver& getModuleResolver(Frontend& frontend, std::option
|
|||
return options->forAutocomplete ? frontend.moduleResolverForAutocomplete : frontend.moduleResolver;
|
||||
}
|
||||
|
||||
bool statIsBeforePos(const AstNode* stat, const Position& cursorPos)
|
||||
{
|
||||
if (FFlag::LuauIncrementalAutocompleteBugfixes)
|
||||
{
|
||||
return (stat->location.begin < cursorPos);
|
||||
}
|
||||
|
||||
return stat->location.begin < cursorPos && stat->location.begin.line < cursorPos.line;
|
||||
}
|
||||
|
||||
FragmentAutocompleteAncestryResult findAncestryForFragmentParse(AstStatBlock* root, const Position& cursorPos)
|
||||
{
|
||||
std::vector<AstNode*> ancestry = findAncestryAtPositionForAutocomplete(root, cursorPos);
|
||||
|
@ -168,12 +255,25 @@ FragmentAutocompleteAncestryResult findAncestryForFragmentParse(AstStatBlock* ro
|
|||
{
|
||||
if (stat->location.begin <= cursorPos)
|
||||
nearestStatement = stat;
|
||||
if (stat->location.begin < cursorPos && stat->location.begin.line < cursorPos.line)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!nearestStatement)
|
||||
nearestStatement = ancestry[0]->asStat();
|
||||
LUAU_ASSERT(nearestStatement);
|
||||
|
||||
for (AstNode* node : ancestry)
|
||||
{
|
||||
if (auto block = node->as<AstStatBlock>())
|
||||
{
|
||||
for (auto stat : block->body)
|
||||
{
|
||||
if (statIsBeforePos(stat, FFlag::LuauIncrementalAutocompleteBugfixes ? nearestStatement->location.begin : cursorPos))
|
||||
{
|
||||
// This statement precedes the current one
|
||||
if (auto loc = stat->as<AstStatLocal>())
|
||||
if (auto statLoc = stat->as<AstStatLocal>())
|
||||
{
|
||||
for (auto v : loc->vars)
|
||||
for (auto v : statLoc->vars)
|
||||
{
|
||||
localStack.push_back(v);
|
||||
localMap[v->name] = v;
|
||||
|
@ -206,11 +306,22 @@ FragmentAutocompleteAncestryResult findAncestryForFragmentParse(AstStatBlock* ro
|
|||
}
|
||||
}
|
||||
}
|
||||
if (FFlag::LuauIncrementalAutocompleteBugfixes)
|
||||
{
|
||||
if (auto exprFunc = node->as<AstExprFunction>())
|
||||
{
|
||||
if (exprFunc->location.contains(cursorPos))
|
||||
{
|
||||
for (auto v : exprFunc->args)
|
||||
{
|
||||
localStack.push_back(v);
|
||||
localMap[v->name] = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!nearestStatement)
|
||||
nearestStatement = ancestry[0]->asStat();
|
||||
LUAU_ASSERT(nearestStatement);
|
||||
return {std::move(localMap), std::move(localStack), std::move(ancestry), std::move(nearestStatement)};
|
||||
}
|
||||
|
||||
|
@ -296,16 +407,17 @@ ScopePtr findClosestScope(const ModulePtr& module, const AstStat* nearestStateme
|
|||
}
|
||||
|
||||
std::optional<FragmentParseResult> parseFragment(
|
||||
const SourceModule& srcModule,
|
||||
AstStatBlock* root,
|
||||
AstNameTable* names,
|
||||
std::string_view src,
|
||||
const Position& cursorPos,
|
||||
std::optional<Position> fragmentEndPosition
|
||||
)
|
||||
{
|
||||
FragmentAutocompleteAncestryResult result = findAncestryForFragmentParse(srcModule.root, cursorPos);
|
||||
FragmentAutocompleteAncestryResult result = findAncestryForFragmentParse(root, cursorPos);
|
||||
AstStat* nearestStatement = result.nearestStatement;
|
||||
|
||||
const Location& rootSpan = srcModule.root->location;
|
||||
const Location& rootSpan = root->location;
|
||||
// Did we append vs did we insert inline
|
||||
bool appended = cursorPos >= rootSpan.end;
|
||||
// statement spans multiple lines
|
||||
|
@ -314,7 +426,7 @@ std::optional<FragmentParseResult> parseFragment(
|
|||
const Position endPos = fragmentEndPosition.value_or(cursorPos);
|
||||
|
||||
// We start by re-parsing everything (we'll refine this as we go)
|
||||
Position startPos = srcModule.root->location.begin;
|
||||
Position startPos = root->location.begin;
|
||||
|
||||
// If we added to the end of the sourceModule, use the end of the nearest location
|
||||
if (appended && multiline)
|
||||
|
@ -330,7 +442,6 @@ std::optional<FragmentParseResult> parseFragment(
|
|||
auto [offsetStart, parseLength] = getDocumentOffsets(src, startPos, endPos);
|
||||
const char* srcStart = src.data() + offsetStart;
|
||||
std::string_view dbg = src.substr(offsetStart, parseLength);
|
||||
const std::shared_ptr<AstNameTable>& nameTbl = srcModule.names;
|
||||
FragmentParseResult fragmentResult;
|
||||
fragmentResult.fragmentToParse = std::string(dbg.data(), parseLength);
|
||||
// For the duration of the incremental parse, we want to allow the name table to re-use duplicate names
|
||||
|
@ -341,7 +452,7 @@ std::optional<FragmentParseResult> parseFragment(
|
|||
opts.allowDeclarationSyntax = false;
|
||||
opts.captureComments = true;
|
||||
opts.parseFragment = FragmentParseResumeSettings{std::move(result.localMap), std::move(result.localStack), startPos};
|
||||
ParseResult p = Luau::Parser::parse(srcStart, parseLength, *nameTbl, *fragmentResult.alloc.get(), opts);
|
||||
ParseResult p = Luau::Parser::parse(srcStart, parseLength, *names, *fragmentResult.alloc, opts);
|
||||
// This means we threw a ParseError and we should decline to offer autocomplete here.
|
||||
if (p.root == nullptr)
|
||||
return std::nullopt;
|
||||
|
@ -362,7 +473,7 @@ std::optional<FragmentParseResult> parseFragment(
|
|||
return fragmentResult;
|
||||
}
|
||||
|
||||
ModulePtr cloneModule(CloneState& cloneState, const ModulePtr& source, std::unique_ptr<Allocator> alloc)
|
||||
ModulePtr cloneModule_DEPRECATED(CloneState& cloneState, const ModulePtr& source, std::unique_ptr<Allocator> alloc)
|
||||
{
|
||||
LUAU_TIMETRACE_SCOPE("Luau::cloneModule", "FragmentAutocomplete");
|
||||
freeze(source->internalTypes);
|
||||
|
@ -372,13 +483,38 @@ ModulePtr cloneModule(CloneState& cloneState, const ModulePtr& source, std::uniq
|
|||
incremental->humanReadableName = source->humanReadableName;
|
||||
incremental->allocator = std::move(alloc);
|
||||
// Clone types
|
||||
cloneModuleMap(incremental->internalTypes, cloneState, source->astTypes, incremental->astTypes);
|
||||
cloneModuleMap(incremental->internalTypes, cloneState, source->astTypePacks, incremental->astTypePacks);
|
||||
cloneModuleMap(incremental->internalTypes, cloneState, source->astExpectedTypes, incremental->astExpectedTypes);
|
||||
cloneModuleMap_DEPRECATED(incremental->internalTypes, cloneState, source->astTypes, incremental->astTypes);
|
||||
cloneModuleMap_DEPRECATED(incremental->internalTypes, cloneState, source->astTypePacks, incremental->astTypePacks);
|
||||
cloneModuleMap_DEPRECATED(incremental->internalTypes, cloneState, source->astExpectedTypes, incremental->astExpectedTypes);
|
||||
|
||||
cloneModuleMap(incremental->internalTypes, cloneState, source->astOverloadResolvedTypes, incremental->astOverloadResolvedTypes);
|
||||
cloneModuleMap_DEPRECATED(incremental->internalTypes, cloneState, source->astOverloadResolvedTypes, incremental->astOverloadResolvedTypes);
|
||||
|
||||
cloneModuleMap(incremental->internalTypes, cloneState, source->astForInNextTypes, incremental->astForInNextTypes);
|
||||
cloneModuleMap_DEPRECATED(incremental->internalTypes, cloneState, source->astForInNextTypes, incremental->astForInNextTypes);
|
||||
|
||||
copyModuleMap(incremental->astScopes, source->astScopes);
|
||||
|
||||
return incremental;
|
||||
}
|
||||
|
||||
ModulePtr cloneModule(CloneState& cloneState, const ModulePtr& source, std::unique_ptr<Allocator> alloc, Scope* freeTypeFreshScope)
|
||||
{
|
||||
LUAU_TIMETRACE_SCOPE("Luau::cloneModule", "FragmentAutocomplete");
|
||||
freeze(source->internalTypes);
|
||||
freeze(source->interfaceTypes);
|
||||
ModulePtr incremental = std::make_shared<Module>();
|
||||
incremental->name = source->name;
|
||||
incremental->humanReadableName = source->humanReadableName;
|
||||
incremental->allocator = std::move(alloc);
|
||||
// Clone types
|
||||
cloneModuleMap(incremental->internalTypes, cloneState, source->astTypes, incremental->astTypes, freeTypeFreshScope);
|
||||
cloneModuleMap(incremental->internalTypes, cloneState, source->astTypePacks, incremental->astTypePacks, freeTypeFreshScope);
|
||||
cloneModuleMap(incremental->internalTypes, cloneState, source->astExpectedTypes, incremental->astExpectedTypes, freeTypeFreshScope);
|
||||
|
||||
cloneModuleMap(
|
||||
incremental->internalTypes, cloneState, source->astOverloadResolvedTypes, incremental->astOverloadResolvedTypes, freeTypeFreshScope
|
||||
);
|
||||
|
||||
cloneModuleMap(incremental->internalTypes, cloneState, source->astForInNextTypes, incremental->astForInNextTypes, freeTypeFreshScope);
|
||||
|
||||
copyModuleMap(incremental->astScopes, source->astScopes);
|
||||
|
||||
|
@ -451,8 +587,15 @@ FragmentTypeCheckResult typecheckFragment_(
|
|||
freeze(stale->internalTypes);
|
||||
freeze(stale->interfaceTypes);
|
||||
CloneState cloneState{frontend.builtinTypes};
|
||||
ModulePtr incrementalModule =
|
||||
FFlag::LuauCloneIncrementalModule ? cloneModule(cloneState, stale, std::move(astAllocator)) : copyModule(stale, std::move(astAllocator));
|
||||
std::shared_ptr<Scope> freshChildOfNearestScope = std::make_shared<Scope>(closestScope);
|
||||
ModulePtr incrementalModule = nullptr;
|
||||
if (FFlag::LuauAllFreeTypesHaveScopes)
|
||||
incrementalModule = cloneModule(cloneState, stale, std::move(astAllocator), freshChildOfNearestScope.get());
|
||||
else if (FFlag::LuauCloneIncrementalModule)
|
||||
incrementalModule = cloneModule_DEPRECATED(cloneState, stale, std::move(astAllocator));
|
||||
else
|
||||
incrementalModule = copyModule(stale, std::move(astAllocator));
|
||||
|
||||
incrementalModule->checkedInNewSolver = true;
|
||||
unfreeze(incrementalModule->internalTypes);
|
||||
unfreeze(incrementalModule->interfaceTypes);
|
||||
|
@ -500,23 +643,32 @@ FragmentTypeCheckResult typecheckFragment_(
|
|||
NotNull{&dfg},
|
||||
{}
|
||||
};
|
||||
std::shared_ptr<Scope> freshChildOfNearestScope = nullptr;
|
||||
|
||||
if (FFlag::LuauCloneIncrementalModule)
|
||||
{
|
||||
freshChildOfNearestScope = std::make_shared<Scope>(closestScope);
|
||||
incrementalModule->scopes.emplace_back(root->location, freshChildOfNearestScope);
|
||||
cg.rootScope = freshChildOfNearestScope.get();
|
||||
|
||||
cloneAndSquashScopes(
|
||||
cloneState, closestScope.get(), stale, NotNull{&incrementalModule->internalTypes}, NotNull{&dfg}, root, freshChildOfNearestScope.get()
|
||||
);
|
||||
if (FFlag::LuauAllFreeTypesHaveScopes)
|
||||
cloneAndSquashScopes(
|
||||
cloneState, closestScope.get(), stale, NotNull{&incrementalModule->internalTypes}, NotNull{&dfg}, root, freshChildOfNearestScope.get()
|
||||
);
|
||||
else
|
||||
cloneAndSquashScopes_DEPRECATED(
|
||||
cloneState, closestScope.get(), stale, NotNull{&incrementalModule->internalTypes}, NotNull{&dfg}, root, freshChildOfNearestScope.get()
|
||||
);
|
||||
cg.visitFragmentRoot(freshChildOfNearestScope, root);
|
||||
|
||||
if (FFlag::LuauPersistConstraintGenerationScopes)
|
||||
{
|
||||
for (auto p : cg.scopes)
|
||||
incrementalModule->scopes.emplace_back(std::move(p));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Any additions to the scope must occur in a fresh scope
|
||||
cg.rootScope = stale->getModuleScope().get();
|
||||
freshChildOfNearestScope = std::make_shared<Scope>(closestScope);
|
||||
incrementalModule->scopes.emplace_back(root->location, freshChildOfNearestScope);
|
||||
mixedModeCompatibility(closestScope, freshChildOfNearestScope, stale, NotNull{&dfg}, root);
|
||||
// closest Scope -> children = { ...., freshChildOfNearestScope}
|
||||
|
@ -529,6 +681,15 @@ FragmentTypeCheckResult typecheckFragment_(
|
|||
closestScope->children.pop_back();
|
||||
}
|
||||
|
||||
if (FFlag::LuauAllFreeTypesHaveScopes)
|
||||
{
|
||||
if (Scope* sc = freshChildOfNearestScope.get())
|
||||
{
|
||||
if (!sc->interiorFreeTypes.has_value())
|
||||
sc->interiorFreeTypes.emplace();
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize the constraint solver and run it
|
||||
ConstraintSolver cs{
|
||||
NotNull{&normalizer},
|
||||
|
@ -586,13 +747,6 @@ std::pair<FragmentTypeCheckStatus, FragmentTypeCheckResult> typecheckFragment(
|
|||
return {FragmentTypeCheckStatus::SkipAutocomplete, {}};
|
||||
}
|
||||
|
||||
const SourceModule* sourceModule = frontend.getSourceModule(moduleName);
|
||||
if (!sourceModule)
|
||||
{
|
||||
LUAU_ASSERT(!"Expected Source Module for fragment typecheck");
|
||||
return {};
|
||||
}
|
||||
|
||||
FrontendModuleResolver& resolver = getModuleResolver(frontend, opts);
|
||||
ModulePtr module = resolver.getModule(moduleName);
|
||||
if (!module)
|
||||
|
@ -601,15 +755,30 @@ std::pair<FragmentTypeCheckStatus, FragmentTypeCheckResult> typecheckFragment(
|
|||
return {};
|
||||
}
|
||||
|
||||
if (FFlag::LuauIncrementalAutocompleteBugfixes)
|
||||
std::optional<FragmentParseResult> tryParse;
|
||||
if (FFlag::LuauModuleHoldsAstRoot)
|
||||
{
|
||||
if (sourceModule->allocator.get() != module->allocator.get())
|
||||
{
|
||||
return {FragmentTypeCheckStatus::SkipAutocomplete, {}};
|
||||
}
|
||||
tryParse = parseFragment(module->root, module->names.get(), src, cursorPos, fragmentEndPosition);
|
||||
}
|
||||
else
|
||||
{
|
||||
const SourceModule* sourceModule = frontend.getSourceModule(moduleName);
|
||||
if (!sourceModule)
|
||||
{
|
||||
LUAU_ASSERT(!"Expected Source Module for fragment typecheck");
|
||||
return {};
|
||||
}
|
||||
|
||||
auto tryParse = parseFragment(*sourceModule, src, cursorPos, fragmentEndPosition);
|
||||
if (FFlag::LuauIncrementalAutocompleteBugfixes)
|
||||
{
|
||||
if (sourceModule->allocator.get() != module->allocator.get())
|
||||
{
|
||||
return {FragmentTypeCheckStatus::SkipAutocomplete, {}};
|
||||
}
|
||||
}
|
||||
|
||||
tryParse = parseFragment(sourceModule->root, sourceModule->names.get(), src, cursorPos, fragmentEndPosition);
|
||||
}
|
||||
|
||||
if (!tryParse)
|
||||
return {FragmentTypeCheckStatus::SkipAutocomplete, {}};
|
||||
|
@ -635,17 +804,16 @@ FragmentAutocompleteStatusResult tryFragmentAutocomplete(
|
|||
StringCompletionCallback stringCompletionCB
|
||||
)
|
||||
{
|
||||
if (FFlag::LuauBetterCursorInCommentDetection)
|
||||
{
|
||||
if (isWithinComment(context.freshParse.commentLocations, cursorPosition))
|
||||
return {FragmentAutocompleteStatus::Success, std::nullopt};
|
||||
}
|
||||
// TODO: we should calculate fragmentEnd position here, by using context.newAstRoot and cursorPosition
|
||||
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
|
||||
);
|
||||
return {FragmentAutocompleteStatus::Success, std::move(fragmentAutocomplete)};
|
||||
}
|
||||
|
@ -671,16 +839,19 @@ FragmentAutocompleteResult fragmentAutocomplete(
|
|||
LUAU_TIMETRACE_SCOPE("Luau::fragmentAutocomplete", "FragmentAutocomplete");
|
||||
LUAU_TIMETRACE_ARGUMENT("name", moduleName.c_str());
|
||||
|
||||
const SourceModule* sourceModule = frontend.getSourceModule(moduleName);
|
||||
if (!sourceModule)
|
||||
if (!FFlag::LuauModuleHoldsAstRoot)
|
||||
{
|
||||
LUAU_ASSERT(!"Expected Source Module for fragment typecheck");
|
||||
return {};
|
||||
}
|
||||
const SourceModule* sourceModule = frontend.getSourceModule(moduleName);
|
||||
if (!sourceModule)
|
||||
{
|
||||
LUAU_ASSERT(!"Expected Source Module for fragment typecheck");
|
||||
return {};
|
||||
}
|
||||
|
||||
// If the cursor is within a comment in the stale source module we should avoid providing a recommendation
|
||||
if (isWithinComment(*sourceModule, fragmentEndPosition.value_or(cursorPosition)))
|
||||
return {};
|
||||
// If the cursor is within a comment in the stale source module we should avoid providing a recommendation
|
||||
if (isWithinComment(*sourceModule, fragmentEndPosition.value_or(cursorPosition)))
|
||||
return {};
|
||||
}
|
||||
|
||||
auto [tcStatus, tcResult] = typecheckFragment(frontend, moduleName, cursorPosition, opts, src, fragmentEndPosition);
|
||||
if (tcStatus == FragmentTypeCheckStatus::SkipAutocomplete)
|
||||
|
|
|
@ -47,10 +47,12 @@ LUAU_FASTFLAGVARIABLE(DebugLuauForceStrictMode)
|
|||
LUAU_FASTFLAGVARIABLE(DebugLuauForceNonStrictMode)
|
||||
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauRunCustomModuleChecks, false)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauModuleHoldsAstRoot)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauBetterReverseDependencyTracking)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFixMultithreadTypecheck)
|
||||
|
||||
LUAU_FASTFLAG(StudioReportLuauAny2)
|
||||
LUAU_FASTFLAGVARIABLE(LuauStoreSolverTypeOnModule)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauSelectivelyRetainDFGArena)
|
||||
|
||||
|
@ -82,6 +84,20 @@ struct BuildQueueItem
|
|||
Frontend::Stats stats;
|
||||
};
|
||||
|
||||
struct BuildQueueWorkState
|
||||
{
|
||||
std::function<void(std::function<void()> task)> executeTask;
|
||||
|
||||
std::vector<BuildQueueItem> buildQueueItems;
|
||||
|
||||
std::mutex mtx;
|
||||
std::condition_variable cv;
|
||||
std::vector<size_t> readyQueueItems;
|
||||
|
||||
size_t processing = 0;
|
||||
size_t remaining = 0;
|
||||
};
|
||||
|
||||
std::optional<Mode> parseMode(const std::vector<HotComment>& hotcomments)
|
||||
{
|
||||
for (const HotComment& hc : hotcomments)
|
||||
|
@ -481,6 +497,203 @@ std::vector<ModuleName> Frontend::checkQueuedModules(
|
|||
std::function<bool(size_t done, size_t total)> progress
|
||||
)
|
||||
{
|
||||
if (!FFlag::LuauFixMultithreadTypecheck)
|
||||
{
|
||||
return checkQueuedModules_DEPRECATED(optionOverride, executeTask, progress);
|
||||
}
|
||||
|
||||
FrontendOptions frontendOptions = optionOverride.value_or(options);
|
||||
if (FFlag::LuauSolverV2)
|
||||
frontendOptions.forAutocomplete = false;
|
||||
|
||||
// By taking data into locals, we make sure queue is cleared at the end, even if an ICE or a different exception is thrown
|
||||
std::vector<ModuleName> currModuleQueue;
|
||||
std::swap(currModuleQueue, moduleQueue);
|
||||
|
||||
DenseHashSet<Luau::ModuleName> seen{{}};
|
||||
|
||||
std::shared_ptr<BuildQueueWorkState> state = std::make_shared<BuildQueueWorkState>();
|
||||
|
||||
for (const ModuleName& name : currModuleQueue)
|
||||
{
|
||||
if (seen.contains(name))
|
||||
continue;
|
||||
|
||||
if (!isDirty(name, frontendOptions.forAutocomplete))
|
||||
{
|
||||
seen.insert(name);
|
||||
continue;
|
||||
}
|
||||
|
||||
std::vector<ModuleName> queue;
|
||||
bool cycleDetected = parseGraph(
|
||||
queue,
|
||||
name,
|
||||
frontendOptions.forAutocomplete,
|
||||
[&seen](const ModuleName& name)
|
||||
{
|
||||
return seen.contains(name);
|
||||
}
|
||||
);
|
||||
|
||||
addBuildQueueItems(state->buildQueueItems, queue, cycleDetected, seen, frontendOptions);
|
||||
}
|
||||
|
||||
if (state->buildQueueItems.empty())
|
||||
return {};
|
||||
|
||||
// We need a mapping from modules to build queue slots
|
||||
std::unordered_map<ModuleName, size_t> moduleNameToQueue;
|
||||
|
||||
for (size_t i = 0; i < state->buildQueueItems.size(); i++)
|
||||
{
|
||||
BuildQueueItem& item = state->buildQueueItems[i];
|
||||
moduleNameToQueue[item.name] = i;
|
||||
}
|
||||
|
||||
// Default task execution is single-threaded and immediate
|
||||
if (!executeTask)
|
||||
{
|
||||
executeTask = [](std::function<void()> task)
|
||||
{
|
||||
task();
|
||||
};
|
||||
}
|
||||
|
||||
state->executeTask = executeTask;
|
||||
state->remaining = state->buildQueueItems.size();
|
||||
|
||||
// Record dependencies between modules
|
||||
for (size_t i = 0; i < state->buildQueueItems.size(); i++)
|
||||
{
|
||||
BuildQueueItem& item = state->buildQueueItems[i];
|
||||
|
||||
for (const ModuleName& dep : item.sourceNode->requireSet)
|
||||
{
|
||||
if (auto it = sourceNodes.find(dep); it != sourceNodes.end())
|
||||
{
|
||||
if (it->second->hasDirtyModule(frontendOptions.forAutocomplete))
|
||||
{
|
||||
item.dirtyDependencies++;
|
||||
|
||||
state->buildQueueItems[moduleNameToQueue[dep]].reverseDeps.push_back(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// In the first pass, check all modules with no pending dependencies
|
||||
for (size_t i = 0; i < state->buildQueueItems.size(); i++)
|
||||
{
|
||||
if (state->buildQueueItems[i].dirtyDependencies == 0)
|
||||
sendQueueItemTask(state, i);
|
||||
}
|
||||
|
||||
// If not a single item was found, a cycle in the graph was hit
|
||||
if (state->processing == 0)
|
||||
sendQueueCycleItemTask(state);
|
||||
|
||||
std::vector<size_t> nextItems;
|
||||
std::optional<size_t> itemWithException;
|
||||
bool cancelled = false;
|
||||
|
||||
while (state->remaining != 0)
|
||||
{
|
||||
{
|
||||
std::unique_lock guard(state->mtx);
|
||||
|
||||
// If nothing is ready yet, wait
|
||||
state->cv.wait(
|
||||
guard,
|
||||
[state]
|
||||
{
|
||||
return !state->readyQueueItems.empty();
|
||||
}
|
||||
);
|
||||
|
||||
// Handle checked items
|
||||
for (size_t i : state->readyQueueItems)
|
||||
{
|
||||
const BuildQueueItem& item = state->buildQueueItems[i];
|
||||
|
||||
// If exception was thrown, stop adding new items and wait for processing items to complete
|
||||
if (item.exception)
|
||||
itemWithException = i;
|
||||
|
||||
if (item.module && item.module->cancelled)
|
||||
cancelled = true;
|
||||
|
||||
if (itemWithException || cancelled)
|
||||
break;
|
||||
|
||||
recordItemResult(item);
|
||||
|
||||
// Notify items that were waiting for this dependency
|
||||
for (size_t reverseDep : item.reverseDeps)
|
||||
{
|
||||
BuildQueueItem& reverseDepItem = state->buildQueueItems[reverseDep];
|
||||
|
||||
LUAU_ASSERT(reverseDepItem.dirtyDependencies != 0);
|
||||
reverseDepItem.dirtyDependencies--;
|
||||
|
||||
// In case of a module cycle earlier, check if unlocked an item that was already processed
|
||||
if (!reverseDepItem.processing && reverseDepItem.dirtyDependencies == 0)
|
||||
nextItems.push_back(reverseDep);
|
||||
}
|
||||
}
|
||||
|
||||
LUAU_ASSERT(state->processing >= state->readyQueueItems.size());
|
||||
state->processing -= state->readyQueueItems.size();
|
||||
|
||||
LUAU_ASSERT(state->remaining >= state->readyQueueItems.size());
|
||||
state->remaining -= state->readyQueueItems.size();
|
||||
state->readyQueueItems.clear();
|
||||
}
|
||||
|
||||
if (progress)
|
||||
{
|
||||
if (!progress(state->buildQueueItems.size() - state->remaining, state->buildQueueItems.size()))
|
||||
cancelled = true;
|
||||
}
|
||||
|
||||
// Items cannot be submitted while holding the lock
|
||||
for (size_t i : nextItems)
|
||||
sendQueueItemTask(state, i);
|
||||
nextItems.clear();
|
||||
|
||||
if (state->processing == 0)
|
||||
{
|
||||
// Typechecking might have been cancelled by user, don't return partial results
|
||||
if (cancelled)
|
||||
return {};
|
||||
|
||||
// We might have stopped because of a pending exception
|
||||
if (itemWithException)
|
||||
recordItemResult(state->buildQueueItems[*itemWithException]);
|
||||
}
|
||||
|
||||
// If we aren't done, but don't have anything processing, we hit a cycle
|
||||
if (state->remaining != 0 && state->processing == 0)
|
||||
sendQueueCycleItemTask(state);
|
||||
}
|
||||
|
||||
std::vector<ModuleName> checkedModules;
|
||||
checkedModules.reserve(state->buildQueueItems.size());
|
||||
|
||||
for (size_t i = 0; i < state->buildQueueItems.size(); i++)
|
||||
checkedModules.push_back(std::move(state->buildQueueItems[i].name));
|
||||
|
||||
return checkedModules;
|
||||
}
|
||||
|
||||
std::vector<ModuleName> Frontend::checkQueuedModules_DEPRECATED(
|
||||
std::optional<FrontendOptions> optionOverride,
|
||||
std::function<void(std::function<void()> task)> executeTask,
|
||||
std::function<bool(size_t done, size_t total)> progress
|
||||
)
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::LuauFixMultithreadTypecheck);
|
||||
|
||||
FrontendOptions frontendOptions = optionOverride.value_or(options);
|
||||
if (FFlag::LuauSolverV2)
|
||||
frontendOptions.forAutocomplete = false;
|
||||
|
@ -1170,6 +1383,58 @@ void Frontend::recordItemResult(const BuildQueueItem& item)
|
|||
stats.filesNonstrict += item.stats.filesNonstrict;
|
||||
}
|
||||
|
||||
void Frontend::performQueueItemTask(std::shared_ptr<BuildQueueWorkState> state, size_t itemPos)
|
||||
{
|
||||
BuildQueueItem& item = state->buildQueueItems[itemPos];
|
||||
|
||||
try
|
||||
{
|
||||
checkBuildQueueItem(item);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
item.exception = std::current_exception();
|
||||
}
|
||||
|
||||
{
|
||||
std::unique_lock guard(state->mtx);
|
||||
state->readyQueueItems.push_back(itemPos);
|
||||
}
|
||||
|
||||
state->cv.notify_one();
|
||||
}
|
||||
|
||||
void Frontend::sendQueueItemTask(std::shared_ptr<BuildQueueWorkState> state, size_t itemPos)
|
||||
{
|
||||
BuildQueueItem& item = state->buildQueueItems[itemPos];
|
||||
|
||||
LUAU_ASSERT(!item.processing);
|
||||
item.processing = true;
|
||||
|
||||
state->processing++;
|
||||
|
||||
state->executeTask(
|
||||
[this, state, itemPos]()
|
||||
{
|
||||
performQueueItemTask(state, itemPos);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void Frontend::sendQueueCycleItemTask(std::shared_ptr<BuildQueueWorkState> state)
|
||||
{
|
||||
for (size_t i = 0; i < state->buildQueueItems.size(); i++)
|
||||
{
|
||||
BuildQueueItem& item = state->buildQueueItems[i];
|
||||
|
||||
if (!item.processing)
|
||||
{
|
||||
sendQueueItemTask(state, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ScopePtr Frontend::getModuleEnvironment(const SourceModule& module, const Config& config, bool forAutocomplete) const
|
||||
{
|
||||
ScopePtr result;
|
||||
|
@ -1422,8 +1687,7 @@ ModulePtr check(
|
|||
LUAU_TIMETRACE_ARGUMENT("name", sourceModule.humanReadableName.c_str());
|
||||
|
||||
ModulePtr result = std::make_shared<Module>();
|
||||
if (FFlag::LuauStoreSolverTypeOnModule)
|
||||
result->checkedInNewSolver = true;
|
||||
result->checkedInNewSolver = true;
|
||||
result->name = sourceModule.name;
|
||||
result->humanReadableName = sourceModule.humanReadableName;
|
||||
result->mode = mode;
|
||||
|
@ -1431,6 +1695,8 @@ ModulePtr check(
|
|||
result->interfaceTypes.owningModule = result.get();
|
||||
result->allocator = sourceModule.allocator;
|
||||
result->names = sourceModule.names;
|
||||
if (FFlag::LuauModuleHoldsAstRoot)
|
||||
result->root = sourceModule.root;
|
||||
|
||||
iceHandler->moduleName = sourceModule.name;
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ LUAU_FASTFLAGVARIABLE(DebugLuauCheckNormalizeInvariant)
|
|||
LUAU_FASTINTVARIABLE(LuauNormalizeCacheLimit, 100000)
|
||||
LUAU_FASTINTVARIABLE(LuauNormalizeIntersectionLimit, 200)
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNormalizeNegationFix)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFixInfiniteRecursionInNormalization)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFixNormalizedIntersectionOfNegatedClass)
|
||||
|
||||
|
@ -3305,7 +3306,12 @@ NormalizationResult Normalizer::intersectNormalWithTy(
|
|||
return NormalizationResult::True;
|
||||
}
|
||||
else if (auto nt = get<NegationType>(t))
|
||||
{
|
||||
if (FFlag::LuauNormalizeNegationFix)
|
||||
here.tyvars = std::move(tyvars);
|
||||
|
||||
return intersectNormalWithTy(here, nt->ty, seenTablePropPairs, seenSetTypes);
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO negated unions, intersections, table, and function.
|
||||
|
|
|
@ -54,7 +54,15 @@ RefinementId RefinementArena::proposition(const RefinementKey* key, TypeId discr
|
|||
if (!key)
|
||||
return nullptr;
|
||||
|
||||
return NotNull{allocator.allocate(Proposition{key, discriminantTy})};
|
||||
return NotNull{allocator.allocate(Proposition{key, discriminantTy, false})};
|
||||
}
|
||||
|
||||
RefinementId RefinementArena::implicitProposition(const RefinementKey* key, TypeId discriminantTy)
|
||||
{
|
||||
if (!key)
|
||||
return nullptr;
|
||||
|
||||
return NotNull{allocator.allocate(Proposition{key, discriminantTy, true})};
|
||||
}
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
#include "Luau/Common.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSymbolEquality)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -15,10 +14,8 @@ bool Symbol::operator==(const Symbol& rhs) const
|
|||
return local == rhs.local;
|
||||
else if (global.value)
|
||||
return rhs.global.value && global == rhs.global.value; // Subtlety: AstName::operator==(const char*) uses strcmp, not pointer identity.
|
||||
else if (FFlag::LuauSolverV2 || FFlag::LuauSymbolEquality)
|
||||
return !rhs.local && !rhs.global.value; // Reflexivity: we already know `this` Symbol is empty, so check that rhs is.
|
||||
else
|
||||
return false;
|
||||
return !rhs.local && !rhs.global.value; // Reflexivity: we already know `this` Symbol is empty, so check that rhs is.
|
||||
}
|
||||
|
||||
std::string toString(const Symbol& name)
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
|
||||
#include "Luau/TableLiteralInference.h"
|
||||
|
||||
#include "Luau/Ast.h"
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/Normalize.h"
|
||||
#include "Luau/Simplify.h"
|
||||
#include "Luau/Subtyping.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/TypeArena.h"
|
||||
|
@ -11,6 +15,7 @@
|
|||
|
||||
LUAU_FASTFLAGVARIABLE(LuauDontInPlaceMutateTableType)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAllowNonSharedTableTypesInLiteral)
|
||||
LUAU_FASTFLAGVARIABLE(LuauBidirectionalInferenceUpcast)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -112,6 +117,7 @@ TypeId matchLiteralType(
|
|||
NotNull<BuiltinTypes> builtinTypes,
|
||||
NotNull<TypeArena> arena,
|
||||
NotNull<Unifier2> unifier,
|
||||
NotNull<Subtyping> subtyping,
|
||||
TypeId expectedType,
|
||||
TypeId exprType,
|
||||
const AstExpr* expr,
|
||||
|
@ -133,7 +139,17 @@ TypeId matchLiteralType(
|
|||
* by the expected type.
|
||||
*/
|
||||
if (!isLiteral(expr))
|
||||
return exprType;
|
||||
{
|
||||
if (FFlag::LuauBidirectionalInferenceUpcast)
|
||||
{
|
||||
auto result = subtyping->isSubtype(/*subTy=*/exprType, /*superTy=*/expectedType, unifier->scope);
|
||||
return result.isSubtype
|
||||
? expectedType
|
||||
: exprType;
|
||||
}
|
||||
else
|
||||
return exprType;
|
||||
}
|
||||
|
||||
expectedType = follow(expectedType);
|
||||
exprType = follow(exprType);
|
||||
|
@ -210,7 +226,16 @@ TypeId matchLiteralType(
|
|||
return exprType;
|
||||
}
|
||||
|
||||
// TODO: lambdas
|
||||
|
||||
if (FFlag::LuauBidirectionalInferenceUpcast && expr->is<AstExprFunction>())
|
||||
{
|
||||
// TODO: Push argument / return types into the lambda. For now, just do
|
||||
// the non-literal thing: check for a subtype and upcast if valid.
|
||||
auto result = subtyping->isSubtype(/*subTy=*/exprType, /*superTy=*/expectedType, unifier->scope);
|
||||
return result.isSubtype
|
||||
? expectedType
|
||||
: exprType;
|
||||
}
|
||||
|
||||
if (auto exprTable = expr->as<AstExprTable>())
|
||||
{
|
||||
|
@ -229,7 +254,7 @@ TypeId matchLiteralType(
|
|||
|
||||
if (tt)
|
||||
{
|
||||
TypeId res = matchLiteralType(astTypes, astExpectedTypes, builtinTypes, arena, unifier, *tt, exprType, expr, toBlock);
|
||||
TypeId res = matchLiteralType(astTypes, astExpectedTypes, builtinTypes, arena, unifier, subtyping, *tt, exprType, expr, toBlock);
|
||||
|
||||
parts.push_back(res);
|
||||
return arena->addType(UnionType{std::move(parts)});
|
||||
|
@ -285,6 +310,7 @@ TypeId matchLiteralType(
|
|||
builtinTypes,
|
||||
arena,
|
||||
unifier,
|
||||
subtyping,
|
||||
expectedTableTy->indexer->indexResultType,
|
||||
propTy,
|
||||
item.value,
|
||||
|
@ -300,6 +326,7 @@ TypeId matchLiteralType(
|
|||
keysToDelete.insert(item.key->as<AstExprConstantString>());
|
||||
else
|
||||
tableTy->props.erase(keyStr);
|
||||
|
||||
}
|
||||
|
||||
// If it's just an extra property and the expected type
|
||||
|
@ -323,21 +350,21 @@ TypeId matchLiteralType(
|
|||
if (expectedProp.isShared())
|
||||
{
|
||||
matchedType =
|
||||
matchLiteralType(astTypes, astExpectedTypes, builtinTypes, arena, unifier, *expectedReadTy, propTy, item.value, toBlock);
|
||||
matchLiteralType(astTypes, astExpectedTypes, builtinTypes, arena, unifier, subtyping, *expectedReadTy, propTy, item.value, toBlock);
|
||||
prop.readTy = matchedType;
|
||||
prop.writeTy = matchedType;
|
||||
}
|
||||
else if (expectedReadTy)
|
||||
{
|
||||
matchedType =
|
||||
matchLiteralType(astTypes, astExpectedTypes, builtinTypes, arena, unifier, *expectedReadTy, propTy, item.value, toBlock);
|
||||
matchLiteralType(astTypes, astExpectedTypes, builtinTypes, arena, unifier, subtyping, *expectedReadTy, propTy, item.value, toBlock);
|
||||
prop.readTy = matchedType;
|
||||
prop.writeTy.reset();
|
||||
}
|
||||
else if (expectedWriteTy)
|
||||
{
|
||||
matchedType =
|
||||
matchLiteralType(astTypes, astExpectedTypes, builtinTypes, arena, unifier, *expectedWriteTy, propTy, item.value, toBlock);
|
||||
matchLiteralType(astTypes, astExpectedTypes, builtinTypes, arena, unifier, subtyping, *expectedWriteTy, propTy, item.value, toBlock);
|
||||
prop.readTy.reset();
|
||||
prop.writeTy = matchedType;
|
||||
}
|
||||
|
@ -371,6 +398,7 @@ TypeId matchLiteralType(
|
|||
builtinTypes,
|
||||
arena,
|
||||
unifier,
|
||||
subtyping,
|
||||
expectedTableTy->indexer->indexResultType,
|
||||
*propTy,
|
||||
item.value,
|
||||
|
|
|
@ -32,10 +32,11 @@ LUAU_FASTINTVARIABLE(LuauVisitRecursionLimit, 500)
|
|||
LUAU_FASTFLAG(LuauKnowsTheDataModel3)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification)
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
LUAU_FASTFLAGVARIABLE(LuauOldSolverCreatesChildScopePointers)
|
||||
LUAU_FASTFLAG(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
|
||||
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
|
||||
|
||||
LUAU_FASTFLAG(LuauModuleHoldsAstRoot)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
@ -255,6 +256,8 @@ ModulePtr TypeChecker::checkWithoutRecursionCheck(const SourceModule& module, Mo
|
|||
currentModule->type = module.type;
|
||||
currentModule->allocator = module.allocator;
|
||||
currentModule->names = module.names;
|
||||
if (FFlag::LuauModuleHoldsAstRoot)
|
||||
currentModule->root = module.root;
|
||||
|
||||
iceHandler->moduleName = module.name;
|
||||
normalizer.arena = ¤tModule->internalTypes;
|
||||
|
@ -5212,12 +5215,9 @@ LUAU_NOINLINE void TypeChecker::reportErrorCodeTooComplex(const Location& locati
|
|||
ScopePtr TypeChecker::childFunctionScope(const ScopePtr& parent, const Location& location, int subLevel)
|
||||
{
|
||||
ScopePtr scope = std::make_shared<Scope>(parent, subLevel);
|
||||
if (FFlag::LuauOldSolverCreatesChildScopePointers)
|
||||
{
|
||||
scope->location = location;
|
||||
scope->returnType = parent->returnType;
|
||||
parent->children.emplace_back(scope.get());
|
||||
}
|
||||
scope->location = location;
|
||||
scope->returnType = parent->returnType;
|
||||
parent->children.emplace_back(scope.get());
|
||||
|
||||
currentModule->scopes.push_back(std::make_pair(location, scope));
|
||||
return scope;
|
||||
|
@ -5229,12 +5229,9 @@ ScopePtr TypeChecker::childScope(const ScopePtr& parent, const Location& locatio
|
|||
ScopePtr scope = std::make_shared<Scope>(parent);
|
||||
scope->level = parent->level;
|
||||
scope->varargPack = parent->varargPack;
|
||||
if (FFlag::LuauOldSolverCreatesChildScopePointers)
|
||||
{
|
||||
scope->location = location;
|
||||
scope->returnType = parent->returnType;
|
||||
parent->children.emplace_back(scope.get());
|
||||
}
|
||||
scope->location = location;
|
||||
scope->returnType = parent->returnType;
|
||||
parent->children.emplace_back(scope.get());
|
||||
|
||||
currentModule->scopes.push_back(std::make_pair(location, scope));
|
||||
return scope;
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include <optional>
|
||||
|
||||
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUnifyMetatableWithAny)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -235,6 +236,10 @@ bool Unifier2::unify(TypeId subTy, TypeId superTy)
|
|||
auto superMetatable = get<MetatableType>(superTy);
|
||||
if (subMetatable && superMetatable)
|
||||
return unify(subMetatable, superMetatable);
|
||||
else if (FFlag::LuauUnifyMetatableWithAny && subMetatable && superAny)
|
||||
return unify(subMetatable, superAny);
|
||||
else if (FFlag::LuauUnifyMetatableWithAny && subAny && superMetatable)
|
||||
return unify(subAny, superMetatable);
|
||||
else if (subMetatable) // if we only have one metatable, unify with the inner table
|
||||
return unify(subMetatable->table, superTy);
|
||||
else if (superMetatable) // if we only have one metatable, unify with the inner table
|
||||
|
@ -524,6 +529,16 @@ bool Unifier2::unify(const TableType* subTable, const AnyType* superAny)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool Unifier2::unify(const MetatableType* subMetatable, const AnyType*)
|
||||
{
|
||||
return unify(subMetatable->metatable, builtinTypes->anyType) && unify(subMetatable->table, builtinTypes->anyType);
|
||||
}
|
||||
|
||||
bool Unifier2::unify(const AnyType*, const MetatableType* superMetatable)
|
||||
{
|
||||
return unify(builtinTypes->anyType, superMetatable->metatable) && unify(builtinTypes->anyType, superMetatable->table);
|
||||
}
|
||||
|
||||
// FIXME? This should probably return an ErrorVec or an optional<TypeError>
|
||||
// rather than a boolean to signal an occurs check failure.
|
||||
bool Unifier2::unify(TypePackId subTp, TypePackId superTp)
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
|
||||
#include <limits.h>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LexerResumesFromPosition2)
|
||||
LUAU_FASTFLAGVARIABLE(LexerFixInterpStringStart)
|
||||
|
||||
namespace Luau
|
||||
|
@ -342,12 +341,9 @@ Lexer::Lexer(const char* buffer, size_t bufferSize, AstNameTable& names, Positio
|
|||
: buffer(buffer)
|
||||
, bufferSize(bufferSize)
|
||||
, offset(0)
|
||||
, line(FFlag::LexerResumesFromPosition2 ? startPosition.line : 0)
|
||||
, lineOffset(FFlag::LexerResumesFromPosition2 ? 0u - startPosition.column : 0)
|
||||
, lexeme(
|
||||
(FFlag::LexerResumesFromPosition2 ? Location(Position(startPosition.line, startPosition.column), 0) : Location(Position(0, 0), 0)),
|
||||
Lexeme::Eof
|
||||
)
|
||||
, line(startPosition.line)
|
||||
, lineOffset(0u - startPosition.column)
|
||||
, lexeme((Location(Position(startPosition.line, startPosition.column), 0)), Lexeme::Eof)
|
||||
, names(names)
|
||||
, skipComments(false)
|
||||
, readNames(true)
|
||||
|
|
|
@ -31,7 +31,7 @@ static void setLuauFlags(bool state)
|
|||
void setLuauFlagsDefault()
|
||||
{
|
||||
for (Luau::FValue<bool>* flag = Luau::FValue<bool>::list; flag; flag = flag->next)
|
||||
if (strncmp(flag->name, "Luau", 4) == 0 && !Luau::isFlagExperimental(flag->name))
|
||||
if (strncmp(flag->name, "Luau", 4) == 0 && !Luau::isAnalysisFlagExperimental(flag->name))
|
||||
flag->value = true;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,10 +6,11 @@
|
|||
namespace Luau
|
||||
{
|
||||
|
||||
inline bool isFlagExperimental(const char* flag)
|
||||
inline bool isAnalysisFlagExperimental(const char* flag)
|
||||
{
|
||||
// Flags in this list are disabled by default in various command-line tools. They may have behavior that is not fully final,
|
||||
// or critical bugs that are found after the code has been submitted.
|
||||
// or critical bugs that are found after the code has been submitted. This list is intended _only_ for flags that affect
|
||||
// Luau's type checking. Flags that may change runtime behavior (e.g.: parser or VM flags) are not appropriate for this list.
|
||||
static const char* const kList[] = {
|
||||
"LuauInstantiateInSubtyping", // requires some fixes to lua-apps code
|
||||
"LuauFixIndexerSubtypingOrdering", // requires some small fixes to lua-apps code since this fixes a false negative
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -14,10 +14,11 @@
|
|||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2);
|
||||
LUAU_FASTFLAG(DebugLuauFreezeArena);
|
||||
LUAU_FASTFLAG(DebugLuauMagicTypes);
|
||||
LUAU_FASTFLAG(DebugLuauFreezeArena)
|
||||
LUAU_FASTFLAG(DebugLuauMagicTypes)
|
||||
LUAU_FASTFLAG(LuauSelectivelyRetainDFGArena)
|
||||
LUAU_FASTFLAG(LuauBetterReverseDependencyTracking);
|
||||
LUAU_FASTFLAG(LuauBetterReverseDependencyTracking)
|
||||
LUAU_FASTFLAG(LuauModuleHoldsAstRoot)
|
||||
|
||||
namespace
|
||||
{
|
||||
|
@ -1542,6 +1543,23 @@ TEST_CASE_FIXTURE(FrontendFixture, "check_module_references_allocator")
|
|||
CHECK_EQ(module->names.get(), source->names.get());
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(FrontendFixture, "check_module_references_correct_ast_root")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauModuleHoldsAstRoot, true};
|
||||
fileResolver.source["game/workspace/MyScript"] = R"(
|
||||
print("Hello World")
|
||||
)";
|
||||
|
||||
frontend.check("game/workspace/MyScript");
|
||||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/workspace/MyScript");
|
||||
SourceModule* source = frontend.getSourceModule("game/workspace/MyScript");
|
||||
CHECK(module);
|
||||
CHECK(source);
|
||||
|
||||
CHECK_EQ(module->root, source->root);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(FrontendFixture, "dfg_data_cleared_on_retain_type_graphs_unset")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauSelectivelyRetainDFGArena, true}};
|
||||
|
|
|
@ -14,7 +14,6 @@ using namespace Luau;
|
|||
LUAU_FASTFLAG(LuauSolverV2);
|
||||
LUAU_FASTFLAG(DebugLuauFreezeArena);
|
||||
LUAU_FASTINT(LuauTypeCloneIterationLimit);
|
||||
LUAU_FASTFLAG(LuauOldSolverCreatesChildScopePointers)
|
||||
TEST_SUITE_BEGIN("ModuleTests");
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "is_within_comment")
|
||||
|
@ -542,7 +541,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "clone_a_bound_typepack_to_a_persistent_typep
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "old_solver_correctly_populates_child_scopes")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauOldSolverCreatesChildScopePointers, true};
|
||||
check(R"(
|
||||
--!strict
|
||||
if true then
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||
LUAU_FASTFLAG(LuauFixNormalizedIntersectionOfNegatedClass)
|
||||
LUAU_FASTFLAG(LuauNormalizeNegationFix)
|
||||
using namespace Luau;
|
||||
|
||||
namespace
|
||||
|
@ -1029,6 +1030,26 @@ TEST_CASE_FIXTURE(NormalizeFixture, "truthy_table_property_and_optional_table_wi
|
|||
CHECK("{ x: number }" == toString(ty));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "free_type_and_not_truthy")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{FFlag::LuauSolverV2, true}, // Only because it affects the stringification of free types
|
||||
{FFlag::LuauNormalizeNegationFix, true},
|
||||
};
|
||||
|
||||
TypeId freeTy = arena.freshType(builtinTypes, &globalScope);
|
||||
TypeId notTruthy = arena.addType(NegationType{builtinTypes->truthyType}); // ~~(false?)
|
||||
|
||||
TypeId intersectionTy = arena.addType(IntersectionType{{freeTy, notTruthy}}); // 'a & ~~(false?)
|
||||
|
||||
auto norm = normalizer.normalize(intersectionTy);
|
||||
REQUIRE(norm);
|
||||
|
||||
TypeId result = normalizer.typeFromNormal(*norm);
|
||||
|
||||
CHECK("'a & (false?)" == toString(result));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "normalizer_should_be_able_to_detect_cyclic_tables_and_not_stack_overflow")
|
||||
{
|
||||
if (!FFlag::LuauSolverV2)
|
||||
|
|
|
@ -12,6 +12,8 @@ LUAU_FASTFLAG(DebugLuauEqSatSimplification)
|
|||
LUAU_FASTFLAG(LuauGeneralizationRemoveRecursiveUpperBound2)
|
||||
LUAU_FASTFLAG(LuauIntersectNotNil)
|
||||
LUAU_FASTFLAG(LuauSkipNoRefineDuringRefinement)
|
||||
LUAU_FASTFLAG(LuauFunctionCallsAreNotNilable)
|
||||
LUAU_FASTFLAG(LuauDoNotLeakNilInRefinement)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
|
@ -2021,14 +2023,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_annotations_arent_relevant_when_doing_d
|
|||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
// Function calls are treated as (potentially) `nil`, the same as table
|
||||
// access, for UX.
|
||||
CHECK_EQ("nil", toString(requireTypeAtPosition({8, 28})));
|
||||
if (FFlag::LuauSolverV2)
|
||||
{
|
||||
// CLI-115478 - This should be never
|
||||
CHECK_EQ("nil", toString(requireTypeAtPosition({9, 28})));
|
||||
}
|
||||
else
|
||||
CHECK_EQ("nil", toString(requireTypeAtPosition({9, 28})));
|
||||
CHECK_EQ("nil", toString(requireTypeAtPosition({9, 28})));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "function_call_with_colon_after_refining_not_to_be_nil")
|
||||
|
@ -2526,4 +2524,42 @@ TEST_CASE_FIXTURE(Fixture, "truthy_call_of_function_with_table_value_as_argument
|
|||
CHECK_EQ("Item", toString(requireTypeAtPosition({9, 28})));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "function_calls_are_not_nillable")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauDoNotLeakNilInRefinement, true};
|
||||
|
||||
LUAU_CHECK_NO_ERRORS(check(R"(
|
||||
local BEFORE_SLASH_PATTERN = "^(.*)[\\/]"
|
||||
function operateOnPath(path: string): string?
|
||||
local fileName = string.gsub(path, BEFORE_SLASH_PATTERN, "")
|
||||
if string.match(fileName, "^init%.") then
|
||||
return "path=" .. fileName
|
||||
end
|
||||
return nil
|
||||
end
|
||||
)"));
|
||||
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1528_method_calls_are_not_nillable")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauDoNotLeakNilInRefinement, true};
|
||||
|
||||
LUAU_CHECK_NO_ERRORS(check(R"(
|
||||
type RunService = {
|
||||
IsRunning: (RunService) -> boolean
|
||||
}
|
||||
type Game = {
|
||||
GetRunService: (Game) -> RunService
|
||||
}
|
||||
local function getServices(g: Game): RunService
|
||||
local service = g:GetRunService()
|
||||
if service:IsRunning() then
|
||||
return service
|
||||
end
|
||||
error("Oh no! The service isn't running!")
|
||||
end
|
||||
)"));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -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/BuiltinDefinitions.h"
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/Error.h"
|
||||
#include "Luau/Frontend.h"
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/TypeInfer.h"
|
||||
|
@ -25,6 +26,7 @@ LUAU_FASTFLAG(LuauAllowNonSharedTableTypesInLiteral)
|
|||
LUAU_FASTFLAG(LuauFollowTableFreeze)
|
||||
LUAU_FASTFLAG(LuauPrecalculateMutatedFreeTypes2)
|
||||
LUAU_FASTFLAG(LuauDeferBidirectionalInferenceForTableAssignment)
|
||||
LUAU_FASTFLAG(LuauBidirectionalInferenceUpcast)
|
||||
|
||||
TEST_SUITE_BEGIN("TableTests");
|
||||
|
||||
|
@ -5183,4 +5185,157 @@ TEST_CASE_FIXTURE(Fixture, "empty_union_container_overflow")
|
|||
)"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "inference_in_constructor")
|
||||
{
|
||||
LUAU_CHECK_NO_ERRORS(check(R"(
|
||||
local function new(y)
|
||||
local t: { x: number } = { x = y }
|
||||
return t
|
||||
end
|
||||
)"));
|
||||
if (FFlag::LuauSolverV2)
|
||||
CHECK_EQ("(number) -> { x: number }", toString(requireType("new")));
|
||||
else
|
||||
CHECK_EQ("(number) -> {| x: number |}", toString(requireType("new")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "returning_optional_in_table")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauDeferBidirectionalInferenceForTableAssignment, true},
|
||||
{FFlag::LuauBidirectionalInferenceUpcast, true},
|
||||
};
|
||||
|
||||
LUAU_CHECK_NO_ERRORS(check(R"(
|
||||
local Numbers = { zero = 0 }
|
||||
local function FuncA(): { Value: number? }
|
||||
return { Value = Numbers.zero }
|
||||
end
|
||||
)"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "returning_mismatched_optional_in_table")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauDeferBidirectionalInferenceForTableAssignment, true},
|
||||
};
|
||||
|
||||
auto result = check(R"(
|
||||
local Numbers = { str = ( "" :: string ) }
|
||||
local function FuncB(): { Value: number? }
|
||||
return {
|
||||
Value = Numbers.str
|
||||
}
|
||||
end
|
||||
)");
|
||||
LUAU_CHECK_ERROR_COUNT(1, result);
|
||||
auto err = get<TypePackMismatch>(result.errors[0]);
|
||||
REQUIRE(err);
|
||||
CHECK_EQ(toString(err->givenTp), "{ Value: string }");
|
||||
CHECK_EQ(toString(err->wantedTp), "{ Value: number? }");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "optional_function_in_table")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauDeferBidirectionalInferenceForTableAssignment, true},
|
||||
{FFlag::LuauBidirectionalInferenceUpcast, true},
|
||||
};
|
||||
|
||||
LUAU_CHECK_NO_ERRORS(check(R"(
|
||||
local t: { (() -> ())? } = {
|
||||
function() end,
|
||||
}
|
||||
)"));
|
||||
|
||||
auto result = check(R"(
|
||||
local t: { ((number) -> ())? } = {
|
||||
function(_: string) end,
|
||||
}
|
||||
)");
|
||||
|
||||
LUAU_CHECK_ERROR_COUNT(1, result);
|
||||
auto err = get<TypeMismatch>(result.errors[0]);
|
||||
REQUIRE(err);
|
||||
CHECK_EQ(toString(err->givenType), "{(string) -> ()}");
|
||||
CHECK_EQ(toString(err->wantedType), "{((number) -> ())?}");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "oss_1596_expression_in_table")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauDeferBidirectionalInferenceForTableAssignment, true},
|
||||
{FFlag::LuauBidirectionalInferenceUpcast, true},
|
||||
};
|
||||
|
||||
LUAU_CHECK_NO_ERRORS(check(R"(
|
||||
type foo = {abc: number?}
|
||||
local x: foo = {abc = 100}
|
||||
local y: foo = {abc = 10 * 10}
|
||||
)"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "oss_1615_parametrized_type_alias")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauDeferBidirectionalInferenceForTableAssignment, true},
|
||||
};
|
||||
|
||||
LUAU_CHECK_NO_ERRORS(check(R"(
|
||||
type Pair<Node> = { sep: {}? }
|
||||
local a: Pair<{}> = {
|
||||
sep = nil,
|
||||
}
|
||||
)"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "oss_1543_optional_generic_param")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauPrecalculateMutatedFreeTypes2, true},
|
||||
{FFlag::LuauDeferBidirectionalInferenceForTableAssignment, true},
|
||||
};
|
||||
|
||||
LUAU_CHECK_NO_ERRORS(check(R"(
|
||||
type foo<T> = { bar: T? }
|
||||
|
||||
local foo: foo<any> = { bar = "foobar" }
|
||||
local foo: foo<any> = { }
|
||||
local foo: foo<nil> = { }
|
||||
)"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "missing_fields_bidirectional_inference")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauPrecalculateMutatedFreeTypes2, true},
|
||||
{FFlag::LuauDeferBidirectionalInferenceForTableAssignment, true},
|
||||
};
|
||||
|
||||
auto result = check(R"(
|
||||
type Book = { title: string, author: string }
|
||||
local b: Book = { title = "The Odyssey" }
|
||||
local t: { Book } = {
|
||||
{ title = "The Illiad", author = "Homer" },
|
||||
{ author = "Virgil" }
|
||||
}
|
||||
)");
|
||||
|
||||
LUAU_CHECK_ERROR_COUNT(2, result);
|
||||
auto err = get<TypeMismatch>(result.errors[0]);
|
||||
REQUIRE(err);
|
||||
CHECK_EQ(toString(err->givenType), "{ title: string }");
|
||||
CHECK_EQ(toString(err->wantedType), "Book");
|
||||
CHECK_EQ(result.errors[0].location, Location{{2, 24}, {2, 49}});
|
||||
err = get<TypeMismatch>(result.errors[1]);
|
||||
REQUIRE(err);
|
||||
CHECK_EQ(toString(err->givenType), "{{ author: string } | { author: string, title: string }}");
|
||||
CHECK_EQ(toString(err->wantedType), "{Book}");
|
||||
CHECK_EQ(result.errors[1].location, Location{{3, 28}, {6, 9}});
|
||||
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -26,6 +26,7 @@ LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
|||
LUAU_FASTFLAG(LuauAstTypeGroup2)
|
||||
LUAU_FASTFLAG(LuauNewNonStrictWarnOnUnknownGlobals)
|
||||
LUAU_FASTFLAG(LuauInferLocalTypesInMultipleAssignments)
|
||||
LUAU_FASTFLAG(LuauUnifyMetatableWithAny)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
|
@ -1808,4 +1809,55 @@ TEST_CASE_FIXTURE(Fixture, "multiple_assignment")
|
|||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "getmetatable_works_with_any")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauUnifyMetatableWithAny, true};
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(check(R"(
|
||||
return {
|
||||
new = function(name: string)
|
||||
local self = newproxy(true) :: any
|
||||
|
||||
getmetatable(self).__tostring = function()
|
||||
return "Hello, I am " .. name
|
||||
end
|
||||
|
||||
return self
|
||||
end,
|
||||
}
|
||||
)"));
|
||||
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "getmetatable_infer_any_ret")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauUnifyMetatableWithAny, true};
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(check(R"(
|
||||
local function spooky(x: any)
|
||||
return getmetatable(x)
|
||||
end
|
||||
)"));
|
||||
|
||||
CHECK_EQ("(any) -> any", toString(requireType("spooky")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "getmetatable_infer_any_param")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauUnifyMetatableWithAny, true},
|
||||
};
|
||||
|
||||
auto result = check(R"(
|
||||
local function check(x): any
|
||||
return getmetatable(x)
|
||||
end
|
||||
)");
|
||||
|
||||
// CLI-144695: We're leaking the `MT` generic here, this happens regardless
|
||||
// of if `LuauUnifyMetatableWithAny` is set.
|
||||
CHECK_EQ("({ @metatable MT, {+ +} }) -> any", toString(requireType("check")));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
Loading…
Add table
Reference in a new issue