mirror of
https://github.com/luau-lang/luau.git
synced 2025-04-05 11:20:54 +01:00
Sync to upstream/release/668 (#1760)
## New Type Solver 1. Update resolved types for singleton unions and intersections to avoid crashing when type checking type assertions. 2. Generalize free return type pack of a function type inferred at call site to ensure that the free type does not leak to another module. 3. Fix crash from cyclic indexers by reducing if possible or producing an error otherwise. 4. Fix handling of irreducible type functions to prevent type inference from failing. 5. Fix handling of recursive metatables to avoid infinite recursion. ## New and Old Type Solver Fix accidental capture of all exceptions in multi-threaded typechecking by converting all typechecking exceptions to `InternalCompilerError` and only capturing those. ## Fragment Autocomplete 1. Add a block based diff algorithm based on class index and span for re-typechecking. This reduces the granularity of fragment autocomplete to avoid flakiness when the fragment does not have enough type information. 2. Fix bugs arising from incorrect scope selection for autocompletion. ## Roundtrippable AST Store type alias location in `TypeFun` class to ensure it is accessible for exported types as part of the public interface. ## Build System 1. Bump minimum supported CMake version to 3.10 since GitHub is phasing out the currently supported minimum version 3.0, released 11 years ago. 2. Fix compilation when `HARDSTACKTESTS` is enabled. ## Miscellaneous Flag removals and cleanup of unused code. ## Internal Contributors Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Ariel Weiss <aaronweiss@roblox.com> Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com> Co-authored-by: Talha Pathan <tpathan@roblox.com> Co-authored-by: Vighnesh Vijay <vvijay@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com> ## External Contributors Thanks to [@grh-official](https://github.com/grh-official) for PR #1759 **Full Changelog**: https://github.com/luau-lang/luau/compare/0.667...0.668 --------- Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com> Co-authored-by: Varun Saini <61795485+vrn-sn@users.noreply.github.com> Co-authored-by: Alexander Youngblood <ayoungblood@roblox.com> Co-authored-by: Menarul Alam <malam@roblox.com> Co-authored-by: Vighnesh <vvijay@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com> Co-authored-by: Ariel Weiss <aaronweiss@roblox.com>
This commit is contained in:
parent
6b33251b89
commit
ee1c6bf0db
39 changed files with 1135 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,365 @@ 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});
|
||||
}
|
||||
|
||||
#if 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"));
|
||||
}
|
||||
);
|
||||
}
|
||||
#endif
|
||||
|
||||
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)
|
||||
|
@ -1194,6 +1208,7 @@ _(_)[_(n32)] %= _(_(_))
|
|||
LUAU_REQUIRE_ERRORS(result);
|
||||
}
|
||||
|
||||
#if !(defined(_WIN32) && !(defined(_M_X64) || defined(_M_ARM64)))
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_propagate_normalization_failures")
|
||||
{
|
||||
ScopedFastInt luauNormalizeIntersectionLimit{FInt::LuauNormalizeIntersectionLimit, 50};
|
||||
|
@ -1210,5 +1225,6 @@ _().readu32 %= _(_(_(_),_))
|
|||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -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