mirror of
https://github.com/luau-lang/luau.git
synced 2025-04-05 19:30:54 +01:00
Sync to upstream/release/668
This commit is contained in:
parent
d4c2c64dcd
commit
d9e8dedd20
39 changed files with 1131 additions and 714 deletions
|
@ -132,6 +132,8 @@ struct ConstraintGenerator
|
|||
|
||||
DenseHashMap<TypeId, TypeIds> localTypes{nullptr};
|
||||
|
||||
DenseHashMap<AstExpr*, Inference> inferredExprCache{nullptr};
|
||||
|
||||
DcrLogger* logger;
|
||||
|
||||
ConstraintGenerator(
|
||||
|
|
|
@ -86,6 +86,7 @@ struct FragmentRegion
|
|||
AstStatBlock* parentBlock = nullptr; // used for scope detection
|
||||
};
|
||||
|
||||
std::optional<Position> blockDiffStart(AstStatBlock* blockOld, AstStatBlock* blockNew, AstStat* nearestStatementNewAst);
|
||||
FragmentRegion getFragmentRegion(AstStatBlock* root, const Position& cursorPosition);
|
||||
FragmentAutocompleteAncestryResult findAncestryForFragmentParse(AstStatBlock* stale, const Position& cursorPos, AstStatBlock* lastGoodParse);
|
||||
FragmentAutocompleteAncestryResult findAncestryForFragmentParse_DEPRECATED(AstStatBlock* root, const Position& cursorPos);
|
||||
|
|
|
@ -215,11 +215,6 @@ 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);
|
||||
|
|
|
@ -15,8 +15,6 @@
|
|||
#include <unordered_map>
|
||||
#include <optional>
|
||||
|
||||
LUAU_FASTFLAG(LuauIncrementalAutocompleteCommentDetection)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
|
|
@ -192,16 +192,6 @@ struct TxnLog
|
|||
// The pointer returned lives until `commit` or `clear` is called.
|
||||
PendingTypePack* changeLevel(TypePackId tp, TypeLevel newLevel);
|
||||
|
||||
// Queues the replacement of a type's scope with the provided scope.
|
||||
//
|
||||
// The pointer returned lives until `commit` or `clear` is called.
|
||||
PendingType* changeScope(TypeId ty, NotNull<Scope> scope);
|
||||
|
||||
// Queues the replacement of a type pack's scope with the provided scope.
|
||||
//
|
||||
// The pointer returned lives until `commit` or `clear` is called.
|
||||
PendingTypePack* changeScope(TypePackId tp, NotNull<Scope> scope);
|
||||
|
||||
// Queues a replacement of a table type with another table type with a new
|
||||
// indexer.
|
||||
//
|
||||
|
|
|
@ -356,10 +356,8 @@ struct FunctionType
|
|||
);
|
||||
|
||||
// Local monomorphic function
|
||||
FunctionType(TypeLevel level, TypePackId argTypes, TypePackId retTypes, std::optional<FunctionDefinition> defn = {}, bool hasSelf = false);
|
||||
FunctionType(
|
||||
TypeLevel level,
|
||||
Scope* scope,
|
||||
TypePackId argTypes,
|
||||
TypePackId retTypes,
|
||||
std::optional<FunctionDefinition> defn = {},
|
||||
|
@ -376,16 +374,6 @@ struct FunctionType
|
|||
std::optional<FunctionDefinition> defn = {},
|
||||
bool hasSelf = false
|
||||
);
|
||||
FunctionType(
|
||||
TypeLevel level,
|
||||
Scope* scope,
|
||||
std::vector<TypeId> generics,
|
||||
std::vector<TypePackId> genericPacks,
|
||||
TypePackId argTypes,
|
||||
TypePackId retTypes,
|
||||
std::optional<FunctionDefinition> defn = {},
|
||||
bool hasSelf = false
|
||||
);
|
||||
|
||||
std::optional<FunctionDefinition> definition;
|
||||
/// These should all be generic
|
||||
|
@ -394,7 +382,6 @@ struct FunctionType
|
|||
std::vector<std::optional<FunctionArgument>> argNames;
|
||||
Tags tags;
|
||||
TypeLevel level;
|
||||
Scope* scope = nullptr;
|
||||
TypePackId argTypes;
|
||||
TypePackId retTypes;
|
||||
std::shared_ptr<MagicFunction> magic = nullptr;
|
||||
|
@ -481,7 +468,9 @@ struct Property
|
|||
TypeId type() const;
|
||||
void setType(TypeId ty);
|
||||
|
||||
// Sets the write type of this property to the read type.
|
||||
// If this property has a present `writeTy`, set it equal to the `readTy`.
|
||||
// This is to ensure that if we normalize a property that has divergent
|
||||
// read and write types, we make them converge (for now).
|
||||
void makeShared();
|
||||
|
||||
bool isShared() const;
|
||||
|
@ -526,9 +515,6 @@ struct TableType
|
|||
std::optional<TypeId> boundTo;
|
||||
Tags tags;
|
||||
|
||||
// Methods of this table that have an untyped self will use the same shared self type.
|
||||
std::optional<TypeId> selfTy;
|
||||
|
||||
// We track the number of as-yet-unadded properties to unsealed tables.
|
||||
// Some constraints will use this information to decide whether or not they
|
||||
// are able to dispatch.
|
||||
|
@ -890,6 +876,9 @@ struct TypeFun
|
|||
*/
|
||||
TypeId type;
|
||||
|
||||
// The location of where this TypeFun was defined, if available
|
||||
std::optional<Location> definitionLocation;
|
||||
|
||||
TypeFun() = default;
|
||||
|
||||
explicit TypeFun(TypeId ty)
|
||||
|
@ -897,16 +886,23 @@ struct TypeFun
|
|||
{
|
||||
}
|
||||
|
||||
TypeFun(std::vector<GenericTypeDefinition> typeParams, TypeId type)
|
||||
TypeFun(std::vector<GenericTypeDefinition> typeParams, TypeId type, std::optional<Location> definitionLocation = std::nullopt)
|
||||
: typeParams(std::move(typeParams))
|
||||
, type(type)
|
||||
, definitionLocation(definitionLocation)
|
||||
{
|
||||
}
|
||||
|
||||
TypeFun(std::vector<GenericTypeDefinition> typeParams, std::vector<GenericTypePackDefinition> typePackParams, TypeId type)
|
||||
TypeFun(
|
||||
std::vector<GenericTypeDefinition> typeParams,
|
||||
std::vector<GenericTypePackDefinition> typePackParams,
|
||||
TypeId type,
|
||||
std::optional<Location> definitionLocation = std::nullopt
|
||||
)
|
||||
: typeParams(std::move(typeParams))
|
||||
, typePackParams(std::move(typePackParams))
|
||||
, type(type)
|
||||
, definitionLocation(definitionLocation)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -29,7 +29,6 @@
|
|||
*/
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAGVARIABLE(LuauStringFormatErrorSuppression)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3)
|
||||
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFollowTableFreeze)
|
||||
|
@ -712,10 +711,8 @@ bool MagicFormat::typeCheck(const MagicFunctionTypeCheckContext& context)
|
|||
|
||||
if (!result.isSubtype)
|
||||
{
|
||||
if (FFlag::LuauStringFormatErrorSuppression)
|
||||
switch (shouldSuppressErrors(NotNull{&context.typechecker->normalizer}, actualTy))
|
||||
{
|
||||
switch (shouldSuppressErrors(NotNull{&context.typechecker->normalizer}, actualTy))
|
||||
{
|
||||
case ErrorSuppression::Suppress:
|
||||
break;
|
||||
case ErrorSuppression::NormalizationFailed:
|
||||
|
@ -725,12 +722,6 @@ bool MagicFormat::typeCheck(const MagicFunctionTypeCheckContext& context)
|
|||
|
||||
if (!reasonings.suppressed)
|
||||
context.typechecker->reportError(TypeMismatch{expectedTy, actualTy, reasonings.toString()}, location);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Reasonings reasonings = context.typechecker->explainReasonings(actualTy, expectedTy, location, result);
|
||||
context.typechecker->reportError(TypeMismatch{expectedTy, actualTy, reasonings.toString()}, location);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -179,8 +179,6 @@ public:
|
|||
generic->scope = nullptr;
|
||||
else if (auto free = getMutable<FreeType>(target))
|
||||
free->scope = nullptr;
|
||||
else if (auto fn = getMutable<FunctionType>(target))
|
||||
fn->scope = nullptr;
|
||||
else if (auto table = getMutable<TableType>(target))
|
||||
table->scope = nullptr;
|
||||
|
||||
|
@ -521,11 +519,6 @@ public:
|
|||
if (FFlag::LuauClonedTableAndFunctionTypesMustHaveScopes)
|
||||
tt->scope = replacementForNullScope;
|
||||
}
|
||||
else if (auto fn = getMutable<FunctionType>(target))
|
||||
{
|
||||
if (FFlag::LuauClonedTableAndFunctionTypesMustHaveScopes)
|
||||
fn->scope = replacementForNullScope;
|
||||
}
|
||||
|
||||
(*types)[ty] = target;
|
||||
queue.emplace_back(target);
|
||||
|
|
|
@ -37,7 +37,6 @@ LUAU_FASTFLAGVARIABLE(LuauPropagateExpectedTypesForCalls)
|
|||
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauTrackInteriorFreeTypesOnScope)
|
||||
LUAU_FASTFLAGVARIABLE(LuauDeferBidirectionalInferenceForTableAssignment)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUngeneralizedTypesForRecursiveFunctions)
|
||||
LUAU_FASTFLAGVARIABLE(LuauGlobalSelfAssignmentCycle)
|
||||
|
||||
|
@ -46,8 +45,11 @@ LUAU_FASTFLAGVARIABLE(LuauInferLocalTypesInMultipleAssignments)
|
|||
LUAU_FASTFLAGVARIABLE(LuauDoNotLeakNilInRefinement)
|
||||
LUAU_FASTFLAGVARIABLE(LuauExtraFollows)
|
||||
LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
|
||||
LUAU_FASTFLAGVARIABLE(LuauRetainDefinitionAliasLocations)
|
||||
|
||||
LUAU_FASTFLAG(LuauDeprecatedAttribute)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCacheInferencePerAstExpr)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAlwaysResolveAstTypes)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -235,7 +237,7 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block)
|
|||
typeFunctionRuntime->rootScope = localTypeFunctionScope;
|
||||
}
|
||||
|
||||
TypeId moduleFnTy = arena->addType(FunctionType{TypeLevel{}, rootScope, builtinTypes->anyTypePack, rootScope->returnType});
|
||||
TypeId moduleFnTy = arena->addType(FunctionType{TypeLevel{}, builtinTypes->anyTypePack, rootScope->returnType});
|
||||
interiorTypes.emplace_back();
|
||||
|
||||
prepopulateGlobalScope(scope, block);
|
||||
|
@ -751,6 +753,9 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc
|
|||
initialFun.typePackParams.push_back(genPack);
|
||||
}
|
||||
|
||||
if (FFlag::LuauRetainDefinitionAliasLocations)
|
||||
initialFun.definitionLocation = alias->location;
|
||||
|
||||
if (alias->exported)
|
||||
scope->exportedTypeBindings[alias->name.value] = std::move(initialFun);
|
||||
else
|
||||
|
@ -808,6 +813,9 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc
|
|||
|
||||
TypeFun typeFunction{std::move(quantifiedTypeParams), typeFunctionTy};
|
||||
|
||||
if (FFlag::LuauRetainDefinitionAliasLocations)
|
||||
typeFunction.definitionLocation = function->location;
|
||||
|
||||
// Set type bindings and definition locations for this user-defined type function
|
||||
if (function->exported)
|
||||
scope->exportedTypeBindings[function->name.value] = std::move(typeFunction);
|
||||
|
@ -835,6 +843,8 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc
|
|||
|
||||
TypeId initialType = arena->addType(BlockedType{});
|
||||
TypeFun initialFun{initialType};
|
||||
if (FFlag::LuauRetainDefinitionAliasLocations)
|
||||
initialFun.definitionLocation = classDeclaration->location;
|
||||
scope->exportedTypeBindings[classDeclaration->name.value] = std::move(initialFun);
|
||||
|
||||
classDefinitionLocations[classDeclaration->name.value] = classDeclaration->location;
|
||||
|
@ -2012,7 +2022,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareFunc
|
|||
defn.varargLocation = global->vararg ? std::make_optional(global->varargLocation) : std::nullopt;
|
||||
defn.originalNameLocation = global->nameLocation;
|
||||
|
||||
TypeId fnType = arena->addType(FunctionType{TypeLevel{}, funScope.get(), std::move(genericTys), std::move(genericTps), paramPack, retPack, defn});
|
||||
TypeId fnType = arena->addType(FunctionType{TypeLevel{}, std::move(genericTys), std::move(genericTps), paramPack, retPack, defn});
|
||||
FunctionType* ftv = getMutable<FunctionType>(fnType);
|
||||
ftv->isCheckedFunction = global->isCheckedFunction();
|
||||
if (FFlag::LuauDeprecatedAttribute)
|
||||
|
@ -2302,7 +2312,7 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall*
|
|||
// TODO: How do expectedTypes play into this? Do they?
|
||||
TypePackId rets = arena->addTypePack(BlockedTypePack{});
|
||||
TypePackId argPack = addTypePack(std::move(args), argTail);
|
||||
FunctionType ftv(TypeLevel{}, scope.get(), argPack, rets, std::nullopt, call->self);
|
||||
FunctionType ftv(TypeLevel{}, argPack, rets, std::nullopt, call->self);
|
||||
|
||||
/*
|
||||
* To make bidirectional type checking work, we need to solve these constraints in a particular order:
|
||||
|
@ -2369,6 +2379,16 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExpr* expr, std::
|
|||
return Inference{builtinTypes->errorRecoveryType()};
|
||||
}
|
||||
|
||||
// We may recurse a given expression more than once when checking compound
|
||||
// assignment, so we store and cache expressions here s.t. when we generate
|
||||
// constraints for something like:
|
||||
//
|
||||
// a[b] += c
|
||||
//
|
||||
// We only solve _one_ set of constraints for `b`.
|
||||
if (FFlag::LuauCacheInferencePerAstExpr && inferredExprCache.contains(expr))
|
||||
return inferredExprCache[expr];
|
||||
|
||||
Inference result;
|
||||
|
||||
if (auto group = expr->as<AstExprGroup>())
|
||||
|
@ -2421,6 +2441,9 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExpr* expr, std::
|
|||
result = Inference{freshType(scope)};
|
||||
}
|
||||
|
||||
if (FFlag::LuauCacheInferencePerAstExpr)
|
||||
inferredExprCache[expr] = result;
|
||||
|
||||
LUAU_ASSERT(result.ty);
|
||||
module->astTypes[expr] = result.ty;
|
||||
if (expectedType)
|
||||
|
@ -3158,49 +3181,17 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr,
|
|||
|
||||
if (expectedType)
|
||||
{
|
||||
if (FFlag::LuauDeferBidirectionalInferenceForTableAssignment)
|
||||
{
|
||||
addConstraint(
|
||||
scope,
|
||||
expr->location,
|
||||
TableCheckConstraint{
|
||||
*expectedType,
|
||||
ty,
|
||||
expr,
|
||||
NotNull{&module->astTypes},
|
||||
NotNull{&module->astExpectedTypes},
|
||||
}
|
||||
);
|
||||
}
|
||||
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
|
||||
// allows us to do some bidirectional inference.
|
||||
toBlock = findBlockedTypesIn(expr, NotNull{&module->astTypes});
|
||||
if (toBlock.empty())
|
||||
{
|
||||
matchLiteralType(
|
||||
NotNull{&module->astTypes},
|
||||
NotNull{&module->astExpectedTypes},
|
||||
builtinTypes,
|
||||
arena,
|
||||
NotNull{&unifier},
|
||||
NotNull{&sp},
|
||||
*expectedType,
|
||||
ty,
|
||||
expr,
|
||||
toBlock
|
||||
);
|
||||
// The visitor we ran prior should ensure that there are no
|
||||
// blocked types that we would encounter while matching on
|
||||
// this expression.
|
||||
LUAU_ASSERT(toBlock.empty());
|
||||
addConstraint(
|
||||
scope,
|
||||
expr->location,
|
||||
TableCheckConstraint{
|
||||
*expectedType,
|
||||
ty,
|
||||
expr,
|
||||
NotNull{&module->astTypes},
|
||||
NotNull{&module->astExpectedTypes},
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return Inference{ty};
|
||||
|
@ -3371,7 +3362,7 @@ ConstraintGenerator::FunctionSignature ConstraintGenerator::checkFunctionSignatu
|
|||
|
||||
// TODO: Preserve argument names in the function's type.
|
||||
|
||||
FunctionType actualFunction{TypeLevel{}, parent.get(), arena->addTypePack(argTypes, varargPack), returnType};
|
||||
FunctionType actualFunction{TypeLevel{}, arena->addTypePack(argTypes, varargPack), returnType};
|
||||
actualFunction.generics = std::move(genericTypes);
|
||||
actualFunction.genericPacks = std::move(genericTypePacks);
|
||||
actualFunction.argNames = std::move(argNames);
|
||||
|
@ -3608,7 +3599,7 @@ TypeId ConstraintGenerator::resolveFunctionType(
|
|||
|
||||
// TODO: FunctionType needs a pointer to the scope so that we know
|
||||
// how to quantify/instantiate it.
|
||||
FunctionType ftv{TypeLevel{}, scope.get(), {}, {}, argTypes, returnTypes};
|
||||
FunctionType ftv{TypeLevel{}, {}, {}, argTypes, returnTypes};
|
||||
ftv.isCheckedFunction = fn->isCheckedFunction();
|
||||
if (FFlag::LuauDeprecatedAttribute)
|
||||
ftv.isDeprecatedFunction = fn->hasAttribute(AstAttr::Type::Deprecated);
|
||||
|
@ -3658,39 +3649,78 @@ TypeId ConstraintGenerator::resolveType(const ScopePtr& scope, AstType* ty, bool
|
|||
}
|
||||
else if (ty->is<AstTypeOptional>())
|
||||
{
|
||||
return builtinTypes->nilType;
|
||||
if (FFlag::LuauAlwaysResolveAstTypes)
|
||||
result = builtinTypes->nilType;
|
||||
else
|
||||
return builtinTypes->nilType;
|
||||
}
|
||||
else if (auto unionAnnotation = ty->as<AstTypeUnion>())
|
||||
{
|
||||
if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
|
||||
if (FFlag::LuauAlwaysResolveAstTypes)
|
||||
{
|
||||
if (unionAnnotation->types.size == 1)
|
||||
return resolveType(scope, unionAnnotation->types.data[0], inTypeArguments);
|
||||
}
|
||||
result = resolveType(scope, unionAnnotation->types.data[0], inTypeArguments);
|
||||
else
|
||||
{
|
||||
std::vector<TypeId> parts;
|
||||
for (AstType* part : unionAnnotation->types)
|
||||
{
|
||||
parts.push_back(resolveType(scope, part, inTypeArguments));
|
||||
}
|
||||
|
||||
std::vector<TypeId> parts;
|
||||
for (AstType* part : unionAnnotation->types)
|
||||
result = arena->addType(UnionType{parts});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
parts.push_back(resolveType(scope, part, inTypeArguments));
|
||||
}
|
||||
if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
|
||||
{
|
||||
if (unionAnnotation->types.size == 1)
|
||||
return resolveType(scope, unionAnnotation->types.data[0], inTypeArguments);
|
||||
}
|
||||
|
||||
result = arena->addType(UnionType{parts});
|
||||
std::vector<TypeId> parts;
|
||||
for (AstType* part : unionAnnotation->types)
|
||||
{
|
||||
parts.push_back(resolveType(scope, part, inTypeArguments));
|
||||
}
|
||||
|
||||
result = arena->addType(UnionType{parts});
|
||||
}
|
||||
}
|
||||
else if (auto intersectionAnnotation = ty->as<AstTypeIntersection>())
|
||||
{
|
||||
if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
|
||||
if (FFlag::LuauAlwaysResolveAstTypes)
|
||||
{
|
||||
if (intersectionAnnotation->types.size == 1)
|
||||
return resolveType(scope, intersectionAnnotation->types.data[0], inTypeArguments);
|
||||
}
|
||||
result = resolveType(scope, intersectionAnnotation->types.data[0], inTypeArguments);
|
||||
else
|
||||
{
|
||||
std::vector<TypeId> parts;
|
||||
for (AstType* part : intersectionAnnotation->types)
|
||||
{
|
||||
parts.push_back(resolveType(scope, part, inTypeArguments));
|
||||
}
|
||||
|
||||
std::vector<TypeId> parts;
|
||||
for (AstType* part : intersectionAnnotation->types)
|
||||
result = arena->addType(IntersectionType{parts});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
parts.push_back(resolveType(scope, part, inTypeArguments));
|
||||
}
|
||||
if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
|
||||
{
|
||||
if (intersectionAnnotation->types.size == 1)
|
||||
return resolveType(scope, intersectionAnnotation->types.data[0], inTypeArguments);
|
||||
}
|
||||
|
||||
result = arena->addType(IntersectionType{parts});
|
||||
std::vector<TypeId> parts;
|
||||
for (AstType* part : intersectionAnnotation->types)
|
||||
{
|
||||
parts.push_back(resolveType(scope, part, inTypeArguments));
|
||||
}
|
||||
|
||||
result = arena->addType(IntersectionType{parts});
|
||||
}
|
||||
}
|
||||
else if (auto typeGroupAnnotation = ty->as<AstTypeGroup>())
|
||||
{
|
||||
|
|
|
@ -35,12 +35,13 @@ LUAU_FASTINTVARIABLE(LuauSolverRecursionLimit, 500)
|
|||
LUAU_FASTFLAGVARIABLE(DebugLuauEqSatSimplification)
|
||||
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTrackInteriorFreeTablesOnScope)
|
||||
LUAU_FASTFLAGVARIABLE(LuauPrecalculateMutatedFreeTypes2)
|
||||
LUAU_FASTFLAGVARIABLE(LuauHasPropProperBlock)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauGreedyGeneralization)
|
||||
LUAU_FASTFLAG(LuauSearchForRefineableType)
|
||||
LUAU_FASTFLAG(LuauDeprecatedAttribute)
|
||||
LUAU_FASTFLAG(LuauBidirectionalInferenceCollectIndexerTypes)
|
||||
LUAU_FASTFLAG(LuauNewTypeFunReductionChecks2)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTrackInferredFunctionTypeFromCall)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -360,32 +361,19 @@ ConstraintSolver::ConstraintSolver(
|
|||
{
|
||||
unsolvedConstraints.emplace_back(c);
|
||||
|
||||
if (FFlag::LuauPrecalculateMutatedFreeTypes2)
|
||||
auto maybeMutatedTypesPerConstraint = c->getMaybeMutatedFreeTypes();
|
||||
for (auto ty : maybeMutatedTypesPerConstraint)
|
||||
{
|
||||
auto maybeMutatedTypesPerConstraint = c->getMaybeMutatedFreeTypes();
|
||||
for (auto ty : maybeMutatedTypesPerConstraint)
|
||||
{
|
||||
auto [refCount, _] = unresolvedConstraints.try_insert(ty, 0);
|
||||
refCount += 1;
|
||||
auto [refCount, _] = unresolvedConstraints.try_insert(ty, 0);
|
||||
refCount += 1;
|
||||
|
||||
if (FFlag::DebugLuauGreedyGeneralization)
|
||||
{
|
||||
auto [it, fresh] = mutatedFreeTypeToConstraint.try_emplace(ty, DenseHashSet<const Constraint*>{nullptr});
|
||||
it->second.insert(c.get());
|
||||
}
|
||||
}
|
||||
maybeMutatedFreeTypes.emplace(c, maybeMutatedTypesPerConstraint);
|
||||
}
|
||||
else
|
||||
{
|
||||
// initialize the reference counts for the free types in this constraint.
|
||||
for (auto ty : c->getMaybeMutatedFreeTypes())
|
||||
if (FFlag::DebugLuauGreedyGeneralization)
|
||||
{
|
||||
// increment the reference count for `ty`
|
||||
auto [refCount, _] = unresolvedConstraints.try_insert(ty, 0);
|
||||
refCount += 1;
|
||||
auto [it, fresh] = mutatedFreeTypeToConstraint.try_emplace(ty, DenseHashSet<const Constraint*>{nullptr});
|
||||
it->second.insert(c.get());
|
||||
}
|
||||
}
|
||||
maybeMutatedFreeTypes.emplace(c, maybeMutatedTypesPerConstraint);
|
||||
|
||||
|
||||
for (NotNull<const Constraint> dep : c->dependencies)
|
||||
|
@ -476,48 +464,24 @@ void ConstraintSolver::run()
|
|||
unblock(c);
|
||||
unsolvedConstraints.erase(unsolvedConstraints.begin() + ptrdiff_t(i));
|
||||
|
||||
if (FFlag::LuauPrecalculateMutatedFreeTypes2)
|
||||
const auto maybeMutated = maybeMutatedFreeTypes.find(c);
|
||||
if (maybeMutated != maybeMutatedFreeTypes.end())
|
||||
{
|
||||
const auto maybeMutated = maybeMutatedFreeTypes.find(c);
|
||||
if (maybeMutated != maybeMutatedFreeTypes.end())
|
||||
DenseHashSet<TypeId> seen{nullptr};
|
||||
for (auto ty : maybeMutated->second)
|
||||
{
|
||||
DenseHashSet<TypeId> seen{nullptr};
|
||||
for (auto ty : maybeMutated->second)
|
||||
// There is a high chance that this type has been rebound
|
||||
// across blocked types, rebound free types, pending
|
||||
// expansion types, etc, so we need to follow it.
|
||||
ty = follow(ty);
|
||||
|
||||
if (FFlag::DebugLuauGreedyGeneralization)
|
||||
{
|
||||
// There is a high chance that this type has been rebound
|
||||
// across blocked types, rebound free types, pending
|
||||
// expansion types, etc, so we need to follow it.
|
||||
ty = follow(ty);
|
||||
|
||||
if (FFlag::DebugLuauGreedyGeneralization)
|
||||
{
|
||||
if (seen.contains(ty))
|
||||
continue;
|
||||
seen.insert(ty);
|
||||
}
|
||||
|
||||
size_t& refCount = unresolvedConstraints[ty];
|
||||
if (refCount > 0)
|
||||
refCount -= 1;
|
||||
|
||||
// We have two constraints that are designed to wait for the
|
||||
// refCount on a free type to be equal to 1: the
|
||||
// PrimitiveTypeConstraint and ReduceConstraint. We
|
||||
// therefore wake any constraint waiting for a free type's
|
||||
// refcount to be 1 or 0.
|
||||
if (refCount <= 1)
|
||||
unblock(ty, Location{});
|
||||
|
||||
if (FFlag::DebugLuauGreedyGeneralization && refCount == 0)
|
||||
generalizeOneType(ty);
|
||||
if (seen.contains(ty))
|
||||
continue;
|
||||
seen.insert(ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// decrement the referenced free types for this constraint if we dispatched successfully!
|
||||
for (auto ty : c->getMaybeMutatedFreeTypes())
|
||||
{
|
||||
|
||||
size_t& refCount = unresolvedConstraints[ty];
|
||||
if (refCount > 0)
|
||||
refCount -= 1;
|
||||
|
@ -529,6 +493,9 @@ void ConstraintSolver::run()
|
|||
// refcount to be 1 or 0.
|
||||
if (refCount <= 1)
|
||||
unblock(ty, Location{});
|
||||
|
||||
if (FFlag::DebugLuauGreedyGeneralization && refCount == 0)
|
||||
generalizeOneType(ty);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1516,7 +1483,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
|
|||
if (status == OverloadResolver::Analysis::Ok)
|
||||
overloadToUse = overload;
|
||||
|
||||
TypeId inferredTy = arena->addType(FunctionType{TypeLevel{}, constraint->scope.get(), argsPack, c.result});
|
||||
TypeId inferredTy = arena->addType(FunctionType{TypeLevel{}, argsPack, c.result});
|
||||
Unifier2 u2{NotNull{arena}, builtinTypes, constraint->scope, NotNull{&iceReporter}};
|
||||
|
||||
const bool occursCheckPassed = u2.unify(overloadToUse, inferredTy);
|
||||
|
@ -1551,6 +1518,11 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
|
|||
queuer.traverse(overloadToUse);
|
||||
queuer.traverse(inferredTy);
|
||||
|
||||
// This can potentially contain free types if the return type of
|
||||
// `inferredTy` is never unified elsewhere.
|
||||
if (FFlag::LuauTrackInteriorFreeTypesOnScope && FFlag::LuauTrackInferredFunctionTypeFromCall)
|
||||
trackInteriorFreeType(constraint->scope, inferredTy);
|
||||
|
||||
unblock(c.result, constraint->location);
|
||||
|
||||
return true;
|
||||
|
@ -1812,8 +1784,16 @@ bool ConstraintSolver::tryDispatch(const HasPropConstraint& c, NotNull<const Con
|
|||
LUAU_ASSERT(get<BlockedType>(resultType));
|
||||
LUAU_ASSERT(canMutate(resultType, constraint));
|
||||
|
||||
if (isBlocked(subjectType) || get<PendingExpansionType>(subjectType) || get<TypeFunctionInstanceType>(subjectType))
|
||||
return block(subjectType, constraint);
|
||||
if (FFlag::LuauHasPropProperBlock)
|
||||
{
|
||||
if (isBlocked(subjectType))
|
||||
return block(subjectType, constraint);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isBlocked(subjectType) || get<PendingExpansionType>(subjectType) || get<TypeFunctionInstanceType>(subjectType))
|
||||
return block(subjectType, constraint);
|
||||
}
|
||||
|
||||
if (const TableType* subjectTable = getTableType(subjectType))
|
||||
{
|
||||
|
|
|
@ -13,32 +13,22 @@
|
|||
|
||||
LUAU_FASTFLAG(DebugLuauFreezeArena)
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAGVARIABLE(LuauPreprocessTypestatedArgument)
|
||||
LUAU_FASTFLAGVARIABLE(LuauDfgScopeStackTrueReset)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
bool doesCallError(const AstExprCall* call); // TypeInfer.cpp
|
||||
|
||||
struct ReferencedDefFinder : public AstVisitor
|
||||
{
|
||||
bool visit(AstExprLocal* local) override
|
||||
{
|
||||
referencedLocalDefs.push_back(local->local);
|
||||
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<AstLocal*> referencedLocalDefs;
|
||||
};
|
||||
|
||||
struct PushScope
|
||||
{
|
||||
ScopeStack& stack;
|
||||
size_t previousSize;
|
||||
|
||||
PushScope(ScopeStack& stack, DfgScope* scope)
|
||||
: stack(stack)
|
||||
, previousSize(stack.size())
|
||||
{
|
||||
// `scope` should never be `nullptr` here.
|
||||
LUAU_ASSERT(scope);
|
||||
|
@ -47,7 +37,18 @@ struct PushScope
|
|||
|
||||
~PushScope()
|
||||
{
|
||||
stack.pop_back();
|
||||
if (FFlag::LuauDfgScopeStackTrueReset)
|
||||
{
|
||||
// If somehow this stack has _shrunk_ to be smaller than we expect,
|
||||
// something very strange has happened.
|
||||
LUAU_ASSERT(stack.size() > previousSize);
|
||||
while (stack.size() > previousSize)
|
||||
stack.pop_back();
|
||||
}
|
||||
else
|
||||
{
|
||||
stack.pop_back();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -872,6 +873,12 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprCall* c)
|
|||
{
|
||||
visitExpr(c->func);
|
||||
|
||||
if (FFlag::LuauPreprocessTypestatedArgument)
|
||||
{
|
||||
for (AstExpr* arg : c->args)
|
||||
visitExpr(arg);
|
||||
}
|
||||
|
||||
if (shouldTypestateForFirstArgument(*c) && c->args.size > 1 && isLValue(*c->args.begin()))
|
||||
{
|
||||
AstExpr* firstArg = *c->args.begin();
|
||||
|
@ -902,8 +909,11 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprCall* c)
|
|||
visitLValue(firstArg, def);
|
||||
}
|
||||
|
||||
for (AstExpr* arg : c->args)
|
||||
visitExpr(arg);
|
||||
if (!FFlag::LuauPreprocessTypestatedArgument)
|
||||
{
|
||||
for (AstExpr* arg : c->args)
|
||||
visitExpr(arg);
|
||||
}
|
||||
|
||||
// We treat function calls as "subscripted" as they could potentially
|
||||
// return a subscripted value, consider:
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#include "Luau/Module.h"
|
||||
#include "Luau/Clone.h"
|
||||
#include "AutocompleteCore.h"
|
||||
#include <optional>
|
||||
|
||||
LUAU_FASTINT(LuauTypeInferRecursionLimit);
|
||||
LUAU_FASTINT(LuauTypeInferIterationLimit);
|
||||
|
@ -35,13 +36,13 @@ LUAU_FASTFLAGVARIABLE(LuauBetterCursorInCommentDetection)
|
|||
LUAU_FASTFLAGVARIABLE(LuauAllFreeTypesHaveScopes)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFragmentAcSupportsReporter)
|
||||
LUAU_FASTFLAGVARIABLE(LuauPersistConstraintGenerationScopes)
|
||||
LUAU_FASTFLAG(LuauModuleHoldsAstRoot)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCloneTypeAliasBindings)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCloneReturnTypePack)
|
||||
LUAU_FASTFLAGVARIABLE(LuauIncrementalAutocompleteDemandBasedCloning)
|
||||
LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFragmentNoTypeFunEval)
|
||||
LUAU_FASTFLAGVARIABLE(LuauBetterScopeSelection)
|
||||
LUAU_FASTFLAGVARIABLE(LuauBlockDiffFragmentSelection)
|
||||
|
||||
namespace
|
||||
{
|
||||
|
@ -253,6 +254,85 @@ struct NearestStatementFinder : public AstVisitor
|
|||
AstStatBlock* parent = nullptr;
|
||||
};
|
||||
|
||||
// This struct takes a block found in a updated AST and looks for the corresponding block in a different ast.
|
||||
// This is a best effort check - we are looking for the block that is as close in location, ideally the same
|
||||
// block as the one from the updated AST
|
||||
struct NearestLikelyBlockFinder : public AstVisitor
|
||||
{
|
||||
explicit NearestLikelyBlockFinder(NotNull<AstStatBlock> stmtBlockRecentAst)
|
||||
: stmtBlockRecentAst(stmtBlockRecentAst)
|
||||
{
|
||||
}
|
||||
|
||||
bool visit(AstStatBlock* block) override
|
||||
{
|
||||
if (block->location.begin <= stmtBlockRecentAst->location.begin)
|
||||
{
|
||||
if (found)
|
||||
{
|
||||
if (found.value()->location.begin < block->location.begin)
|
||||
found.emplace(block);
|
||||
}
|
||||
else
|
||||
{
|
||||
found.emplace(block);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
NotNull<AstStatBlock> stmtBlockRecentAst;
|
||||
std::optional<AstStatBlock*> found = std::nullopt;
|
||||
};
|
||||
|
||||
// Diffs two ast stat blocks. Once at the first difference, consume between that range and the end of the nearest statement
|
||||
std::optional<Position> blockDiffStart(AstStatBlock* blockOld, AstStatBlock* blockNew, AstStat* nearestStatementNewAst)
|
||||
{
|
||||
AstArray<AstStat*> _old = blockOld->body;
|
||||
AstArray<AstStat*> _new = blockNew->body;
|
||||
size_t oldSize = _old.size;
|
||||
size_t stIndex = 0;
|
||||
|
||||
// We couldn't find a nearest statement
|
||||
if (nearestStatementNewAst == blockNew)
|
||||
return std::nullopt;
|
||||
bool found = false;
|
||||
for (auto st : _new)
|
||||
{
|
||||
if (st == nearestStatementNewAst)
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
stIndex++;
|
||||
}
|
||||
|
||||
if (!found)
|
||||
return std::nullopt;
|
||||
// Take care of some easy cases!
|
||||
if (oldSize == 0 && _new.size >= 0)
|
||||
return {_new.data[0]->location.begin};
|
||||
|
||||
if (_new.size < oldSize)
|
||||
return std::nullopt;
|
||||
|
||||
for (size_t i = 0; i < std::min(oldSize, stIndex + 1); i++)
|
||||
{
|
||||
AstStat* oldStat = _old.data[i];
|
||||
AstStat* newStat = _new.data[i];
|
||||
|
||||
bool isSame = oldStat->classIndex == newStat->classIndex && oldStat->location == newStat->location;
|
||||
if (!isSame)
|
||||
return {oldStat->location.begin};
|
||||
}
|
||||
|
||||
if (oldSize <= stIndex)
|
||||
return {_new.data[oldSize]->location.begin};
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
|
||||
FragmentRegion getFragmentRegion(AstStatBlock* root, const Position& cursorPosition)
|
||||
{
|
||||
NearestStatementFinder nsf{cursorPosition};
|
||||
|
@ -263,12 +343,33 @@ FragmentRegion getFragmentRegion(AstStatBlock* root, const Position& cursorPosit
|
|||
return FragmentRegion{getFragmentLocation(nsf.nearest, cursorPosition), nsf.nearest, parent};
|
||||
};
|
||||
|
||||
FragmentRegion getFragmentRegionWithBlockDiff(AstStatBlock* stale, AstStatBlock* fresh, const Position& cursorPos)
|
||||
{
|
||||
// Visit the new ast
|
||||
NearestStatementFinder nsf{cursorPos};
|
||||
fresh->visit(&nsf);
|
||||
// parent must always be non-null
|
||||
NotNull<AstStatBlock> parent{nsf.parent ? nsf.parent : fresh};
|
||||
NotNull<AstStat> nearest{nsf.nearest ? nsf.nearest : fresh};
|
||||
// Grab the same start block in the stale ast
|
||||
NearestLikelyBlockFinder lsf{parent};
|
||||
stale->visit(&lsf);
|
||||
|
||||
if (auto sameBlock = lsf.found)
|
||||
{
|
||||
if (std::optional<Position> fd = blockDiffStart(*sameBlock, parent, nearest))
|
||||
return FragmentRegion{Location{*fd, cursorPos}, nearest, parent};
|
||||
}
|
||||
return FragmentRegion{getFragmentLocation(nsf.nearest, cursorPos), nearest, parent};
|
||||
}
|
||||
|
||||
FragmentAutocompleteAncestryResult findAncestryForFragmentParse(AstStatBlock* stale, const Position& cursorPos, AstStatBlock* lastGoodParse)
|
||||
{
|
||||
// the freshest ast can sometimes be null if the parse was bad.
|
||||
if (lastGoodParse == nullptr)
|
||||
return {};
|
||||
FragmentRegion region = getFragmentRegion(lastGoodParse, cursorPos);
|
||||
FragmentRegion region = FFlag::LuauBlockDiffFragmentSelection ? getFragmentRegionWithBlockDiff(stale, lastGoodParse, cursorPos)
|
||||
: getFragmentRegion(lastGoodParse, cursorPos);
|
||||
std::vector<AstNode*> ancestry = findAncestryAtPositionForAutocomplete(stale, cursorPos);
|
||||
LUAU_ASSERT(ancestry.size() >= 1);
|
||||
// We should only pick up locals that are before the region
|
||||
|
@ -516,7 +617,7 @@ void cloneTypesFromFragment(
|
|||
destScope->lvalueTypes[d] = Luau::cloneIncremental(pair->second.typeId, *destArena, cloneState, destScope);
|
||||
destScope->bindings[pair->first] = Luau::cloneIncremental(pair->second, *destArena, cloneState, destScope);
|
||||
}
|
||||
else if (FFlag::LuauBetterScopeSelection)
|
||||
else if (FFlag::LuauBetterScopeSelection && !FFlag::LuauBlockDiffFragmentSelection)
|
||||
{
|
||||
destScope->lvalueTypes[d] = builtins->unknownType;
|
||||
Binding b;
|
||||
|
@ -916,17 +1017,32 @@ ScopePtr findClosestScope_DEPRECATED(const ModulePtr& module, const AstStat* nea
|
|||
ScopePtr findClosestScope(const ModulePtr& module, const Position& scopePos)
|
||||
{
|
||||
LUAU_ASSERT(module->hasModuleScope());
|
||||
|
||||
ScopePtr closest = module->getModuleScope();
|
||||
|
||||
// find the scope the nearest statement belonged to.
|
||||
for (const auto& [loc, sc] : module->scopes)
|
||||
if (FFlag::LuauBlockDiffFragmentSelection)
|
||||
{
|
||||
if (sc->location.contains(scopePos) && closest->location.begin < sc->location.begin)
|
||||
closest = sc;
|
||||
ScopePtr closest = module->getModuleScope();
|
||||
// find the scope the nearest statement belonged to.
|
||||
for (const auto& [loc, sc] : module->scopes)
|
||||
{
|
||||
// We bias towards the later scopes because those correspond to inner scopes.
|
||||
// in the case of if statements, we create two scopes at the same location for the body of the then
|
||||
// and else branches, so we need to bias later. This is why the closest update condition has a <=
|
||||
// instead of a <
|
||||
if (sc->location.contains(scopePos) && closest->location.begin <= sc->location.begin)
|
||||
closest = sc;
|
||||
}
|
||||
return closest;
|
||||
}
|
||||
else
|
||||
{
|
||||
ScopePtr closest = module->getModuleScope();
|
||||
// find the scope the nearest statement belonged to.
|
||||
for (const auto& [loc, sc] : module->scopes)
|
||||
{
|
||||
if (sc->location.contains(scopePos) && closest->location.begin < sc->location.begin)
|
||||
closest = sc;
|
||||
}
|
||||
return closest;
|
||||
}
|
||||
|
||||
return closest;
|
||||
}
|
||||
|
||||
std::optional<FragmentParseResult> parseFragment_DEPRECATED(
|
||||
|
@ -1455,29 +1571,9 @@ std::pair<FragmentTypeCheckStatus, FragmentTypeCheckResult> typecheckFragment(
|
|||
}
|
||||
|
||||
std::optional<FragmentParseResult> tryParse;
|
||||
if (FFlag::LuauModuleHoldsAstRoot)
|
||||
{
|
||||
tryParse = FFlag::LuauBetterScopeSelection
|
||||
? parseFragment(module->root, recentParse, module->names.get(), src, cursorPos, fragmentEndPosition)
|
||||
: parseFragment_DEPRECATED(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 {};
|
||||
}
|
||||
tryParse = FFlag::LuauBetterScopeSelection ? parseFragment(module->root, recentParse, module->names.get(), src, cursorPos, fragmentEndPosition)
|
||||
: parseFragment_DEPRECATED(module->root, module->names.get(), src, cursorPos, fragmentEndPosition);
|
||||
|
||||
if (sourceModule->allocator.get() != module->allocator.get())
|
||||
{
|
||||
return {FragmentTypeCheckStatus::SkipAutocomplete, {}};
|
||||
}
|
||||
|
||||
tryParse = parseFragment_DEPRECATED(sourceModule->root, sourceModule->names.get(), src, cursorPos, fragmentEndPosition);
|
||||
reportWaypoint(reporter, FragmentAutocompleteWaypoint::ParseFragmentEnd);
|
||||
}
|
||||
|
||||
if (!tryParse)
|
||||
return {FragmentTypeCheckStatus::SkipAutocomplete, {}};
|
||||
|
@ -1554,20 +1650,6 @@ FragmentAutocompleteResult fragmentAutocomplete(
|
|||
LUAU_TIMETRACE_SCOPE("Luau::fragmentAutocomplete", "FragmentAutocomplete");
|
||||
LUAU_TIMETRACE_ARGUMENT("name", moduleName.c_str());
|
||||
|
||||
if (!FFlag::LuauModuleHoldsAstRoot)
|
||||
{
|
||||
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 {};
|
||||
}
|
||||
|
||||
auto [tcStatus, tcResult] = typecheckFragment(frontend, moduleName, cursorPosition, opts, src, fragmentEndPosition, recentParse, reporter);
|
||||
if (tcStatus == FragmentTypeCheckStatus::SkipAutocomplete)
|
||||
return {};
|
||||
|
|
|
@ -39,6 +39,7 @@ LUAU_FASTINT(LuauTarjanChildLimit)
|
|||
LUAU_FASTFLAG(LuauInferInNoCheckMode)
|
||||
LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3)
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauRethrowKnownExceptions, false)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJsonFile)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauForbidInternalTypes)
|
||||
|
@ -46,10 +47,6 @@ LUAU_FASTFLAGVARIABLE(DebugLuauForceStrictMode)
|
|||
LUAU_FASTFLAGVARIABLE(DebugLuauForceNonStrictMode)
|
||||
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauRunCustomModuleChecks, false)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauModuleHoldsAstRoot)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauFixMultithreadTypecheck)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauSelectivelyRetainDFGArena)
|
||||
LUAU_FASTFLAG(LuauTypeFunResultInAutocomplete)
|
||||
|
||||
|
@ -480,11 +477,6 @@ 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;
|
||||
|
@ -669,247 +661,6 @@ std::vector<ModuleName> Frontend::checkQueuedModules(
|
|||
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;
|
||||
|
||||
// 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::vector<BuildQueueItem> buildQueueItems;
|
||||
|
||||
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(buildQueueItems, queue, cycleDetected, seen, frontendOptions);
|
||||
}
|
||||
|
||||
if (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 < buildQueueItems.size(); i++)
|
||||
{
|
||||
BuildQueueItem& item = buildQueueItems[i];
|
||||
moduleNameToQueue[item.name] = i;
|
||||
}
|
||||
|
||||
// Default task execution is single-threaded and immediate
|
||||
if (!executeTask)
|
||||
{
|
||||
executeTask = [](std::function<void()> task)
|
||||
{
|
||||
task();
|
||||
};
|
||||
}
|
||||
|
||||
std::mutex mtx;
|
||||
std::condition_variable cv;
|
||||
std::vector<size_t> readyQueueItems;
|
||||
|
||||
size_t processing = 0;
|
||||
size_t remaining = buildQueueItems.size();
|
||||
|
||||
auto itemTask = [&](size_t i)
|
||||
{
|
||||
BuildQueueItem& item = buildQueueItems[i];
|
||||
|
||||
try
|
||||
{
|
||||
checkBuildQueueItem(item);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
item.exception = std::current_exception();
|
||||
}
|
||||
|
||||
{
|
||||
std::unique_lock guard(mtx);
|
||||
readyQueueItems.push_back(i);
|
||||
}
|
||||
|
||||
cv.notify_one();
|
||||
};
|
||||
|
||||
auto sendItemTask = [&](size_t i)
|
||||
{
|
||||
BuildQueueItem& item = buildQueueItems[i];
|
||||
|
||||
item.processing = true;
|
||||
processing++;
|
||||
|
||||
executeTask(
|
||||
[&itemTask, i]()
|
||||
{
|
||||
itemTask(i);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
auto sendCycleItemTask = [&]
|
||||
{
|
||||
for (size_t i = 0; i < buildQueueItems.size(); i++)
|
||||
{
|
||||
BuildQueueItem& item = buildQueueItems[i];
|
||||
|
||||
if (!item.processing)
|
||||
{
|
||||
sendItemTask(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// In a first pass, check modules that have no dependencies and record info of those modules that wait
|
||||
for (size_t i = 0; i < buildQueueItems.size(); i++)
|
||||
{
|
||||
BuildQueueItem& item = 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++;
|
||||
|
||||
buildQueueItems[moduleNameToQueue[dep]].reverseDeps.push_back(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (item.dirtyDependencies == 0)
|
||||
sendItemTask(i);
|
||||
}
|
||||
|
||||
// Not a single item was found, a cycle in the graph was hit
|
||||
if (processing == 0)
|
||||
sendCycleItemTask();
|
||||
|
||||
std::vector<size_t> nextItems;
|
||||
std::optional<size_t> itemWithException;
|
||||
bool cancelled = false;
|
||||
|
||||
while (remaining != 0)
|
||||
{
|
||||
{
|
||||
std::unique_lock guard(mtx);
|
||||
|
||||
// If nothing is ready yet, wait
|
||||
cv.wait(
|
||||
guard,
|
||||
[&readyQueueItems]
|
||||
{
|
||||
return !readyQueueItems.empty();
|
||||
}
|
||||
);
|
||||
|
||||
// Handle checked items
|
||||
for (size_t i : readyQueueItems)
|
||||
{
|
||||
const BuildQueueItem& item = 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 = 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(processing >= readyQueueItems.size());
|
||||
processing -= readyQueueItems.size();
|
||||
|
||||
LUAU_ASSERT(remaining >= readyQueueItems.size());
|
||||
remaining -= readyQueueItems.size();
|
||||
readyQueueItems.clear();
|
||||
}
|
||||
|
||||
if (progress)
|
||||
{
|
||||
if (!progress(buildQueueItems.size() - remaining, buildQueueItems.size()))
|
||||
cancelled = true;
|
||||
}
|
||||
|
||||
// Items cannot be submitted while holding the lock
|
||||
for (size_t i : nextItems)
|
||||
sendItemTask(i);
|
||||
nextItems.clear();
|
||||
|
||||
if (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(buildQueueItems[*itemWithException]);
|
||||
}
|
||||
|
||||
// If we aren't done, but don't have anything processing, we hit a cycle
|
||||
if (remaining != 0 && processing == 0)
|
||||
sendCycleItemTask();
|
||||
}
|
||||
|
||||
std::vector<ModuleName> checkedModules;
|
||||
checkedModules.reserve(buildQueueItems.size());
|
||||
|
||||
for (size_t i = 0; i < buildQueueItems.size(); i++)
|
||||
checkedModules.push_back(std::move(buildQueueItems[i].name));
|
||||
|
||||
return checkedModules;
|
||||
}
|
||||
|
||||
std::optional<CheckResult> Frontend::getCheckResult(const ModuleName& name, bool accumulateNested, bool forAutocomplete)
|
||||
{
|
||||
if (FFlag::LuauSolverV2)
|
||||
|
@ -1351,13 +1102,27 @@ void Frontend::performQueueItemTask(std::shared_ptr<BuildQueueWorkState> state,
|
|||
{
|
||||
BuildQueueItem& item = state->buildQueueItems[itemPos];
|
||||
|
||||
try
|
||||
if (DFFlag::LuauRethrowKnownExceptions)
|
||||
{
|
||||
checkBuildQueueItem(item);
|
||||
try
|
||||
{
|
||||
checkBuildQueueItem(item);
|
||||
}
|
||||
catch (const Luau::InternalCompilerError&)
|
||||
{
|
||||
item.exception = std::current_exception();
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
else
|
||||
{
|
||||
item.exception = std::current_exception();
|
||||
try
|
||||
{
|
||||
checkBuildQueueItem(item);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
item.exception = std::current_exception();
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -1616,8 +1381,7 @@ ModulePtr check(
|
|||
result->interfaceTypes.owningModule = result.get();
|
||||
result->allocator = sourceModule.allocator;
|
||||
result->names = sourceModule.names;
|
||||
if (FFlag::LuauModuleHoldsAstRoot)
|
||||
result->root = sourceModule.root;
|
||||
result->root = sourceModule.root;
|
||||
|
||||
iceHandler->moduleName = sourceModule.name;
|
||||
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
#include "Luau/VisitType.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete)
|
||||
LUAU_FASTFLAGVARIABLE(LuauGeneralizationRemoveRecursiveUpperBound2)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -96,7 +95,7 @@ struct MutatingGeneralizer : TypeOnceVisitor
|
|||
LUAU_ASSERT(onlyType != haystack);
|
||||
emplaceType<BoundType>(asMutable(haystack), onlyType);
|
||||
}
|
||||
else if (FFlag::LuauGeneralizationRemoveRecursiveUpperBound2 && ut->options.empty())
|
||||
else if (ut->options.empty())
|
||||
{
|
||||
emplaceType<BoundType>(asMutable(haystack), builtinTypes->neverType);
|
||||
}
|
||||
|
@ -143,7 +142,7 @@ struct MutatingGeneralizer : TypeOnceVisitor
|
|||
LUAU_ASSERT(onlyType != needle);
|
||||
emplaceType<BoundType>(asMutable(needle), onlyType);
|
||||
}
|
||||
else if (FFlag::LuauGeneralizationRemoveRecursiveUpperBound2 && it->parts.empty())
|
||||
else if (it->parts.empty())
|
||||
{
|
||||
emplaceType<BoundType>(asMutable(needle), builtinTypes->unknownType);
|
||||
}
|
||||
|
|
|
@ -61,7 +61,7 @@ TypeId Instantiation::clean(TypeId ty)
|
|||
const FunctionType* ftv = log->getMutable<FunctionType>(ty);
|
||||
LUAU_ASSERT(ftv);
|
||||
|
||||
FunctionType clone = FunctionType{level, scope, ftv->argTypes, ftv->retTypes, ftv->definition, ftv->hasSelf};
|
||||
FunctionType clone = FunctionType{level, ftv->argTypes, ftv->retTypes, ftv->definition, ftv->hasSelf};
|
||||
clone.magic = ftv->magic;
|
||||
clone.tags = ftv->tags;
|
||||
clone.argNames = ftv->argNames;
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
#include <algorithm>
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2);
|
||||
LUAU_FASTFLAGVARIABLE(LuauIncrementalAutocompleteCommentDetection)
|
||||
LUAU_FASTFLAG(LuauRetainDefinitionAliasLocations)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -38,21 +38,6 @@ void resetLogLuauProc()
|
|||
logLuau = &defaultLogLuau;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static bool contains_DEPRECATED(Position pos, Comment comment)
|
||||
{
|
||||
if (comment.location.contains(pos))
|
||||
return true;
|
||||
else if (comment.type == Lexeme::BrokenComment && comment.location.begin <= pos) // Broken comments are broken specifically because they don't
|
||||
// have an end
|
||||
return true;
|
||||
else if (comment.type == Lexeme::Comment && comment.location.end == pos)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool contains(Position pos, Comment comment)
|
||||
{
|
||||
if (comment.location.contains(pos))
|
||||
|
@ -76,11 +61,8 @@ bool isWithinComment(const std::vector<Comment>& commentLocations, Position pos)
|
|||
Comment{Lexeme::Comment, Location{pos, pos}},
|
||||
[](const Comment& a, const Comment& b)
|
||||
{
|
||||
if (FFlag::LuauIncrementalAutocompleteCommentDetection)
|
||||
{
|
||||
if (a.type == Lexeme::Comment)
|
||||
return a.location.end.line < b.location.end.line;
|
||||
}
|
||||
if (a.type == Lexeme::Comment)
|
||||
return a.location.end.line < b.location.end.line;
|
||||
return a.location.end < b.location.end;
|
||||
}
|
||||
);
|
||||
|
@ -88,7 +70,7 @@ bool isWithinComment(const std::vector<Comment>& commentLocations, Position pos)
|
|||
if (iter == commentLocations.end())
|
||||
return false;
|
||||
|
||||
if (FFlag::LuauIncrementalAutocompleteCommentDetection ? contains(pos, *iter) : contains_DEPRECATED(pos, *iter))
|
||||
if (contains(pos, *iter))
|
||||
return true;
|
||||
|
||||
// Due to the nature of std::lower_bound, it is possible that iter points at a comment that ends
|
||||
|
@ -172,8 +154,6 @@ struct ClonePublicInterface : Substitution
|
|||
}
|
||||
|
||||
ftv->level = TypeLevel{0, 0};
|
||||
if (FFlag::LuauSolverV2)
|
||||
ftv->scope = nullptr;
|
||||
}
|
||||
else if (TableType* ttv = getMutable<TableType>(result))
|
||||
{
|
||||
|
@ -285,7 +265,10 @@ struct ClonePublicInterface : Substitution
|
|||
|
||||
TypeId type = cloneType(tf.type);
|
||||
|
||||
return TypeFun{typeParams, typePackParams, type};
|
||||
if (FFlag::LuauRetainDefinitionAliasLocations)
|
||||
return TypeFun{typeParams, typePackParams, type, tf.definitionLocation};
|
||||
else
|
||||
return TypeFun{typeParams, typePackParams, type};
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ LUAU_FASTFLAG(LuauSolverV2)
|
|||
LUAU_FASTFLAGVARIABLE(LuauFixInfiniteRecursionInNormalization)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNormalizedBufferIsNotUnknown)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNormalizeLimitFunctionSet)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNormalizationCatchMetatableCycles)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -3363,7 +3364,7 @@ NormalizationResult Normalizer::intersectNormalWithTy(
|
|||
return NormalizationResult::True;
|
||||
}
|
||||
|
||||
void makeTableShared(TypeId ty)
|
||||
void makeTableShared_DEPRECATED(TypeId ty)
|
||||
{
|
||||
ty = follow(ty);
|
||||
if (auto tableTy = getMutable<TableType>(ty))
|
||||
|
@ -3373,11 +3374,35 @@ void makeTableShared(TypeId ty)
|
|||
}
|
||||
else if (auto metatableTy = get<MetatableType>(ty))
|
||||
{
|
||||
makeTableShared(metatableTy->metatable);
|
||||
makeTableShared(metatableTy->table);
|
||||
makeTableShared_DEPRECATED(metatableTy->metatable);
|
||||
makeTableShared_DEPRECATED(metatableTy->table);
|
||||
}
|
||||
}
|
||||
|
||||
void makeTableShared(TypeId ty, DenseHashSet<TypeId>& seen)
|
||||
{
|
||||
ty = follow(ty);
|
||||
if (seen.contains(ty))
|
||||
return;
|
||||
seen.insert(ty);
|
||||
if (auto tableTy = getMutable<TableType>(ty))
|
||||
{
|
||||
for (auto& [_, prop] : tableTy->props)
|
||||
prop.makeShared();
|
||||
}
|
||||
else if (auto metatableTy = get<MetatableType>(ty))
|
||||
{
|
||||
makeTableShared(metatableTy->metatable, seen);
|
||||
makeTableShared(metatableTy->table, seen);
|
||||
}
|
||||
}
|
||||
|
||||
void makeTableShared(TypeId ty)
|
||||
{
|
||||
DenseHashSet<TypeId> seen{nullptr};
|
||||
makeTableShared(ty, seen);
|
||||
}
|
||||
|
||||
// -------- Convert back from a normalized type to a type
|
||||
TypeId Normalizer::typeFromNormal(const NormalizedType& norm)
|
||||
{
|
||||
|
@ -3477,7 +3502,10 @@ TypeId Normalizer::typeFromNormal(const NormalizedType& norm)
|
|||
result.reserve(result.size() + norm.tables.size());
|
||||
for (auto table : norm.tables)
|
||||
{
|
||||
makeTableShared(table);
|
||||
if (FFlag::LuauNormalizationCatchMetatableCycles)
|
||||
makeTableShared(table);
|
||||
else
|
||||
makeTableShared_DEPRECATED(table);
|
||||
result.push_back(table);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -454,7 +454,7 @@ SolveResult solveFunctionCall(
|
|||
|
||||
TypePackId resultPack = arena->freshTypePack(scope);
|
||||
|
||||
TypeId inferredTy = arena->addType(FunctionType{TypeLevel{}, scope.get(), argsPack, resultPack});
|
||||
TypeId inferredTy = arena->addType(FunctionType{TypeLevel{}, argsPack, resultPack});
|
||||
Unifier2 u2{NotNull{arena}, builtinTypes, scope, iceReporter};
|
||||
|
||||
const bool occursCheckPassed = u2.unify(*overloadToUse, inferredTy);
|
||||
|
|
|
@ -96,7 +96,7 @@ static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log, bool a
|
|||
return dest.addType(a);
|
||||
else if constexpr (std::is_same_v<T, FunctionType>)
|
||||
{
|
||||
FunctionType clone = FunctionType{a.level, a.scope, a.argTypes, a.retTypes, a.definition, a.hasSelf};
|
||||
FunctionType clone = FunctionType{a.level, a.argTypes, a.retTypes, a.definition, a.hasSelf};
|
||||
clone.generics = a.generics;
|
||||
clone.genericPacks = a.genericPacks;
|
||||
clone.magic = a.magic;
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
LUAU_FASTFLAGVARIABLE(LuauBidirectionalInferenceUpcast)
|
||||
LUAU_FASTFLAGVARIABLE(LuauBidirectionalInferenceCollectIndexerTypes)
|
||||
LUAU_FASTFLAGVARIABLE(LuauBidirectionalFailsafe)
|
||||
LUAU_FASTFLAGVARIABLE(LuauBidirectionalInferenceElideAssert)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -414,7 +415,8 @@ TypeId matchLiteralType(
|
|||
}
|
||||
else if (item.kind == AstExprTable::Item::List)
|
||||
{
|
||||
LUAU_ASSERT(tableTy->indexer);
|
||||
if (!FFlag::LuauBidirectionalInferenceCollectIndexerTypes || !FFlag::LuauBidirectionalInferenceElideAssert)
|
||||
LUAU_ASSERT(tableTy->indexer);
|
||||
|
||||
if (expectedTableTy->indexer)
|
||||
{
|
||||
|
|
|
@ -407,41 +407,6 @@ PendingTypePack* TxnLog::changeLevel(TypePackId tp, TypeLevel newLevel)
|
|||
return newTp;
|
||||
}
|
||||
|
||||
PendingType* TxnLog::changeScope(TypeId ty, NotNull<Scope> newScope)
|
||||
{
|
||||
LUAU_ASSERT(get<FreeType>(ty) || get<TableType>(ty) || get<FunctionType>(ty));
|
||||
|
||||
PendingType* newTy = queue(ty);
|
||||
if (FreeType* ftv = Luau::getMutable<FreeType>(newTy))
|
||||
{
|
||||
ftv->scope = newScope;
|
||||
}
|
||||
else if (TableType* ttv = Luau::getMutable<TableType>(newTy))
|
||||
{
|
||||
LUAU_ASSERT(ttv->state == TableState::Free || ttv->state == TableState::Generic);
|
||||
ttv->scope = newScope;
|
||||
}
|
||||
else if (FunctionType* ftv = Luau::getMutable<FunctionType>(newTy))
|
||||
{
|
||||
ftv->scope = newScope;
|
||||
}
|
||||
|
||||
return newTy;
|
||||
}
|
||||
|
||||
PendingTypePack* TxnLog::changeScope(TypePackId tp, NotNull<Scope> newScope)
|
||||
{
|
||||
LUAU_ASSERT(get<FreeTypePack>(tp));
|
||||
|
||||
PendingTypePack* newTp = queue(tp);
|
||||
if (FreeTypePack* ftp = Luau::getMutable<FreeTypePack>(newTp))
|
||||
{
|
||||
ftp->scope = newScope;
|
||||
}
|
||||
|
||||
return newTp;
|
||||
}
|
||||
|
||||
PendingType* TxnLog::changeIndexer(TypeId ty, std::optional<TableIndexer> indexer)
|
||||
{
|
||||
LUAU_ASSERT(get<TableType>(ty));
|
||||
|
|
|
@ -630,23 +630,6 @@ FunctionType::FunctionType(TypeLevel level, TypePackId argTypes, TypePackId retT
|
|||
{
|
||||
}
|
||||
|
||||
FunctionType::FunctionType(
|
||||
TypeLevel level,
|
||||
Scope* scope,
|
||||
TypePackId argTypes,
|
||||
TypePackId retTypes,
|
||||
std::optional<FunctionDefinition> defn,
|
||||
bool hasSelf
|
||||
)
|
||||
: definition(std::move(defn))
|
||||
, level(level)
|
||||
, scope(scope)
|
||||
, argTypes(argTypes)
|
||||
, retTypes(retTypes)
|
||||
, hasSelf(hasSelf)
|
||||
{
|
||||
}
|
||||
|
||||
FunctionType::FunctionType(
|
||||
std::vector<TypeId> generics,
|
||||
std::vector<TypePackId> genericPacks,
|
||||
|
@ -683,27 +666,6 @@ FunctionType::FunctionType(
|
|||
{
|
||||
}
|
||||
|
||||
FunctionType::FunctionType(
|
||||
TypeLevel level,
|
||||
Scope* scope,
|
||||
std::vector<TypeId> generics,
|
||||
std::vector<TypePackId> genericPacks,
|
||||
TypePackId argTypes,
|
||||
TypePackId retTypes,
|
||||
std::optional<FunctionDefinition> defn,
|
||||
bool hasSelf
|
||||
)
|
||||
: definition(std::move(defn))
|
||||
, generics(generics)
|
||||
, genericPacks(genericPacks)
|
||||
, level(level)
|
||||
, scope(scope)
|
||||
, argTypes(argTypes)
|
||||
, retTypes(retTypes)
|
||||
, hasSelf(hasSelf)
|
||||
{
|
||||
}
|
||||
|
||||
Property::Property() {}
|
||||
|
||||
Property::Property(
|
||||
|
|
|
@ -50,7 +50,6 @@ LUAU_FASTFLAGVARIABLE(DebugLuauLogTypeFamilies)
|
|||
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
|
||||
LUAU_FASTFLAG(LuauTypeFunResultInAutocomplete)
|
||||
LUAU_FASTFLAGVARIABLE(LuauMetatableTypeFunctions)
|
||||
LUAU_FASTFLAGVARIABLE(LuauClipNestedAndRecursiveUnion)
|
||||
LUAU_FASTFLAGVARIABLE(LuauIndexTypeFunctionImprovements)
|
||||
LUAU_FASTFLAGVARIABLE(LuauIndexTypeFunctionFunctionMetamethods)
|
||||
LUAU_FASTFLAGVARIABLE(LuauIntersectNotNil)
|
||||
|
@ -59,6 +58,7 @@ LUAU_FASTFLAGVARIABLE(LuauMetatablesHaveLength)
|
|||
LUAU_FASTFLAGVARIABLE(LuauDontForgetToReduceUnionFunc)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSearchForRefineableType)
|
||||
LUAU_FASTFLAGVARIABLE(LuauIndexAnyIsAny)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFixCyclicIndexInIndexer)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSimplyRefineNotNil)
|
||||
LUAU_FASTFLAGVARIABLE(LuauIndexDeferPendingIndexee)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNewTypeFunReductionChecks2)
|
||||
|
@ -2772,7 +2772,19 @@ bool searchPropsAndIndexer(
|
|||
// index into tbl's indexer
|
||||
if (tblIndexer)
|
||||
{
|
||||
if (isSubtype(ty, tblIndexer->indexType, ctx->scope, ctx->builtins, ctx->simplifier, *ctx->ice))
|
||||
TypeId indexType = FFlag::LuauFixCyclicIndexInIndexer ? follow(tblIndexer->indexType) : tblIndexer->indexType;
|
||||
|
||||
if (FFlag::LuauFixCyclicIndexInIndexer)
|
||||
{
|
||||
if (auto tfit = get<TypeFunctionInstanceType>(indexType))
|
||||
{
|
||||
// if we have an index function here, it means we're in a cycle, so let's see if it's well-founded if we tie the knot
|
||||
if (tfit->function.get() == &builtinTypeFunctions().indexFunc)
|
||||
indexType = follow(tblIndexer->indexResultType);
|
||||
}
|
||||
}
|
||||
|
||||
if (isSubtype(ty, indexType, ctx->scope, ctx->builtins, ctx->simplifier, *ctx->ice))
|
||||
{
|
||||
TypeId idxResultTy = follow(tblIndexer->indexResultType);
|
||||
|
||||
|
|
|
@ -34,8 +34,7 @@ LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification)
|
|||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
LUAU_FASTFLAG(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
|
||||
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
|
||||
|
||||
LUAU_FASTFLAG(LuauModuleHoldsAstRoot)
|
||||
LUAU_FASTFLAG(LuauRetainDefinitionAliasLocations)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -256,8 +255,7 @@ 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;
|
||||
currentModule->root = module.root;
|
||||
|
||||
iceHandler->moduleName = module.name;
|
||||
normalizer.arena = ¤tModule->internalTypes;
|
||||
|
@ -1658,7 +1656,10 @@ void TypeChecker::prototype(const ScopePtr& scope, const AstStatTypeAlias& typea
|
|||
FreeType* ftv = getMutable<FreeType>(ty);
|
||||
LUAU_ASSERT(ftv);
|
||||
ftv->forwardedTypeAlias = true;
|
||||
bindingsMap[name] = {std::move(generics), std::move(genericPacks), ty};
|
||||
if (FFlag::LuauRetainDefinitionAliasLocations)
|
||||
bindingsMap[name] = {std::move(generics), std::move(genericPacks), ty, typealias.location};
|
||||
else
|
||||
bindingsMap[name] = {std::move(generics), std::move(genericPacks), ty};
|
||||
|
||||
scope->typeAliasLocations[name] = typealias.location;
|
||||
scope->typeAliasNameLocations[name] = typealias.nameLocation;
|
||||
|
@ -1703,7 +1704,10 @@ void TypeChecker::prototype(const ScopePtr& scope, const AstStatDeclareClass& de
|
|||
TypeId metaTy = addType(TableType{TableState::Sealed, scope->level});
|
||||
|
||||
ctv->metatable = metaTy;
|
||||
scope->exportedTypeBindings[className] = TypeFun{{}, classTy};
|
||||
if (FFlag::LuauRetainDefinitionAliasLocations)
|
||||
scope->exportedTypeBindings[className] = TypeFun{{}, classTy, declaredClass.location};
|
||||
else
|
||||
scope->exportedTypeBindings[className] = TypeFun{{}, classTy};
|
||||
}
|
||||
|
||||
ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatDeclareClass& declaredClass)
|
||||
|
|
|
@ -433,9 +433,6 @@ bool Unifier2::unify(TableType* subTable, const TableType* superTable)
|
|||
superTypePackParamsIter++;
|
||||
}
|
||||
|
||||
if (subTable->selfTy && superTable->selfTy)
|
||||
result &= unify(*subTable->selfTy, *superTable->selfTy);
|
||||
|
||||
if (subTable->indexer && superTable->indexer)
|
||||
{
|
||||
result &= unify(subTable->indexer->indexType, superTable->indexer->indexType);
|
||||
|
|
|
@ -4,7 +4,7 @@ if(EXT_PLATFORM_STRING)
|
|||
return()
|
||||
endif()
|
||||
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
option(LUAU_BUILD_CLI "Build CLI" ON)
|
||||
option(LUAU_BUILD_TESTS "Build tests" ON)
|
||||
|
|
|
@ -443,7 +443,7 @@ static void shrinkstack(lua_State* L)
|
|||
|
||||
if (3 * size_t(s_used) < size_t(L->stacksize) && 2 * (BASIC_STACK_SIZE + EXTRA_STACK) < L->stacksize)
|
||||
luaD_reallocstack(L, L->stacksize / 2, 0); // still big enough...
|
||||
condhardstacktests(luaD_reallocstack(L, s_used));
|
||||
condhardstacktests(luaD_reallocstack(L, s_used, 0));
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -76,7 +76,7 @@
|
|||
|
||||
#define luaC_checkGC(L) \
|
||||
{ \
|
||||
condhardstacktests(luaD_reallocstack(L, L->stacksize - EXTRA_STACK)); \
|
||||
condhardstacktests(luaD_reallocstack(L, L->stacksize - EXTRA_STACK, 0)); \
|
||||
if (luaC_needsGC(L)) \
|
||||
{ \
|
||||
condhardmemtests(luaC_validate(L), 1); \
|
||||
|
|
|
@ -25,7 +25,6 @@
|
|||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete)
|
||||
LUAU_FASTFLAG(LuauIncrementalAutocompleteCommentDetection)
|
||||
LUAU_FASTINT(LuauParseErrorLimit)
|
||||
LUAU_FASTFLAG(LuauCloneIncrementalModule)
|
||||
|
||||
|
@ -36,7 +35,6 @@ LUAU_FASTFLAG(LuauBetterReverseDependencyTracking)
|
|||
LUAU_FASTFLAG(LuauAutocompleteUsesModuleForTypeCompatibility)
|
||||
LUAU_FASTFLAG(LuauBetterCursorInCommentDetection)
|
||||
LUAU_FASTFLAG(LuauAllFreeTypesHaveScopes)
|
||||
LUAU_FASTFLAG(LuauModuleHoldsAstRoot)
|
||||
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
|
||||
LUAU_FASTFLAG(LuauClonedTableAndFunctionTypesMustHaveScopes)
|
||||
LUAU_FASTFLAG(LuauDisableNewSolverAssertsInMixedMode)
|
||||
|
@ -46,6 +44,7 @@ LUAU_FASTFLAG(LuauCloneReturnTypePack)
|
|||
LUAU_FASTFLAG(LuauIncrementalAutocompleteDemandBasedCloning)
|
||||
LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
|
||||
LUAU_FASTFLAG(LuauBetterScopeSelection)
|
||||
LUAU_FASTFLAG(LuauBlockDiffFragmentSelection)
|
||||
|
||||
static std::optional<AutocompleteEntryMap> nullCallback(std::string tag, std::optional<const ClassType*> ptr, std::optional<std::string> contents)
|
||||
{
|
||||
|
@ -79,7 +78,6 @@ struct FragmentAutocompleteFixtureImpl : BaseType
|
|||
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};
|
||||
|
@ -87,6 +85,8 @@ struct FragmentAutocompleteFixtureImpl : BaseType
|
|||
ScopedFastFlag luauCloneReturnTypePack{FFlag::LuauCloneReturnTypePack, true};
|
||||
ScopedFastFlag luauIncrementalAutocompleteDemandBasedCloning{FFlag::LuauIncrementalAutocompleteDemandBasedCloning, true};
|
||||
ScopedFastFlag luauBetterScopeSelection{FFlag::LuauBetterScopeSelection, true};
|
||||
ScopedFastFlag luauBlockDiffFragmentSelection{FFlag::LuauBlockDiffFragmentSelection, true};
|
||||
ScopedFastFlag luauAutocompleteUsesModuleForTypeCompatibility{FFlag::LuauAutocompleteUsesModuleForTypeCompatibility, true};
|
||||
|
||||
FragmentAutocompleteFixtureImpl()
|
||||
: BaseType(true)
|
||||
|
@ -98,15 +98,20 @@ struct FragmentAutocompleteFixtureImpl : BaseType
|
|||
return this->check(source, getOptions());
|
||||
}
|
||||
|
||||
ParseResult parseHelper(std::string document)
|
||||
ParseResult parseHelper_(SourceModule& source, std::string document)
|
||||
{
|
||||
SourceModule& source = getSource();
|
||||
ParseOptions parseOptions;
|
||||
parseOptions.captureComments = true;
|
||||
ParseResult parseResult = Parser::parse(document.c_str(), document.length(), *source.names, *source.allocator, parseOptions);
|
||||
return parseResult;
|
||||
}
|
||||
|
||||
ParseResult parseHelper(std::string document)
|
||||
{
|
||||
SourceModule& source = getSource();
|
||||
return parseHelper_(source, document);
|
||||
}
|
||||
|
||||
FragmentAutocompleteAncestryResult runAutocompleteVisitor(const std::string& source, const Position& cursorPos)
|
||||
{
|
||||
ParseResult p = this->tryParse(source); // We don't care about parsing incomplete asts
|
||||
|
@ -1332,7 +1337,7 @@ t
|
|||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
// NOLINTEND(bugprone-unchecked-optional-access)
|
||||
|
||||
|
||||
TEST_SUITE_BEGIN("FragmentAutocompleteTypeCheckerTests");
|
||||
|
||||
|
@ -1468,27 +1473,6 @@ tbl.
|
|||
CHECK_EQ(AutocompleteContext::Property, fragment.result->acResults.context);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "typecheck_fragment_handles_stale_module")
|
||||
{
|
||||
ScopedFastFlag sff(FFlag::LuauModuleHoldsAstRoot, false);
|
||||
const std::string sourceName = "MainModule";
|
||||
fileResolver.source[sourceName] = "local x = 5";
|
||||
|
||||
CheckResult checkResult = frontend.check(sourceName, getOptions());
|
||||
LUAU_REQUIRE_NO_ERRORS(checkResult);
|
||||
|
||||
auto [result, _] = typecheckFragmentForModule(sourceName, fileResolver.source[sourceName], Luau::Position(0, 0));
|
||||
CHECK_EQ(result, FragmentTypeCheckStatus::Success);
|
||||
|
||||
frontend.markDirty(sourceName);
|
||||
frontend.parse(sourceName);
|
||||
|
||||
CHECK_NE(frontend.getSourceModule(sourceName), nullptr);
|
||||
|
||||
auto [result2, __] = typecheckFragmentForModule(sourceName, fileResolver.source[sourceName], Luau::Position(0, 0));
|
||||
CHECK_EQ(result2, FragmentTypeCheckStatus::SkipAutocomplete);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "typecheck_fragment_handles_unusable_module")
|
||||
{
|
||||
const std::string sourceA = "MainModule";
|
||||
|
@ -2553,7 +2537,7 @@ l
|
|||
|
||||
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "do_not_recommend_results_in_multiline_comment")
|
||||
{
|
||||
ScopedFastFlag sff[] = {{FFlag::LuauIncrementalAutocompleteCommentDetection, true}, {FFlag::LuauBetterCursorInCommentDetection, true}};
|
||||
ScopedFastFlag sff = {FFlag::LuauBetterCursorInCommentDetection, true};
|
||||
std::string source = R"(--[[
|
||||
)";
|
||||
std::string dest = R"(--[[
|
||||
|
@ -2574,7 +2558,7 @@ a
|
|||
|
||||
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "no_recs_for_comments_simple")
|
||||
{
|
||||
ScopedFastFlag sff[] = {{FFlag::LuauIncrementalAutocompleteCommentDetection, true}, {FFlag::LuauBetterCursorInCommentDetection, true}};
|
||||
ScopedFastFlag sff = {FFlag::LuauBetterCursorInCommentDetection, true};
|
||||
const std::string source = R"(
|
||||
-- sel
|
||||
-- retur
|
||||
|
@ -2655,7 +2639,7 @@ baz
|
|||
|
||||
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "no_recs_for_comments")
|
||||
{
|
||||
ScopedFastFlag sff[] = {{FFlag::LuauIncrementalAutocompleteCommentDetection, true}, {FFlag::LuauBetterCursorInCommentDetection, true}};
|
||||
ScopedFastFlag sff = {FFlag::LuauBetterCursorInCommentDetection, true};
|
||||
const std::string source = R"(
|
||||
-- sel
|
||||
-- retur
|
||||
|
@ -2735,7 +2719,7 @@ if x == 5
|
|||
local x = 5
|
||||
if x == 5 then -- a comment
|
||||
)";
|
||||
ScopedFastFlag sff[] = {{FFlag::LuauIncrementalAutocompleteCommentDetection, true}, {FFlag::LuauBetterCursorInCommentDetection, true}};
|
||||
ScopedFastFlag sff = {FFlag::LuauBetterCursorInCommentDetection, true};
|
||||
autocompleteFragmentInBothSolvers(
|
||||
source,
|
||||
updated,
|
||||
|
@ -2770,22 +2754,6 @@ type A = <>random non code text here
|
|||
);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "fragment_autocomplete_handles_stale_module")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauModuleHoldsAstRoot, false};
|
||||
const std::string sourceName = "MainModule";
|
||||
fileResolver.source[sourceName] = "local x = 5";
|
||||
|
||||
frontend.check(sourceName, getOptions());
|
||||
frontend.markDirty(sourceName);
|
||||
frontend.parse(sourceName);
|
||||
|
||||
FragmentAutocompleteStatusResult frag = autocompleteFragmentForModule(sourceName, fileResolver.source[sourceName], Luau::Position(0, 0));
|
||||
REQUIRE(frag.result);
|
||||
CHECK(frag.result->acResults.entryMap.empty());
|
||||
CHECK_EQ(frag.result->incrementalModule, nullptr);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "require_tracing")
|
||||
{
|
||||
fileResolver.source["MainModule/A"] = R"(
|
||||
|
@ -3085,7 +3053,6 @@ z = a.P.E
|
|||
|
||||
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()
|
||||
|
@ -3269,5 +3236,363 @@ end)
|
|||
);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "differ_1")
|
||||
{
|
||||
const std::string source = R"()";
|
||||
const std::string dest = R"(local tbl = { foo = 1, bar = 2 };
|
||||
tbl.b)";
|
||||
|
||||
autocompleteFragmentInBothSolvers(
|
||||
source,
|
||||
dest,
|
||||
Position{1, 5},
|
||||
[](FragmentAutocompleteStatusResult& status)
|
||||
{
|
||||
CHECK(FragmentAutocompleteStatus::Success == status.status);
|
||||
REQUIRE(status.result);
|
||||
CHECK(!status.result->acResults.entryMap.empty());
|
||||
CHECK(status.result->acResults.entryMap.count("foo"));
|
||||
CHECK(status.result->acResults.entryMap.count("bar"));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "block_diff_test_both_empty")
|
||||
{
|
||||
SourceModule stale;
|
||||
SourceModule fresh;
|
||||
ParseResult o = parseHelper_(stale, R"()");
|
||||
ParseResult n = parseHelper_(stale, R"()");
|
||||
auto pos = Luau::blockDiffStart(o.root, n.root, nullptr);
|
||||
CHECK(pos == std::nullopt);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "block_diff_test_both_empty_e2e")
|
||||
{
|
||||
const std::string source = R"()";
|
||||
|
||||
autocompleteFragmentInBothSolvers(
|
||||
source,
|
||||
source,
|
||||
Position{0, 0},
|
||||
[](FragmentAutocompleteStatusResult& result)
|
||||
{
|
||||
CHECK(FragmentAutocompleteStatus::Success == result.status);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "block_diff_added_locals_1")
|
||||
{
|
||||
SourceModule stale;
|
||||
SourceModule fresh;
|
||||
ParseResult o = parseHelper_(stale, R"()");
|
||||
ParseResult n = parseHelper_(fresh, R"(local x = 4
|
||||
local y = 3
|
||||
local z = 3)");
|
||||
auto pos = Luau::blockDiffStart(o.root, n.root, n.root->body.data[2]);
|
||||
REQUIRE(pos);
|
||||
CHECK(*pos == Position{0, 0});
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "block_diff_added_locals_1_e2e")
|
||||
{
|
||||
const std::string source = R"()";
|
||||
const std::string dest = R"(local f1 = 4
|
||||
local f2 = "a"
|
||||
local f3 = f
|
||||
)";
|
||||
|
||||
autocompleteFragmentInBothSolvers(
|
||||
source,
|
||||
dest,
|
||||
Position{2, 12},
|
||||
[](FragmentAutocompleteStatusResult& result)
|
||||
{
|
||||
CHECK(FragmentAutocompleteStatus::Success == result.status);
|
||||
REQUIRE(result.result);
|
||||
CHECK(!result.result->acResults.entryMap.empty());
|
||||
CHECK(result.result->acResults.entryMap.count("f1"));
|
||||
CHECK(result.result->acResults.entryMap.count("f2"));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "block_diff_added_locals_1_e2e_in_the_middle")
|
||||
{
|
||||
const std::string source = R"()";
|
||||
const std::string dest = R"(local f1 = 4
|
||||
local f2 = f
|
||||
local f3 = f
|
||||
)";
|
||||
|
||||
autocompleteFragmentInBothSolvers(
|
||||
source,
|
||||
dest,
|
||||
Position{1, 12},
|
||||
[](FragmentAutocompleteStatusResult& result)
|
||||
{
|
||||
CHECK(FragmentAutocompleteStatus::Success == result.status);
|
||||
REQUIRE(result.result);
|
||||
CHECK(!result.result->acResults.entryMap.empty());
|
||||
CHECK(result.result->acResults.entryMap.count("f1"));
|
||||
CHECK(!result.result->acResults.entryMap.count("f3"));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "block_diff_added_locals_2")
|
||||
{
|
||||
SourceModule stale;
|
||||
SourceModule fresh;
|
||||
ParseResult o = parseHelper_(stale, R"(local x = 4)");
|
||||
ParseResult n = parseHelper_(fresh, R"(local x = 4
|
||||
local y = 3
|
||||
local z = 3)");
|
||||
auto pos = Luau::blockDiffStart(o.root, n.root, n.root->body.data[1]);
|
||||
REQUIRE(pos);
|
||||
CHECK(*pos == Position{1, 0});
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "block_diff_added_locals_3")
|
||||
{
|
||||
SourceModule stale;
|
||||
SourceModule fresh;
|
||||
ParseResult o = parseHelper_(stale, R"(local x = 4
|
||||
local y = 2 + 1)");
|
||||
ParseResult n = parseHelper_(fresh, R"(local x = 4
|
||||
local y = 3
|
||||
local z = 3
|
||||
local foo = 8)");
|
||||
auto pos = Luau::blockDiffStart(o.root, n.root, n.root->body.data[3]);
|
||||
REQUIRE(pos);
|
||||
CHECK(*pos == Position{1, 0});
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "block_diff_added_locals_3")
|
||||
{
|
||||
const std::string source = R"(local f1 = 4
|
||||
local f2 = 2 + 1)";
|
||||
const std::string dest = R"(local f1 = 4
|
||||
local f2 = 3
|
||||
local f3 = 3
|
||||
local foo = 8 + )";
|
||||
autocompleteFragmentInBothSolvers(
|
||||
source,
|
||||
dest,
|
||||
Position{3, 16},
|
||||
[](FragmentAutocompleteStatusResult& result)
|
||||
{
|
||||
CHECK(FragmentAutocompleteStatus::Success == result.status);
|
||||
REQUIRE(result.result);
|
||||
CHECK(!result.result->acResults.entryMap.empty());
|
||||
CHECK(result.result->acResults.entryMap.count("f1"));
|
||||
CHECK(result.result->acResults.entryMap.count("f2"));
|
||||
CHECK(result.result->acResults.entryMap.count("f3"));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "block_diff_added_locals_fake_similarity")
|
||||
{
|
||||
// Captures the bad behaviour of block based diffs
|
||||
SourceModule stale;
|
||||
SourceModule fresh;
|
||||
ParseResult o = parseHelper_(stale, R"(local x = 4
|
||||
local y = true
|
||||
local z = 2 + 1)");
|
||||
ParseResult n = parseHelper_(fresh, R"(local x = 4
|
||||
local y = "tr"
|
||||
local z = 3
|
||||
local foo = 8)");
|
||||
auto pos = Luau::blockDiffStart(o.root, n.root, n.root->body.data[2]);
|
||||
REQUIRE(pos);
|
||||
CHECK(*pos == Position{2, 0});
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "TypeCorrectLocalReturn_assert")
|
||||
{
|
||||
const std::string source = R"()";
|
||||
const std::string dest = R"(local function target(a: number, b: string) return a + #b end
|
||||
local function bar1(a: string) reutrn a .. 'x' end
|
||||
local function bar2(a: number) return -a end
|
||||
return target(bar)";
|
||||
|
||||
autocompleteFragmentInBothSolvers(
|
||||
source,
|
||||
dest,
|
||||
Position{3, 17},
|
||||
[](FragmentAutocompleteStatusResult& status)
|
||||
{
|
||||
CHECK(FragmentAutocompleteStatus::Success == status.status);
|
||||
REQUIRE(status.result);
|
||||
CHECK(!status.result->acResults.entryMap.empty());
|
||||
CHECK(status.result->acResults.entryMap.count("bar1"));
|
||||
CHECK(status.result->acResults.entryMap.count("bar2"));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "TypeCorrectLocalRank_assert")
|
||||
{
|
||||
const std::string source = R"()";
|
||||
const std::string dest = R"(local function target(a: number, b: string) return a + #b end
|
||||
local bar1 = 'hello'
|
||||
local bar2 = 4
|
||||
return target(bar)";
|
||||
|
||||
autocompleteFragmentInBothSolvers(
|
||||
source,
|
||||
dest,
|
||||
Position{3, 17},
|
||||
[](FragmentAutocompleteStatusResult& status)
|
||||
{
|
||||
CHECK(FragmentAutocompleteStatus::Success == status.status);
|
||||
REQUIRE(status.result);
|
||||
CHECK(!status.result->acResults.entryMap.empty());
|
||||
CHECK(status.result->acResults.entryMap.count("bar1"));
|
||||
CHECK(status.result->acResults.entryMap.count("bar2"));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "str_metata_table_finished_defining")
|
||||
{
|
||||
const std::string source = R"(local function foobar(): string return "" end
|
||||
local foo = f)";
|
||||
const std::string dest = R"(local function foobar(): string return "" end
|
||||
local foo = foobar()
|
||||
foo:)";
|
||||
|
||||
autocompleteFragmentInBothSolvers(
|
||||
source,
|
||||
dest,
|
||||
Position{2, 4},
|
||||
[](FragmentAutocompleteStatusResult& res)
|
||||
{
|
||||
CHECK(FragmentAutocompleteStatus::Success == res.status);
|
||||
REQUIRE(res.result);
|
||||
CHECK(!res.result->acResults.entryMap.empty());
|
||||
CHECK(res.result->acResults.entryMap.count("len"));
|
||||
CHECK(res.result->acResults.entryMap.count("gsub"));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "str_metata_table_redef")
|
||||
{
|
||||
const std::string source = R"(local x = 42)";
|
||||
const std::string dest = R"(local x = 42
|
||||
local x = ""
|
||||
x:)";
|
||||
|
||||
autocompleteFragmentInBothSolvers(
|
||||
source,
|
||||
dest,
|
||||
Position{2, 2},
|
||||
[](FragmentAutocompleteStatusResult& res)
|
||||
{
|
||||
CHECK(FragmentAutocompleteStatus::Success == res.status);
|
||||
REQUIRE(res.result);
|
||||
CHECK(!res.result->acResults.entryMap.empty());
|
||||
CHECK(res.result->acResults.entryMap.count("len"));
|
||||
CHECK(res.result->acResults.entryMap.count("gsub"));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "diff_multiple_blocks_on_same_line")
|
||||
{
|
||||
const std::string source = R"(
|
||||
do local function foo() end; local x = ""; end do local function bar() end)";
|
||||
const std::string dest = R"(
|
||||
do local function foo() end; local x = ""; end do local function bar() end local x = {a : number}; b end )";
|
||||
|
||||
autocompleteFragmentInBothSolvers(
|
||||
source,
|
||||
dest,
|
||||
Position{1, 101},
|
||||
[](FragmentAutocompleteStatusResult& res)
|
||||
{
|
||||
CHECK(FragmentAutocompleteStatus::Success == res.status);
|
||||
REQUIRE(res.result);
|
||||
CHECK(!res.result->acResults.entryMap.empty());
|
||||
CHECK(res.result->acResults.entryMap.count("bar"));
|
||||
CHECK(!res.result->acResults.entryMap.count("foo"));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "nested_blocks_else_simple")
|
||||
{
|
||||
const std::string source = R"(
|
||||
local function foo(t : {foo : string})
|
||||
local x = t.foo
|
||||
do
|
||||
if t then
|
||||
end
|
||||
end
|
||||
end
|
||||
)";
|
||||
const std::string dest = R"(
|
||||
local function foo(t : {foo : string})
|
||||
local x = t.foo
|
||||
do
|
||||
if t then
|
||||
x:
|
||||
end
|
||||
end
|
||||
end
|
||||
)";
|
||||
autocompleteFragmentInBothSolvers(
|
||||
source,
|
||||
dest,
|
||||
Position{5, 14},
|
||||
[](FragmentAutocompleteStatusResult& res)
|
||||
{
|
||||
CHECK(FragmentAutocompleteStatus::Success == res.status);
|
||||
REQUIRE(res.result);
|
||||
CHECK(!res.result->acResults.entryMap.empty());
|
||||
CHECK(res.result->acResults.entryMap.count("gsub"));
|
||||
CHECK(res.result->acResults.entryMap.count("len"));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "nested_blocks_else_difficult_2")
|
||||
{
|
||||
const std::string source = R"(
|
||||
local function foo(t : {foo : number})
|
||||
do
|
||||
if t then
|
||||
end
|
||||
end
|
||||
end
|
||||
)";
|
||||
const std::string dest = R"(
|
||||
local function foo(t : {foo : number})
|
||||
do
|
||||
if t then
|
||||
else
|
||||
local x = 4
|
||||
return x + t.
|
||||
end
|
||||
end
|
||||
end
|
||||
)";
|
||||
autocompleteFragmentInBothSolvers(
|
||||
source,
|
||||
dest,
|
||||
Position{6, 24},
|
||||
[](FragmentAutocompleteStatusResult& res)
|
||||
{
|
||||
CHECK(FragmentAutocompleteStatus::Success == res.status);
|
||||
REQUIRE(res.result);
|
||||
CHECK(!res.result->acResults.entryMap.empty());
|
||||
CHECK(res.result->acResults.entryMap.count("foo"));
|
||||
}
|
||||
);
|
||||
}
|
||||
// NOLINTEND(bugprone-unchecked-optional-access)
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -17,7 +17,6 @@ LUAU_FASTFLAG(LuauSolverV2);
|
|||
LUAU_FASTFLAG(DebugLuauFreezeArena)
|
||||
LUAU_FASTFLAG(DebugLuauMagicTypes)
|
||||
LUAU_FASTFLAG(LuauSelectivelyRetainDFGArena)
|
||||
LUAU_FASTFLAG(LuauModuleHoldsAstRoot)
|
||||
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
|
||||
|
||||
namespace
|
||||
|
@ -1555,7 +1554,6 @@ TEST_CASE_FIXTURE(FrontendFixture, "check_module_references_allocator")
|
|||
|
||||
TEST_CASE_FIXTURE(FrontendFixture, "check_module_references_correct_ast_root")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauModuleHoldsAstRoot, true};
|
||||
fileResolver.source["game/workspace/MyScript"] = R"(
|
||||
print("Hello World")
|
||||
)";
|
||||
|
@ -1793,4 +1791,96 @@ TEST_CASE_FIXTURE(FrontendFixture, "test_invalid_dependency_tracking_per_module_
|
|||
CHECK(frontend.allModuleDependenciesValid("game/Gui/Modules/A", opts.forAutocomplete));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(FrontendFixture, "queue_check_simple")
|
||||
{
|
||||
fileResolver.source["game/Gui/Modules/A"] = R"(
|
||||
--!strict
|
||||
return {hello=5, world=true}
|
||||
)";
|
||||
fileResolver.source["game/Gui/Modules/B"] = R"(
|
||||
--!strict
|
||||
local Modules = game:GetService('Gui').Modules
|
||||
local A = require(Modules.A)
|
||||
return {b_value = A.hello}
|
||||
)";
|
||||
|
||||
frontend.queueModuleCheck("game/Gui/Modules/B");
|
||||
frontend.checkQueuedModules();
|
||||
|
||||
auto result = frontend.getCheckResult("game/Gui/Modules/B", true);
|
||||
REQUIRE(result);
|
||||
LUAU_REQUIRE_NO_ERRORS(*result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(FrontendFixture, "queue_check_cycle_instant")
|
||||
{
|
||||
fileResolver.source["game/Gui/Modules/A"] = R"(
|
||||
--!strict
|
||||
local Modules = game:GetService('Gui').Modules
|
||||
local B = require(Modules.B)
|
||||
return {a_value = B.hello}
|
||||
)";
|
||||
fileResolver.source["game/Gui/Modules/B"] = R"(
|
||||
--!strict
|
||||
local Modules = game:GetService('Gui').Modules
|
||||
local A = require(Modules.A)
|
||||
return {b_value = A.hello}
|
||||
)";
|
||||
|
||||
frontend.queueModuleCheck("game/Gui/Modules/B");
|
||||
frontend.checkQueuedModules();
|
||||
|
||||
auto result = frontend.getCheckResult("game/Gui/Modules/B", true);
|
||||
REQUIRE(result);
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, *result);
|
||||
CHECK(toString(result->errors[0]) == "Cyclic module dependency: game/Gui/Modules/B -> game/Gui/Modules/A");
|
||||
CHECK(toString(result->errors[1]) == "Cyclic module dependency: game/Gui/Modules/A -> game/Gui/Modules/B");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(FrontendFixture, "queue_check_cycle_delayed")
|
||||
{
|
||||
fileResolver.source["game/Gui/Modules/C"] = R"(
|
||||
--!strict
|
||||
return {c_value = 5}
|
||||
)";
|
||||
fileResolver.source["game/Gui/Modules/A"] = R"(
|
||||
--!strict
|
||||
local Modules = game:GetService('Gui').Modules
|
||||
local C = require(Modules.C)
|
||||
local B = require(Modules.B)
|
||||
return {a_value = B.hello + C.c_value}
|
||||
)";
|
||||
fileResolver.source["game/Gui/Modules/B"] = R"(
|
||||
--!strict
|
||||
local Modules = game:GetService('Gui').Modules
|
||||
local C = require(Modules.C)
|
||||
local A = require(Modules.A)
|
||||
return {b_value = A.hello + C.c_value}
|
||||
)";
|
||||
|
||||
frontend.queueModuleCheck("game/Gui/Modules/B");
|
||||
frontend.checkQueuedModules();
|
||||
|
||||
auto result = frontend.getCheckResult("game/Gui/Modules/B", true);
|
||||
REQUIRE(result);
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, *result);
|
||||
CHECK(toString(result->errors[0]) == "Cyclic module dependency: game/Gui/Modules/B -> game/Gui/Modules/A");
|
||||
CHECK(toString(result->errors[1]) == "Cyclic module dependency: game/Gui/Modules/A -> game/Gui/Modules/B");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(FrontendFixture, "queue_check_propagates_ice")
|
||||
{
|
||||
ScopedFastFlag sffs{FFlag::DebugLuauMagicTypes, true};
|
||||
|
||||
ModuleName mm = fromString("MainModule");
|
||||
fileResolver.source[mm] = R"(
|
||||
--!strict
|
||||
local a: _luau_ice = 55
|
||||
)";
|
||||
frontend.markDirty(mm);
|
||||
frontend.queueModuleCheck("MainModule");
|
||||
|
||||
CHECK_THROWS_AS(frontend.checkQueuedModules(), InternalCompilerError);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -15,6 +15,9 @@
|
|||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(DebugLuauForbidInternalTypes)
|
||||
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
|
||||
LUAU_FASTFLAG(LuauTrackInferredFunctionTypeFromCall)
|
||||
|
||||
TEST_SUITE_BEGIN("Generalization");
|
||||
|
||||
|
@ -250,4 +253,42 @@ end
|
|||
)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "generalization_should_not_leak_free_type")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::DebugLuauForbidInternalTypes, true},
|
||||
{FFlag::LuauTrackInteriorFreeTypesOnScope, true},
|
||||
{FFlag::LuauTrackInferredFunctionTypeFromCall, true}
|
||||
};
|
||||
|
||||
// This test case should just not assert
|
||||
CheckResult result = check(R"(
|
||||
function foo()
|
||||
|
||||
local productButtonPairs = {}
|
||||
local func
|
||||
local dir = -1
|
||||
|
||||
local function updateSearch()
|
||||
for product, button in pairs(productButtonPairs) do
|
||||
-- This line may have a floating free type pack.
|
||||
button.LayoutOrder = func(product) * dir
|
||||
end
|
||||
end
|
||||
|
||||
function(mode)
|
||||
if mode == 'New'then
|
||||
func = function(p)
|
||||
return p.id
|
||||
end
|
||||
elseif mode == 'Price'then
|
||||
func = function(p)
|
||||
return p.price
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -18,6 +18,7 @@ LUAU_FASTINT(LuauNormalizeIntersectionLimit)
|
|||
LUAU_FASTINT(LuauNormalizeUnionLimit)
|
||||
LUAU_FASTFLAG(LuauNormalizeLimitFunctionSet)
|
||||
LUAU_FASTFLAG(LuauSubtypingStopAtNormFail)
|
||||
LUAU_FASTFLAG(LuauNormalizationCatchMetatableCycles)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
|
@ -1072,6 +1073,19 @@ TEST_CASE_FIXTURE(NormalizeFixture, "free_type_and_not_truthy")
|
|||
CHECK("'a & (false?)" == toString(result));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "normalize_recursive_metatable")
|
||||
{
|
||||
ScopedFastFlag sff[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauNormalizationCatchMetatableCycles, true}};
|
||||
|
||||
TypeId root = arena.addType(BlockedType{});
|
||||
TypeId emptyTable = arena.addType(TableType(TableState::Sealed, {}));
|
||||
TypeId metatable = arena.addType(MetatableType{emptyTable, root});
|
||||
emplaceType<BoundType>(asMutable(root), metatable);
|
||||
auto normalized = normalizer.normalize(root);
|
||||
REQUIRE(normalized);
|
||||
CHECK_EQ("t1 where t1 = { @metatable t1, { } }", toString(normalizer.typeFromNormal(*normalized)));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "normalizer_should_be_able_to_detect_cyclic_tables_and_not_stack_overflow")
|
||||
{
|
||||
if (!FFlag::LuauSolverV2)
|
||||
|
|
|
@ -20,6 +20,8 @@ LUAU_FASTFLAG(LuauMetatableTypeFunctions)
|
|||
LUAU_FASTFLAG(LuauMetatablesHaveLength)
|
||||
LUAU_FASTFLAG(LuauIndexAnyIsAny)
|
||||
LUAU_FASTFLAG(LuauNewTypeFunReductionChecks2)
|
||||
LUAU_FASTFLAG(LuauHasPropProperBlock)
|
||||
LUAU_FASTFLAG(LuauFixCyclicIndexInIndexer)
|
||||
|
||||
struct TypeFunctionFixture : Fixture
|
||||
{
|
||||
|
@ -922,6 +924,76 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "index_of_any_is_any")
|
|||
CHECK(toString(requireTypeAlias("T")) == "any");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "index_should_not_crash_on_cyclic_stuff")
|
||||
{
|
||||
if (!FFlag::LuauSolverV2)
|
||||
return;
|
||||
|
||||
ScopedFastFlag sff{FFlag::LuauFixCyclicIndexInIndexer, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local PlayerData = {}
|
||||
|
||||
type Keys = index<typeof(PlayerData), true>
|
||||
|
||||
local function UpdateData(key: Keys)
|
||||
PlayerData[key] = 4
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
CHECK(toString(requireTypeAlias("Keys")) == "index<PlayerData, true>");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "index_should_not_crash_on_cyclic_stuff2")
|
||||
{
|
||||
if (!FFlag::LuauSolverV2)
|
||||
return;
|
||||
|
||||
ScopedFastFlag sff{FFlag::LuauFixCyclicIndexInIndexer, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local PlayerData = {}
|
||||
|
||||
type Keys = index<typeof(PlayerData), number>
|
||||
|
||||
local function UpdateData(key: Keys)
|
||||
PlayerData[key] = 4
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK(toString(requireTypeAlias("Keys")) == "number");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "index_should_not_crash_on_cyclic_stuff3")
|
||||
{
|
||||
if (!FFlag::LuauSolverV2)
|
||||
return;
|
||||
|
||||
ScopedFastFlag sff{FFlag::LuauFixCyclicIndexInIndexer, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local PlayerData = {
|
||||
Coins = 0,
|
||||
Level = 1,
|
||||
Exp = 0,
|
||||
MapExp = 100,
|
||||
}
|
||||
|
||||
type Keys = index<typeof(PlayerData), true>
|
||||
|
||||
local function UpdateData(key: Keys, value)
|
||||
PlayerData[key] = value
|
||||
end
|
||||
|
||||
UpdateData("Coins", 2)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK(toString(requireTypeAlias("Keys")) == "unknown");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_function_works")
|
||||
{
|
||||
if (!FFlag::LuauSolverV2)
|
||||
|
@ -1583,4 +1655,22 @@ end
|
|||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "has_prop_on_irreducible_type_function")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag luauHasPropProperBlock{FFlag::LuauHasPropProperBlock, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local test = "a" + "b"
|
||||
print(test.a)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
CHECK(
|
||||
"Operator '+' could not be applied to operands of types string and string; there is no corresponding overload for __add" ==
|
||||
toString(result.errors[0])
|
||||
);
|
||||
CHECK("Type 'add<string, string>' does not have key 'a'" == toString(result.errors[1]));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -12,10 +12,9 @@ using namespace Luau;
|
|||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauFixInfiniteRecursionInNormalization)
|
||||
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
|
||||
LUAU_FASTFLAG(LuauPrecalculateMutatedFreeTypes2)
|
||||
LUAU_FASTFLAG(LuauDeferBidirectionalInferenceForTableAssignment)
|
||||
LUAU_FASTFLAG(LuauBidirectionalInferenceUpcast)
|
||||
LUAU_FASTFLAG(LuauBidirectionalInferenceCollectIndexerTypes)
|
||||
LUAU_FASTFLAG(LuauRetainDefinitionAliasLocations)
|
||||
|
||||
TEST_SUITE_BEGIN("TypeAliases");
|
||||
|
||||
|
@ -258,8 +257,6 @@ TEST_CASE_FIXTURE(Fixture, "dependent_generic_aliases")
|
|||
TEST_CASE_FIXTURE(Fixture, "mutually_recursive_generic_aliases")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauPrecalculateMutatedFreeTypes2, true},
|
||||
{FFlag::LuauDeferBidirectionalInferenceForTableAssignment, true},
|
||||
{FFlag::LuauBidirectionalInferenceUpcast, true},
|
||||
{FFlag::LuauBidirectionalInferenceCollectIndexerTypes, true},
|
||||
};
|
||||
|
@ -1226,4 +1223,40 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "gh1632_no_infinite_recursion_in_normalizatio
|
|||
LUAU_CHECK_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "exported_alias_location_is_accessible_on_module")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauRetainDefinitionAliasLocations, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
export type Value = string
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
auto module = getMainModule();
|
||||
auto tfun = module->exportedTypeBindings.find("Value");
|
||||
REQUIRE(tfun != module->exportedTypeBindings.end());
|
||||
CHECK_EQ(tfun->second.definitionLocation, Location{{1, 8}, {1, 34}});
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "exported_type_function_location_is_accessible_on_module")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauRetainDefinitionAliasLocations, true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
export type function Apply()
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
auto module = getMainModule();
|
||||
auto tfun = module->exportedTypeBindings.find("Apply");
|
||||
REQUIRE(tfun != module->exportedTypeBindings.end());
|
||||
CHECK_EQ(tfun->second.definitionLocation, Location{{1, 8}, {2, 11}});
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -11,7 +11,6 @@ using namespace Luau;
|
|||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauTableCloneClonesType3)
|
||||
LUAU_FASTFLAG(LuauStringFormatErrorSuppression)
|
||||
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
|
||||
|
||||
TEST_SUITE_BEGIN("BuiltinTests");
|
||||
|
@ -1672,10 +1671,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "string_format_should_support_any")
|
|||
print(string.format("Hello, %s!", x))
|
||||
)");
|
||||
|
||||
if (FFlag::LuauStringFormatErrorSuppression)
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
else
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauDeferBidirectionalInferenceForTableAssignment)
|
||||
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
|
||||
|
||||
using namespace Luau;
|
||||
|
@ -856,8 +855,6 @@ end
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "generic_functions_should_be_memory_safe")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauDeferBidirectionalInferenceForTableAssignment, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
-- At one point this produced a UAF
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
|
||||
LUAU_FASTFLAG(LuauGeneralizationRemoveRecursiveUpperBound2)
|
||||
LUAU_FASTFLAG(LuauIntersectNotNil)
|
||||
LUAU_FASTFLAG(LuauSkipNoRefineDuringRefinement)
|
||||
LUAU_FASTFLAG(LuauFunctionCallsAreNotNilable)
|
||||
|
@ -2456,7 +2455,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "remove_recursive_upper_bound_when_generalizi
|
|||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::DebugLuauEqSatSimplification, true},
|
||||
{FFlag::LuauGeneralizationRemoveRecursiveUpperBound2, true},
|
||||
};
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(check(R"(
|
||||
|
|
|
@ -24,14 +24,13 @@ LUAU_FASTFLAG(LuauFixIndexerSubtypingOrdering)
|
|||
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
|
||||
LUAU_FASTFLAG(LuauTrackInteriorFreeTablesOnScope)
|
||||
LUAU_FASTFLAG(LuauFollowTableFreeze)
|
||||
LUAU_FASTFLAG(LuauPrecalculateMutatedFreeTypes2)
|
||||
LUAU_FASTFLAG(LuauDeferBidirectionalInferenceForTableAssignment)
|
||||
LUAU_FASTFLAG(LuauBidirectionalInferenceUpcast)
|
||||
LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint)
|
||||
LUAU_FASTFLAG(LuauSearchForRefineableType)
|
||||
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
|
||||
LUAU_FASTFLAG(LuauBidirectionalInferenceCollectIndexerTypes)
|
||||
LUAU_FASTFLAG(LuauBidirectionalFailsafe)
|
||||
LUAU_FASTFLAG(LuauBidirectionalInferenceElideAssert)
|
||||
|
||||
TEST_SUITE_BEGIN("TableTests");
|
||||
|
||||
|
@ -5172,8 +5171,6 @@ TEST_CASE_FIXTURE(Fixture, "function_check_constraint_too_eager")
|
|||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauPrecalculateMutatedFreeTypes2, true},
|
||||
{FFlag::LuauDeferBidirectionalInferenceForTableAssignment, true},
|
||||
{FFlag::LuauBidirectionalInferenceCollectIndexerTypes, true},
|
||||
};
|
||||
|
||||
|
@ -5217,8 +5214,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "magic_functions_bidirectionally_inferred")
|
|||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauPrecalculateMutatedFreeTypes2, true},
|
||||
{FFlag::LuauDeferBidirectionalInferenceForTableAssignment, true},
|
||||
{FFlag::LuauBidirectionalInferenceCollectIndexerTypes, true},
|
||||
};
|
||||
|
||||
|
@ -5345,8 +5340,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_freeze_musnt_assert")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "optional_property_with_call")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauDeferBidirectionalInferenceForTableAssignment, true};
|
||||
|
||||
LUAU_CHECK_NO_ERRORS(check(R"(
|
||||
type t = {
|
||||
key: boolean?,
|
||||
|
@ -5400,7 +5393,6 @@ TEST_CASE_FIXTURE(Fixture, "inference_in_constructor")
|
|||
TEST_CASE_FIXTURE(Fixture, "returning_optional_in_table")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauDeferBidirectionalInferenceForTableAssignment, true},
|
||||
{FFlag::LuauBidirectionalInferenceUpcast, true},
|
||||
};
|
||||
|
||||
|
@ -5416,7 +5408,6 @@ TEST_CASE_FIXTURE(Fixture, "returning_mismatched_optional_in_table")
|
|||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauDeferBidirectionalInferenceForTableAssignment, true},
|
||||
};
|
||||
|
||||
auto result = check(R"(
|
||||
|
@ -5438,7 +5429,6 @@ TEST_CASE_FIXTURE(Fixture, "optional_function_in_table")
|
|||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauDeferBidirectionalInferenceForTableAssignment, true},
|
||||
{FFlag::LuauBidirectionalInferenceUpcast, true},
|
||||
};
|
||||
|
||||
|
@ -5464,7 +5454,6 @@ TEST_CASE_FIXTURE(Fixture, "optional_function_in_table")
|
|||
TEST_CASE_FIXTURE(Fixture, "oss_1596_expression_in_table")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauDeferBidirectionalInferenceForTableAssignment, true},
|
||||
{FFlag::LuauBidirectionalInferenceUpcast, true},
|
||||
};
|
||||
|
||||
|
@ -5477,10 +5466,6 @@ TEST_CASE_FIXTURE(Fixture, "oss_1596_expression_in_table")
|
|||
|
||||
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<{}> = {
|
||||
|
@ -5491,11 +5476,6 @@ TEST_CASE_FIXTURE(Fixture, "oss_1615_parametrized_type_alias")
|
|||
|
||||
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? }
|
||||
|
||||
|
@ -5509,8 +5489,6 @@ TEST_CASE_FIXTURE(Fixture, "missing_fields_bidirectional_inference")
|
|||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauPrecalculateMutatedFreeTypes2, true},
|
||||
{FFlag::LuauDeferBidirectionalInferenceForTableAssignment, true},
|
||||
{FFlag::LuauBidirectionalInferenceUpcast, true},
|
||||
{FFlag::LuauBidirectionalInferenceCollectIndexerTypes, true},
|
||||
};
|
||||
|
@ -5544,8 +5522,6 @@ TEST_CASE_FIXTURE(Fixture, "generic_index_syntax_bidirectional_infer_with_tables
|
|||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauPrecalculateMutatedFreeTypes2, true},
|
||||
{FFlag::LuauDeferBidirectionalInferenceForTableAssignment, true},
|
||||
{FFlag::LuauBidirectionalInferenceUpcast, true},
|
||||
{FFlag::LuauBidirectionalInferenceCollectIndexerTypes, true},
|
||||
};
|
||||
|
@ -5690,5 +5666,23 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "function_call_in_indexer_with_compound_assig
|
|||
)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "fuzz_match_literal_type_crash_again")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauBidirectionalInferenceCollectIndexerTypes, true},
|
||||
{FFlag::LuauBidirectionalInferenceElideAssert, true},
|
||||
};
|
||||
CheckResult result = check(R"(
|
||||
function f(_: { [string]: {unknown}} ) end
|
||||
f(
|
||||
{
|
||||
_ = { 42 },
|
||||
_ = { x = "foo" },
|
||||
}
|
||||
)
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -31,6 +31,8 @@ LUAU_FASTFLAG(LuauUnifyMetatableWithAny)
|
|||
LUAU_FASTFLAG(LuauExtraFollows)
|
||||
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
|
||||
LUAU_FASTFLAG(LuauTypeCheckerAcceptNumberConcats)
|
||||
LUAU_FASTFLAG(LuauPreprocessTypestatedArgument)
|
||||
LUAU_FASTFLAG(LuauCacheInferencePerAstExpr)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
|
@ -1946,5 +1948,58 @@ TEST_CASE_FIXTURE(Fixture, "concat_string_with_string_union")
|
|||
)"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_local_before_declaration_ice")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauPreprocessTypestatedArgument, true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local _
|
||||
table.freeze(_, _)
|
||||
)");
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
auto err0 = get<TypeMismatch>(result.errors[0]);
|
||||
CHECK(err0);
|
||||
CHECK_EQ("nil", toString(err0->givenType));
|
||||
CHECK_EQ("table", toString(err0->wantedType));
|
||||
auto err1 = get<CountMismatch>(result.errors[1]);
|
||||
CHECK(err1);
|
||||
CHECK_EQ(1, err1->expected);
|
||||
CHECK_EQ(2, err1->actual);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "fuzz_dont_double_solve_compound_assignment" * doctest::timeout(1.0))
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauCacheInferencePerAstExpr, true}
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local _ = {}
|
||||
_[function<t0...>(...)
|
||||
_[function(...)
|
||||
_[_] %= _
|
||||
_ = {}
|
||||
_ = (- _)()
|
||||
end] %= _
|
||||
_[_] %= _
|
||||
end] %= true
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
LUAU_REQUIRE_NO_ERROR(result, ConstraintSolvingIncompleteError);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "assert_allows_singleton_union_or_intersection")
|
||||
{
|
||||
LUAU_REQUIRE_NO_ERRORS(check(R"(
|
||||
local x = 42 :: | number
|
||||
local y = 42 :: & number
|
||||
)"));
|
||||
}
|
||||
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
Loading…
Add table
Reference in a new issue