Sync to upstream/release/673 (#1812)

# General Changes

* Remove a `static_assert` that prevented Luau from building on 32-bit
targets.
* Fix `proxyrequire` (see https://github.com/luau-lang/luau/pull/1804)
* Replace contents API with new `loadname` API in Luau.Require
* Store the positions of `:` symbols in the CST.
* Fix a minor bug in the new incremental autocomplete engine: Properly
suggest `else` and `elseif` if the cursor is currently within an `if`
block.

# New Type Solver

* Allow generics to be substituted for negation types when performing
subtype tests
* Crash fixes
* Surface subtyping errors more consistently.
* Avoid creating double-negation types (like `~~(false?)`) when
generating constraints.

# Internal Contributors

Co-authored-by: Andy Friesen <afriesen@roblox.com>
Co-authored-by: Ariel Weiss <aaronweiss@roblox.com>
Co-authored-by: Aviral Goel <agoel@roblox.com>
Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com>
Co-authored-by: Sora Kanosue <skanosue@roblox.com>
Co-authored-by: Talha Pathan <tpathan@roblox.com>
Co-authored-by: Varun Saini <vsaini@roblox.com>
Co-authored-by: Vighnesh Vijay <vvijay@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>

---------

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: Aviral Goel <agoel@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:
Andy Friesen 2025-05-09 16:35:47 -07:00 committed by GitHub
parent 42281aa8de
commit 3eb0c13678
Signed by: DevComp
GPG key ID: B5690EEEBB952194
60 changed files with 1325 additions and 2383 deletions

View file

@ -51,6 +51,10 @@ struct GeneralizationConstraint
std::vector<TypeId> interiorTypes; std::vector<TypeId> interiorTypes;
bool hasDeprecatedAttribute = false; bool hasDeprecatedAttribute = false;
/// If true, never introduce generics. Always replace free types by their
/// bounds or unknown. Presently used only to generalize the whole module.
bool noGenerics = false;
}; };
// variables ~ iterate iterator // variables ~ iterate iterator

View file

@ -13,8 +13,6 @@
#include "Luau/TypeOrPack.h" #include "Luau/TypeOrPack.h"
#include "Luau/TypeUtils.h" #include "Luau/TypeUtils.h"
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
namespace Luau namespace Luau
{ {
@ -40,29 +38,18 @@ struct Reasonings
std::string toString() std::string toString()
{ {
if (FFlag::LuauImproveTypePathsInErrors && reasons.empty()) if (reasons.empty())
return ""; return "";
// DenseHashSet ordering is entirely undefined, so we want to // DenseHashSet ordering is entirely undefined, so we want to
// sort the reasons here to achieve a stable error // sort the reasons here to achieve a stable error
// stringification. // stringification.
std::sort(reasons.begin(), reasons.end()); std::sort(reasons.begin(), reasons.end());
std::string allReasons = FFlag::LuauImproveTypePathsInErrors ? "\nthis is because " : ""; std::string allReasons = "\nthis is because ";
bool first = true;
for (const std::string& reason : reasons) for (const std::string& reason : reasons)
{ {
if (FFlag::LuauImproveTypePathsInErrors) if (reasons.size() > 1)
{ allReasons += "\n\t * ";
if (reasons.size() > 1)
allReasons += "\n\t * ";
}
else
{
if (first)
first = false;
else
allReasons += "\n\t";
}
allReasons += reason; allReasons += reason;
} }

View file

@ -33,7 +33,7 @@ LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauNonReentrantGeneralization2) LUAU_FASTFLAG(LuauNonReentrantGeneralization2)
LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3) LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3)
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunTypecheck) LUAU_FASTFLAGVARIABLE(LuauUserTypeFunTypecheck)
LUAU_FASTFLAGVARIABLE(LuauMagicFreezeCheckBlocked) LUAU_FASTFLAGVARIABLE(LuauMagicFreezeCheckBlocked2)
LUAU_FASTFLAGVARIABLE(LuauFormatUseLastPosition) LUAU_FASTFLAGVARIABLE(LuauFormatUseLastPosition)
namespace Luau namespace Luau
@ -1616,11 +1616,11 @@ bool MagicFreeze::infer(const MagicFunctionCallContext& context)
std::optional<DefId> resultDef = dfg->getDefOptional(targetExpr); std::optional<DefId> resultDef = dfg->getDefOptional(targetExpr);
std::optional<TypeId> resultTy = resultDef ? scope->lookup(*resultDef) : std::nullopt; std::optional<TypeId> resultTy = resultDef ? scope->lookup(*resultDef) : std::nullopt;
if (FFlag::LuauMagicFreezeCheckBlocked) if (FFlag::LuauMagicFreezeCheckBlocked2)
{ {
if (resultTy && !get<BlockedType>(resultTy)) if (resultTy && !get<BlockedType>(follow(resultTy)))
{ {
// If there's an existing result type but it's _not_ blocked, then // If there's an existing result type, but it's _not_ blocked, then
// we aren't type stating this builtin and should fall back to // we aren't type stating this builtin and should fall back to
// regular inference. // regular inference.
return false; return false;
@ -1629,6 +1629,8 @@ bool MagicFreeze::infer(const MagicFunctionCallContext& context)
std::optional<TypeId> frozenType = freezeTable(inputType, context); std::optional<TypeId> frozenType = freezeTable(inputType, context);
// At this point: we know for sure that if `resultTy` exists, it is a
// blocked type, and can safely emplace it.
if (!frozenType) if (!frozenType)
{ {
if (resultTy) if (resultTy)

View file

@ -14,7 +14,6 @@ LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTINTVARIABLE(LuauTypeCloneIterationLimit, 100'000) LUAU_FASTINTVARIABLE(LuauTypeCloneIterationLimit, 100'000)
LUAU_FASTFLAGVARIABLE(LuauClonedTableAndFunctionTypesMustHaveScopes) LUAU_FASTFLAGVARIABLE(LuauClonedTableAndFunctionTypesMustHaveScopes)
LUAU_FASTFLAGVARIABLE(LuauDoNotClonePersistentBindings) LUAU_FASTFLAGVARIABLE(LuauDoNotClonePersistentBindings)
LUAU_FASTFLAG(LuauIncrementalAutocompleteDemandBasedCloning)
namespace Luau namespace Luau
{ {
@ -549,11 +548,6 @@ public:
void cloneChildren(LazyType* t) override void cloneChildren(LazyType* t) override
{ {
// Do not clone lazy types // Do not clone lazy types
if (!FFlag::LuauIncrementalAutocompleteDemandBasedCloning)
{
if (auto unwrapped = t->unwrapped.load())
t->unwrapped.store(shallowClone(unwrapped));
}
} }
}; };

View file

@ -42,7 +42,6 @@ LUAU_FASTFLAGVARIABLE(LuauUngeneralizedTypesForRecursiveFunctions)
LUAU_FASTFLAG(LuauUserTypeFunTypecheck) LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
LUAU_FASTFLAGVARIABLE(LuauRetainDefinitionAliasLocations) LUAU_FASTFLAGVARIABLE(LuauRetainDefinitionAliasLocations)
LUAU_FASTFLAG(LuauDeprecatedAttribute)
LUAU_FASTFLAGVARIABLE(LuauCacheInferencePerAstExpr) LUAU_FASTFLAGVARIABLE(LuauCacheInferencePerAstExpr)
LUAU_FASTFLAGVARIABLE(LuauAlwaysResolveAstTypes) LUAU_FASTFLAGVARIABLE(LuauAlwaysResolveAstTypes)
LUAU_FASTFLAGVARIABLE(LuauWeakNilRefinementType) LUAU_FASTFLAGVARIABLE(LuauWeakNilRefinementType)
@ -52,6 +51,7 @@ LUAU_FASTFLAG(LuauGlobalVariableModuleIsolation)
LUAU_FASTFLAGVARIABLE(LuauNoTypeFunctionsNamedTypeOf) LUAU_FASTFLAGVARIABLE(LuauNoTypeFunctionsNamedTypeOf)
LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions) LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions)
LUAU_FASTFLAG(LuauDoNotAddUpvalueTypesToLocalType) LUAU_FASTFLAG(LuauDoNotAddUpvalueTypesToLocalType)
LUAU_FASTFLAGVARIABLE(LuauAvoidDoubleNegation)
namespace Luau namespace Luau
{ {
@ -288,7 +288,9 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block)
GeneralizationConstraint{ GeneralizationConstraint{
result, result,
moduleFnTy, moduleFnTy,
std::vector<TypeId>{}, /*interiorTypes*/ std::vector<TypeId>{},
/*hasDeprecatedAttribute*/ false,
/*noGenerics*/ true
} }
); );
@ -573,7 +575,17 @@ void ConstraintGenerator::computeRefinement(
// if we have a negative sense, then we need to negate the discriminant // if we have a negative sense, then we need to negate the discriminant
if (!sense) if (!sense)
discriminantTy = arena->addType(NegationType{discriminantTy}); {
if (FFlag::LuauAvoidDoubleNegation)
{
if (auto nt = get<NegationType>(follow(discriminantTy)))
discriminantTy = nt->ty;
else
discriminantTy = arena->addType(NegationType{discriminantTy});
}
else
discriminantTy = arena->addType(NegationType{discriminantTy});
}
if (eq) if (eq)
discriminantTy = createTypeFunctionInstance(builtinTypeFunctions().singletonFunc, {discriminantTy}, {}, scope, location); discriminantTy = createTypeFunctionInstance(builtinTypeFunctions().singletonFunc, {discriminantTy}, {}, scope, location);
@ -1377,7 +1389,6 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatRepeat* rep
static void propagateDeprecatedAttributeToConstraint(ConstraintV& c, const AstExprFunction* func) static void propagateDeprecatedAttributeToConstraint(ConstraintV& c, const AstExprFunction* func)
{ {
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
if (GeneralizationConstraint* genConstraint = c.get_if<GeneralizationConstraint>()) if (GeneralizationConstraint* genConstraint = c.get_if<GeneralizationConstraint>())
{ {
genConstraint->hasDeprecatedAttribute = func->hasAttribute(AstAttr::Type::Deprecated); genConstraint->hasDeprecatedAttribute = func->hasAttribute(AstAttr::Type::Deprecated);
@ -1386,7 +1397,6 @@ static void propagateDeprecatedAttributeToConstraint(ConstraintV& c, const AstEx
static void propagateDeprecatedAttributeToType(TypeId signature, const AstExprFunction* func) static void propagateDeprecatedAttributeToType(TypeId signature, const AstExprFunction* func)
{ {
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
FunctionType* fty = getMutable<FunctionType>(signature); FunctionType* fty = getMutable<FunctionType>(signature);
LUAU_ASSERT(fty); LUAU_ASSERT(fty);
fty->isDeprecatedFunction = func->hasAttribute(AstAttr::Type::Deprecated); fty->isDeprecatedFunction = func->hasAttribute(AstAttr::Type::Deprecated);
@ -1429,8 +1439,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocalFuncti
std::unique_ptr<Constraint> c = std::unique_ptr<Constraint> c =
std::make_unique<Constraint>(constraintScope, function->name->location, GeneralizationConstraint{functionType, sig.signature}); std::make_unique<Constraint>(constraintScope, function->name->location, GeneralizationConstraint{functionType, sig.signature});
if (FFlag::LuauDeprecatedAttribute) propagateDeprecatedAttributeToConstraint(c->c, function->func);
propagateDeprecatedAttributeToConstraint(c->c, function->func);
Constraint* previous = nullptr; Constraint* previous = nullptr;
forEachConstraint( forEachConstraint(
@ -1457,8 +1466,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocalFuncti
else else
{ {
module->astTypes[function->func] = sig.signature; module->astTypes[function->func] = sig.signature;
if (FFlag::LuauDeprecatedAttribute) propagateDeprecatedAttributeToType(sig.signature, function->func);
propagateDeprecatedAttributeToType(sig.signature, function->func);
} }
return ControlFlow::None; return ControlFlow::None;
@ -1502,8 +1510,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f
if (sigFullyDefined) if (sigFullyDefined)
{ {
emplaceType<BoundType>(asMutable(generalizedType), sig.signature); emplaceType<BoundType>(asMutable(generalizedType), sig.signature);
if (FFlag::LuauDeprecatedAttribute) propagateDeprecatedAttributeToType(sig.signature, function->func);
propagateDeprecatedAttributeToType(sig.signature, function->func);
} }
else else
{ {
@ -1512,8 +1519,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f
NotNull<Constraint> c = addConstraint(constraintScope, function->name->location, GeneralizationConstraint{generalizedType, sig.signature}); NotNull<Constraint> c = addConstraint(constraintScope, function->name->location, GeneralizationConstraint{generalizedType, sig.signature});
getMutable<BlockedType>(generalizedType)->setOwner(c); getMutable<BlockedType>(generalizedType)->setOwner(c);
if (FFlag::LuauDeprecatedAttribute) propagateDeprecatedAttributeToConstraint(c->c, function->func);
propagateDeprecatedAttributeToConstraint(c->c, function->func);
Constraint* previous = nullptr; Constraint* previous = nullptr;
forEachConstraint( forEachConstraint(
@ -2054,8 +2060,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareFunc
FunctionType* ftv = getMutable<FunctionType>(fnType); FunctionType* ftv = getMutable<FunctionType>(fnType);
ftv->isCheckedFunction = global->isCheckedFunction(); ftv->isCheckedFunction = global->isCheckedFunction();
if (FFlag::LuauDeprecatedAttribute) ftv->isDeprecatedFunction = global->hasAttribute(AstAttr::Type::Deprecated);
ftv->isDeprecatedFunction = global->hasAttribute(AstAttr::Type::Deprecated);
ftv->argNames.reserve(global->paramNames.size); ftv->argNames.reserve(global->paramNames.size);
for (const auto& el : global->paramNames) for (const auto& el : global->paramNames)
@ -3710,8 +3715,7 @@ TypeId ConstraintGenerator::resolveFunctionType(
// how to quantify/instantiate it. // how to quantify/instantiate it.
FunctionType ftv{TypeLevel{}, {}, {}, argTypes, returnTypes}; FunctionType ftv{TypeLevel{}, {}, {}, argTypes, returnTypes};
ftv.isCheckedFunction = fn->isCheckedFunction(); ftv.isCheckedFunction = fn->isCheckedFunction();
if (FFlag::LuauDeprecatedAttribute) ftv.isDeprecatedFunction = fn->hasAttribute(AstAttr::Type::Deprecated);
ftv.isDeprecatedFunction = fn->hasAttribute(AstAttr::Type::Deprecated);
// This replicates the behavior of the appropriate FunctionType // This replicates the behavior of the appropriate FunctionType
// constructors. // constructors.

View file

@ -36,10 +36,10 @@ LUAU_FASTFLAGVARIABLE(LuauHasPropProperBlock)
LUAU_FASTFLAGVARIABLE(DebugLuauGreedyGeneralization) LUAU_FASTFLAGVARIABLE(DebugLuauGreedyGeneralization)
LUAU_FASTFLAG(LuauDeprecatedAttribute) LUAU_FASTFLAG(LuauDeprecatedAttribute)
LUAU_FASTFLAG(LuauNonReentrantGeneralization2) LUAU_FASTFLAG(LuauNonReentrantGeneralization2)
LUAU_FASTFLAG(LuauBidirectionalInferenceCollectIndexerTypes)
LUAU_FASTFLAG(LuauNewTypeFunReductionChecks2) LUAU_FASTFLAG(LuauNewTypeFunReductionChecks2)
LUAU_FASTFLAGVARIABLE(LuauTrackInferredFunctionTypeFromCall) LUAU_FASTFLAGVARIABLE(LuauTrackInferredFunctionTypeFromCall)
LUAU_FASTFLAGVARIABLE(LuauAddCallConstraintForIterableFunctions) LUAU_FASTFLAGVARIABLE(LuauAddCallConstraintForIterableFunctions)
LUAU_FASTFLAGVARIABLE(LuauGuardAgainstMalformedTypeAliasExpansion)
namespace Luau namespace Luau
{ {
@ -870,13 +870,10 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
else else
unify(constraint, generalizedType, *generalizedTy); unify(constraint, generalizedType, *generalizedTy);
if (FFlag::LuauDeprecatedAttribute) if (FunctionType* fty = getMutable<FunctionType>(follow(generalizedType)))
{ {
if (FunctionType* fty = getMutable<FunctionType>(follow(generalizedType))) if (c.hasDeprecatedAttribute)
{ fty->isDeprecatedFunction = true;
if (c.hasDeprecatedAttribute)
fty->isDeprecatedFunction = true;
}
} }
} }
else else
@ -933,6 +930,23 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
} }
} }
if (FFlag::DebugLuauGreedyGeneralization)
{
if (c.noGenerics)
{
if (auto ft = getMutable<FunctionType>(c.sourceType))
{
for (TypeId gen : ft->generics)
asMutable(gen)->ty.emplace<BoundType>(builtinTypes->unknownType);
ft->generics.clear();
for (TypePackId gen : ft->genericPacks)
asMutable(gen)->ty.emplace<BoundTypePack>(builtinTypes->unknownTypePack);
ft->genericPacks.clear();
}
}
}
return true; return true;
} }
@ -1213,7 +1227,19 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
// deterministic. // deterministic.
if (TypeId* cached = instantiatedAliases.find(signature)) if (TypeId* cached = instantiatedAliases.find(signature))
{ {
bindResult(*cached); // However, we might now be revealing a malformed mutually recursive
// alias. `instantiatedAliases` can change from underneath us in a
// way that can cause a cached type id to bind to itself if we don't
// do this check.
if (FFlag::LuauGuardAgainstMalformedTypeAliasExpansion && occursCheck(follow(c.target), *cached))
{
reportError(OccursCheckFailed{}, constraint->location);
bindResult(errorRecoveryType());
}
else
{
bindResult(*cached);
}
return true; return true;
} }
@ -1633,45 +1659,13 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<con
for (auto generic : ftv->generics) for (auto generic : ftv->generics)
{ {
replacements[generic] = builtinTypes->unknownType; replacements[generic] = builtinTypes->unknownType;
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes) containsGenerics.generics.insert(generic);
containsGenerics.generics.insert(generic);
} }
for (auto genericPack : ftv->genericPacks) for (auto genericPack : ftv->genericPacks)
{ {
replacementPacks[genericPack] = builtinTypes->unknownTypePack; replacementPacks[genericPack] = builtinTypes->unknownTypePack;
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes) containsGenerics.generics.insert(genericPack);
containsGenerics.generics.insert(genericPack);
}
// If the type of the function has generics, we don't actually want to push any of the generics themselves
// into the argument types as expected types because this creates an unnecessary loop. Instead, we want to
// replace these types with `unknown` (and `...unknown`) to keep any structure but not create the cycle.
if (!FFlag::LuauBidirectionalInferenceCollectIndexerTypes)
{
if (!replacements.empty() || !replacementPacks.empty())
{
Replacer replacer{arena, std::move(replacements), std::move(replacementPacks)};
std::optional<TypeId> res = replacer.substitute(fn);
if (res)
{
if (*res != fn)
{
FunctionType* ftvMut = getMutable<FunctionType>(*res);
LUAU_ASSERT(ftvMut);
ftvMut->generics.clear();
ftvMut->genericPacks.clear();
}
fn = *res;
ftv = get<FunctionType>(*res);
LUAU_ASSERT(ftv);
// we've potentially copied type functions here, so we need to reproduce their reduce constraint.
reproduceConstraints(constraint->scope, constraint->location, replacer);
}
}
} }
const std::vector<TypeId> expectedArgs = flatten(ftv->argTypes).first; const std::vector<TypeId> expectedArgs = flatten(ftv->argTypes).first;
@ -1690,7 +1684,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<con
(*c.astExpectedTypes)[expr] = expectedArgTy; (*c.astExpectedTypes)[expr] = expectedArgTy;
// Generic types are skipped over entirely, for now. // Generic types are skipped over entirely, for now.
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes && containsGenerics.hasGeneric(expectedArgTy)) if (containsGenerics.hasGeneric(expectedArgTy))
continue; continue;
const FunctionType* expectedLambdaTy = get<FunctionType>(expectedArgTy); const FunctionType* expectedLambdaTy = get<FunctionType>(expectedArgTy);
@ -1857,7 +1851,7 @@ bool ConstraintSolver::tryDispatchHasIndexer(
return true; return true;
} }
if (auto ft = get<FreeType>(subjectType)) if (auto ft = getMutable<FreeType>(subjectType))
{ {
if (auto tbl = get<TableType>(follow(ft->upperBound)); tbl && tbl->indexer) if (auto tbl = get<TableType>(follow(ft->upperBound)); tbl && tbl->indexer)
{ {
@ -1874,7 +1868,19 @@ bool ConstraintSolver::tryDispatchHasIndexer(
TypeId upperBound = TypeId upperBound =
arena->addType(TableType{/* props */ {}, TableIndexer{indexType, resultType}, TypeLevel{}, ft->scope, TableState::Unsealed}); arena->addType(TableType{/* props */ {}, TableIndexer{indexType, resultType}, TypeLevel{}, ft->scope, TableState::Unsealed});
unify(constraint, subjectType, upperBound); if (FFlag::DebugLuauGreedyGeneralization)
{
TypeId sr = follow(simplifyIntersection(constraint->scope, constraint->location, ft->upperBound, upperBound));
if (get<NeverType>(sr))
bind(constraint, resultType, builtinTypes->errorType);
else
ft->upperBound = sr;
}
else
{
unify(constraint, subjectType, upperBound);
}
return true; return true;
} }

View file

@ -133,10 +133,7 @@ struct ErrorConverter
size_t luauIndentTypeMismatchMaxTypeLength = size_t(FInt::LuauIndentTypeMismatchMaxTypeLength); size_t luauIndentTypeMismatchMaxTypeLength = size_t(FInt::LuauIndentTypeMismatchMaxTypeLength);
if (givenType.length() <= luauIndentTypeMismatchMaxTypeLength || wantedType.length() <= luauIndentTypeMismatchMaxTypeLength) if (givenType.length() <= luauIndentTypeMismatchMaxTypeLength || wantedType.length() <= luauIndentTypeMismatchMaxTypeLength)
return "Type " + given + " could not be converted into " + wanted; return "Type " + given + " could not be converted into " + wanted;
if (FFlag::LuauImproveTypePathsInErrors) return "Type\n\t" + given + "\ncould not be converted into\n\t" + wanted;
return "Type\n\t" + given + "\ncould not be converted into\n\t" + wanted;
else
return "Type\n " + given + "\ncould not be converted into\n " + wanted;
}; };
if (givenTypeName == wantedTypeName) if (givenTypeName == wantedTypeName)

View file

@ -33,7 +33,6 @@ LUAU_FASTFLAGVARIABLE(LuauBetterCursorInCommentDetection)
LUAU_FASTFLAGVARIABLE(LuauAllFreeTypesHaveScopes) LUAU_FASTFLAGVARIABLE(LuauAllFreeTypesHaveScopes)
LUAU_FASTFLAGVARIABLE(LuauPersistConstraintGenerationScopes) LUAU_FASTFLAGVARIABLE(LuauPersistConstraintGenerationScopes)
LUAU_FASTFLAGVARIABLE(LuauCloneTypeAliasBindings) LUAU_FASTFLAGVARIABLE(LuauCloneTypeAliasBindings)
LUAU_FASTFLAGVARIABLE(LuauIncrementalAutocompleteDemandBasedCloning)
LUAU_FASTFLAG(LuauUserTypeFunTypecheck) LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
LUAU_FASTFLAGVARIABLE(LuauFragmentNoTypeFunEval) LUAU_FASTFLAGVARIABLE(LuauFragmentNoTypeFunEval)
LUAU_FASTFLAGVARIABLE(LuauBetterScopeSelection) LUAU_FASTFLAGVARIABLE(LuauBetterScopeSelection)
@ -41,51 +40,11 @@ LUAU_FASTFLAGVARIABLE(LuauBlockDiffFragmentSelection)
LUAU_FASTFLAGVARIABLE(LuauFragmentAcMemoryLeak) LUAU_FASTFLAGVARIABLE(LuauFragmentAcMemoryLeak)
LUAU_FASTFLAGVARIABLE(LuauGlobalVariableModuleIsolation) LUAU_FASTFLAGVARIABLE(LuauGlobalVariableModuleIsolation)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAGVARIABLE(LuauFragmentAutocompleteIfRecommendations)
namespace
{
template<typename T>
void copyModuleVec(std::vector<T>& result, const std::vector<T>& input)
{
result.insert(result.end(), input.begin(), input.end());
}
template<typename K, typename V>
void copyModuleMap(Luau::DenseHashMap<K, V>& result, const Luau::DenseHashMap<K, V>& input)
{
for (auto [k, v] : input)
result[k] = v;
}
} // namespace
namespace Luau namespace Luau
{ {
template<typename K, typename V>
void cloneModuleMap_DEPRECATED(TypeArena& destArena, CloneState& cloneState, const Luau::DenseHashMap<K, V>& source, Luau::DenseHashMap<K, V>& dest)
{
for (auto [k, v] : source)
{
dest[k] = Luau::clone(v, destArena, cloneState);
}
}
template<typename K, typename V>
void cloneModuleMap(
TypeArena& destArena,
CloneState& cloneState,
const Luau::DenseHashMap<K, V>& source,
Luau::DenseHashMap<K, V>& dest,
Scope* freshScopeForFreeType
)
{
for (auto [k, v] : source)
{
dest[k] = Luau::cloneIncremental(v, destArena, cloneState, freshScopeForFreeType);
}
}
static std::pair<size_t, size_t> getDocumentOffsets(std::string_view src, const Position& startPos, const Position& endPos); static std::pair<size_t, size_t> getDocumentOffsets(std::string_view src, const Position& startPos, const Position& endPos);
// when typing a function partially, get the span of the first line // when typing a function partially, get the span of the first line
@ -141,6 +100,29 @@ Location getAstStatForExtents(AstStatFor* forStat)
return Location{begin, end}; return Location{begin, end};
} }
// Traverses the linkedlist of else if statements to find the one that matches the cursor position the best
AstStatIf* getNearestIfToCursor(AstStat* stmt, const Position& cursorPos)
{
AstStatIf* current = stmt->as<AstStatIf>();
if (!current)
return nullptr;
while (current->is<AstStatIf>())
{
if (current->elsebody && current->elsebody->location.containsClosed(cursorPos))
{
if (auto elseIfS = current->elsebody->as<AstStatIf>())
{
current = elseIfS;
}
else
break;
}
else
break;
}
return current;
}
Location getFragmentLocation(AstStat* nearestStatement, const Position& cursorPosition) Location getFragmentLocation(AstStat* nearestStatement, const Position& cursorPosition)
{ {
Location empty{cursorPosition, cursorPosition}; Location empty{cursorPosition, cursorPosition};
@ -199,25 +181,59 @@ Location getFragmentLocation(AstStat* nearestStatement, const Position& cursorPo
else else
return empty; return empty;
} }
if (FFlag::LuauFragmentAutocompleteIfRecommendations)
if (auto ifS = nearestStatement->as<AstStatIf>())
{ {
auto conditionExtents = Location{ifS->location.begin, ifS->condition->location.end}; if (auto ifS = getNearestIfToCursor(nearestStatement, cursorPosition))
if (conditionExtents.containsClosed(cursorPosition))
return nonEmpty;
else if (ifS->thenbody->location.containsClosed(cursorPosition))
return empty;
else if (auto elseS = ifS->elsebody)
{ {
if (auto elseIf = ifS->elsebody->as<AstStatIf>()) auto conditionExtents = Location{ifS->condition->location.begin, ifS->condition->location.end};
if (conditionExtents.containsClosed(cursorPosition) || !ifS->thenLocation)
{ {
// CLI-152249 - the condition parse location can sometimes be after the body of the if
if (elseIf->thenbody->hasEnd) // statement. This is a bug that results returning locations like {3,0 - 2,0} which is
// wrong.
if (ifS->condition->location.begin > cursorPosition)
return empty; return empty;
else return Location{ifS->condition->location.begin, cursorPosition};
return {elseS->location.begin, cursorPosition}; }
else if (ifS->thenbody->location.containsClosed(cursorPosition))
return empty;
else if (auto elseS = ifS->elsebody)
{
if (auto elseIf = ifS->elsebody->as<AstStatIf>())
{
auto elseIfConditionExtents = Location{elseIf->location.begin, elseIf->condition->location.end};
if (elseIfConditionExtents.containsClosed(cursorPosition))
return {elseIf->condition->location.begin, cursorPosition};
if (elseIf->thenbody->hasEnd)
return empty;
else
return {elseS->location.begin, cursorPosition};
}
return empty;
}
}
}
else
{
if (auto ifS = nearestStatement->as<AstStatIf>())
{
auto conditionExtents = Location{ifS->location.begin, ifS->condition->location.end};
if (conditionExtents.containsClosed(cursorPosition))
return nonEmpty;
else if (ifS->thenbody->location.containsClosed(cursorPosition))
return empty;
else if (auto elseS = ifS->elsebody)
{
if (auto elseIf = ifS->elsebody->as<AstStatIf>())
{
if (elseIf->thenbody->hasEnd)
return empty;
else
return {elseS->location.begin, cursorPosition};
}
return empty;
} }
return empty;
} }
} }
@ -504,9 +520,31 @@ std::optional<FragmentParseResult> parseFragment(
if (p.root == nullptr) if (p.root == nullptr)
return std::nullopt; return std::nullopt;
std::vector<AstNode*> fabricatedAncestry = std::move(result.ancestry); std::vector<AstNode*> fabricatedAncestry;
// Moves cannot be on the rhs of a ? : statement, so the assignment is placed in an if else stmt
// Make sure to trim this comment after you remove FFlag::LuauFragmentAutocompleteIfRecommendations
if (FFlag::LuauFragmentAutocompleteIfRecommendations)
fabricatedAncestry = findAncestryAtPositionForAutocomplete(mostRecentParse, cursorPos);
else
fabricatedAncestry = std::move(result.ancestry);
std::vector<AstNode*> fragmentAncestry = findAncestryAtPositionForAutocomplete(p.root, cursorPos); std::vector<AstNode*> fragmentAncestry = findAncestryAtPositionForAutocomplete(p.root, cursorPos);
fabricatedAncestry.insert(fabricatedAncestry.end(), fragmentAncestry.begin(), fragmentAncestry.end());
// Computes the accurate ancestry and then replaces the nodes that correspond to the fragment ancestry
// Needed because we look up types by pointer identity
if (FFlag::LuauFragmentAutocompleteIfRecommendations)
{
LUAU_ASSERT(!fabricatedAncestry.empty());
auto back = fabricatedAncestry.size() - 1;
for (auto it = fragmentAncestry.rbegin(); it != fragmentAncestry.rend(); ++it)
{
if (back >= 0 && back < fabricatedAncestry.size() && (*it)->classIndex == fabricatedAncestry[back]->classIndex)
fabricatedAncestry[back] = *it;
back--;
}
}
else
fabricatedAncestry.insert(fabricatedAncestry.end(), fragmentAncestry.begin(), fragmentAncestry.end());
if (nearestStatement == nullptr) if (nearestStatement == nullptr)
nearestStatement = p.root; nearestStatement = p.root;
fragmentResult.root = p.root; fragmentResult.root = p.root;
@ -718,179 +756,6 @@ void cloneTypesFromFragment(
destScope->returnType = Luau::cloneIncremental(staleScope->returnType, *destArena, cloneState, destScope); destScope->returnType = Luau::cloneIncremental(staleScope->returnType, *destArena, cloneState, destScope);
} }
struct MixedModeIncrementalTCDefFinder : public AstVisitor
{
bool visit(AstExprLocal* local) override
{
referencedLocalDefs.emplace_back(local->local, local);
return true;
}
bool visit(AstTypeTypeof* node) override
{
// We need to traverse typeof expressions because they may refer to locals that we need
// to populate the local environment for fragment typechecking. For example, `typeof(m)`
// requires that we find the local/global `m` and place it in the environment.
// The default behaviour here is to return false, and have individual visitors override
// the specific behaviour they need.
return true;
}
bool visit(AstStatTypeAlias* alias) override
{
if (FFlag::LuauCloneTypeAliasBindings)
declaredAliases.insert(std::string(alias->name.value));
return true;
}
// ast defs is just a mapping from expr -> def in general
// will get built up by the dfg builder
// localDefs, we need to copy over
std::vector<std::pair<AstLocal*, AstExpr*>> referencedLocalDefs;
DenseHashSet<Name> declaredAliases{""};
};
void cloneAndSquashScopes_DEPRECATED(
CloneState& cloneState,
const Scope* staleScope,
const ModulePtr& staleModule,
NotNull<TypeArena> destArena,
NotNull<DataFlowGraph> dfg,
AstStatBlock* program,
Scope* destScope
)
{
LUAU_TIMETRACE_SCOPE("Luau::cloneAndSquashScopes", "FragmentAutocomplete");
std::vector<const Scope*> scopes;
for (const Scope* current = staleScope; current; current = current->parent.get())
{
scopes.emplace_back(current);
}
// in reverse order (we need to clone the parents and override defs as we go down the list)
for (auto it = scopes.rbegin(); it != scopes.rend(); ++it)
{
const Scope* curr = *it;
// Clone the lvalue types
for (const auto& [def, ty] : curr->lvalueTypes)
destScope->lvalueTypes[def] = Luau::clone(ty, *destArena, cloneState);
// Clone the rvalueRefinements
for (const auto& [def, ty] : curr->rvalueRefinements)
destScope->rvalueRefinements[def] = Luau::clone(ty, *destArena, cloneState);
for (const auto& [n, m] : curr->importedTypeBindings)
{
std::unordered_map<Name, TypeFun> importedBindingTypes;
for (const auto& [v, tf] : m)
importedBindingTypes[v] = Luau::clone(tf, *destArena, cloneState);
destScope->importedTypeBindings[n] = m;
}
// Finally, clone up the bindings
for (const auto& [s, b] : curr->bindings)
{
destScope->bindings[s] = Luau::clone(b, *destArena, cloneState);
}
}
// The above code associates defs with TypeId's in the scope
// so that lookup to locals will succeed.
MixedModeIncrementalTCDefFinder finder;
program->visit(&finder);
std::vector<std::pair<AstLocal*, AstExpr*>> locals = std::move(finder.referencedLocalDefs);
for (auto [loc, expr] : locals)
{
if (std::optional<Binding> binding = staleScope->linearSearchForBinding(loc->name.value, true))
{
destScope->lvalueTypes[dfg->getDef(expr)] = Luau::clone(binding->typeId, *destArena, cloneState);
}
}
return;
}
void cloneAndSquashScopes(
CloneState& cloneState,
const Scope* staleScope,
const ModulePtr& staleModule,
NotNull<TypeArena> destArena,
NotNull<DataFlowGraph> dfg,
AstStatBlock* program,
Scope* destScope
)
{
LUAU_TIMETRACE_SCOPE("Luau::cloneAndSquashScopes", "FragmentAutocomplete");
std::vector<const Scope*> scopes;
for (const Scope* current = staleScope; current; current = current->parent.get())
{
scopes.emplace_back(current);
}
MixedModeIncrementalTCDefFinder finder;
if (FFlag::LuauCloneTypeAliasBindings)
program->visit(&finder);
// in reverse order (we need to clone the parents and override defs as we go down the list)
for (auto it = scopes.rbegin(); it != scopes.rend(); ++it)
{
const Scope* curr = *it;
// Clone the lvalue types
for (const auto& [def, ty] : curr->lvalueTypes)
destScope->lvalueTypes[def] = Luau::cloneIncremental(ty, *destArena, cloneState, destScope);
// Clone the rvalueRefinements
for (const auto& [def, ty] : curr->rvalueRefinements)
destScope->rvalueRefinements[def] = Luau::cloneIncremental(ty, *destArena, cloneState, destScope);
if (FFlag::LuauCloneTypeAliasBindings)
{
for (const auto& [n, tf] : curr->exportedTypeBindings)
{
if (!finder.declaredAliases.contains(n))
destScope->exportedTypeBindings[n] = Luau::cloneIncremental(tf, *destArena, cloneState, destScope);
}
for (const auto& [n, tf] : curr->privateTypeBindings)
{
if (!finder.declaredAliases.contains(n))
destScope->privateTypeBindings[n] = Luau::cloneIncremental(tf, *destArena, cloneState, destScope);
}
}
for (const auto& [n, m] : curr->importedTypeBindings)
{
std::unordered_map<Name, TypeFun> importedBindingTypes;
for (const auto& [v, tf] : m)
importedBindingTypes[v] = Luau::cloneIncremental(tf, *destArena, cloneState, destScope);
destScope->importedTypeBindings[n] = std::move(importedBindingTypes);
}
// Finally, clone up the bindings
for (const auto& [s, b] : curr->bindings)
{
destScope->bindings[s] = Luau::cloneIncremental(b, *destArena, cloneState, destScope);
}
}
if (!FFlag::LuauCloneTypeAliasBindings)
program->visit(&finder);
// The above code associates defs with TypeId's in the scope
// so that lookup to locals will succeed.
std::vector<std::pair<AstLocal*, AstExpr*>> locals = std::move(finder.referencedLocalDefs);
for (auto [loc, expr] : locals)
{
if (std::optional<Binding> binding = staleScope->linearSearchForBinding(loc->name.value, true))
{
destScope->lvalueTypes[dfg->getDef(expr)] = Luau::cloneIncremental(binding->typeId, *destArena, cloneState, destScope);
}
}
if (destScope->returnType)
destScope->returnType = Luau::cloneIncremental(destScope->returnType, *destArena, cloneState, destScope);
return;
}
static FrontendModuleResolver& getModuleResolver(Frontend& frontend, std::optional<FrontendOptions> options) static FrontendModuleResolver& getModuleResolver(Frontend& frontend, std::optional<FrontendOptions> options)
{ {
if (FFlag::LuauSolverV2 || !options) if (FFlag::LuauSolverV2 || !options)
@ -1031,8 +896,11 @@ static std::pair<size_t, size_t> getDocumentOffsets(std::string_view src, const
if (endPos.line == lineCount && endPos.column == colCount) if (endPos.line == lineCount && endPos.column == colCount)
{ {
endOffset = docOffset; endOffset = docOffset;
while (endOffset < src.size() && src[endOffset] != '\n') if (!FFlag::LuauFragmentAutocompleteIfRecommendations)
endOffset++; {
while (endOffset < src.size() && src[endOffset] != '\n')
endOffset++;
}
foundEnd = true; foundEnd = true;
} }
@ -1177,80 +1045,6 @@ std::optional<FragmentParseResult> parseFragment_DEPRECATED(
return fragmentResult; return fragmentResult;
} }
ModulePtr cloneModule_DEPRECATED(CloneState& cloneState, const ModulePtr& source, std::unique_ptr<Allocator> alloc)
{
LUAU_TIMETRACE_SCOPE("Luau::cloneModule", "FragmentAutocomplete");
freeze(source->internalTypes);
freeze(source->interfaceTypes);
ModulePtr incremental = std::make_shared<Module>();
incremental->name = source->name;
incremental->humanReadableName = source->humanReadableName;
incremental->allocator = std::move(alloc);
// Clone types
cloneModuleMap_DEPRECATED(incremental->internalTypes, cloneState, source->astTypes, incremental->astTypes);
cloneModuleMap_DEPRECATED(incremental->internalTypes, cloneState, source->astTypePacks, incremental->astTypePacks);
cloneModuleMap_DEPRECATED(incremental->internalTypes, cloneState, source->astExpectedTypes, incremental->astExpectedTypes);
cloneModuleMap_DEPRECATED(incremental->internalTypes, cloneState, source->astOverloadResolvedTypes, incremental->astOverloadResolvedTypes);
cloneModuleMap_DEPRECATED(incremental->internalTypes, cloneState, source->astForInNextTypes, incremental->astForInNextTypes);
copyModuleMap(incremental->astScopes, source->astScopes);
return incremental;
}
ModulePtr cloneModule(CloneState& cloneState, const ModulePtr& source, std::unique_ptr<Allocator> alloc, Scope* freeTypeFreshScope)
{
LUAU_TIMETRACE_SCOPE("Luau::cloneModule", "FragmentAutocomplete");
freeze(source->internalTypes);
freeze(source->interfaceTypes);
ModulePtr incremental = std::make_shared<Module>();
incremental->name = source->name;
incremental->humanReadableName = source->humanReadableName;
incremental->allocator = std::move(alloc);
// Clone types
cloneModuleMap(incremental->internalTypes, cloneState, source->astTypes, incremental->astTypes, freeTypeFreshScope);
cloneModuleMap(incremental->internalTypes, cloneState, source->astTypePacks, incremental->astTypePacks, freeTypeFreshScope);
cloneModuleMap(incremental->internalTypes, cloneState, source->astExpectedTypes, incremental->astExpectedTypes, freeTypeFreshScope);
cloneModuleMap(
incremental->internalTypes, cloneState, source->astOverloadResolvedTypes, incremental->astOverloadResolvedTypes, freeTypeFreshScope
);
cloneModuleMap(incremental->internalTypes, cloneState, source->astForInNextTypes, incremental->astForInNextTypes, freeTypeFreshScope);
copyModuleMap(incremental->astScopes, source->astScopes);
return incremental;
}
void mixedModeCompatibility(
const ScopePtr& bottomScopeStale,
const ScopePtr& myFakeScope,
const ModulePtr& stale,
NotNull<DataFlowGraph> dfg,
AstStatBlock* program
)
{
// This code does the following
// traverse program
// look for ast refs for locals
// ask for the corresponding defId from dfg
// given that defId, and that expression, in the incremental module, map lvalue types from defID to
MixedModeIncrementalTCDefFinder finder;
program->visit(&finder);
std::vector<std::pair<AstLocal*, AstExpr*>> locals = std::move(finder.referencedLocalDefs);
for (auto [loc, expr] : locals)
{
if (std::optional<Binding> binding = bottomScopeStale->linearSearchForBinding(loc->name.value, true))
{
myFakeScope->lvalueTypes[dfg->getDef(expr)] = binding->typeId;
}
}
}
static void reportWaypoint(IFragmentAutocompleteReporter* reporter, FragmentAutocompleteWaypoint type) static void reportWaypoint(IFragmentAutocompleteReporter* reporter, FragmentAutocompleteWaypoint type)
{ {
if (!reporter) if (!reporter)
@ -1267,156 +1061,6 @@ static void reportFragmentString(IFragmentAutocompleteReporter* reporter, std::s
reporter->reportFragmentString(fragment); reporter->reportFragmentString(fragment);
} }
FragmentTypeCheckResult typecheckFragmentHelper_DEPRECATED(
Frontend& frontend,
AstStatBlock* root,
const ModulePtr& stale,
const ScopePtr& closestScope,
const Position& cursorPos,
std::unique_ptr<Allocator> astAllocator,
const FrontendOptions& opts,
IFragmentAutocompleteReporter* reporter
)
{
LUAU_TIMETRACE_SCOPE("Luau::typecheckFragment_", "FragmentAutocomplete");
freeze(stale->internalTypes);
freeze(stale->interfaceTypes);
reportWaypoint(reporter, FragmentAutocompleteWaypoint::CloneModuleStart);
CloneState cloneState{frontend.builtinTypes};
std::shared_ptr<Scope> freshChildOfNearestScope = std::make_shared<Scope>(closestScope);
ModulePtr incrementalModule = nullptr;
if (FFlag::LuauAllFreeTypesHaveScopes)
incrementalModule = cloneModule(cloneState, stale, std::move(astAllocator), freshChildOfNearestScope.get());
else
incrementalModule = cloneModule_DEPRECATED(cloneState, stale, std::move(astAllocator));
reportWaypoint(reporter, FragmentAutocompleteWaypoint::CloneModuleEnd);
incrementalModule->checkedInNewSolver = true;
unfreeze(incrementalModule->internalTypes);
unfreeze(incrementalModule->interfaceTypes);
/// Setup typecheck limits
TypeCheckLimits limits;
if (opts.moduleTimeLimitSec)
limits.finishTime = TimeTrace::getClock() + *opts.moduleTimeLimitSec;
else
limits.finishTime = std::nullopt;
limits.cancellationToken = opts.cancellationToken;
/// Icehandler
NotNull<InternalErrorReporter> iceHandler{&frontend.iceHandler};
/// Make the shared state for the unifier (recursion + iteration limits)
UnifierSharedState unifierState{iceHandler};
unifierState.counters.recursionLimit = FInt::LuauTypeInferRecursionLimit;
unifierState.counters.iterationLimit = limits.unifierIterationLimit.value_or(FInt::LuauTypeInferIterationLimit);
/// Initialize the normalizer
Normalizer normalizer{&incrementalModule->internalTypes, frontend.builtinTypes, NotNull{&unifierState}};
/// User defined type functions runtime
TypeFunctionRuntime typeFunctionRuntime(iceHandler, NotNull{&limits});
/// Create a DataFlowGraph just for the surrounding context
DataFlowGraph dfg = DataFlowGraphBuilder::build(root, NotNull{&incrementalModule->defArena}, NotNull{&incrementalModule->keyArena}, iceHandler);
reportWaypoint(reporter, FragmentAutocompleteWaypoint::DfgBuildEnd);
SimplifierPtr simplifier = newSimplifier(NotNull{&incrementalModule->internalTypes}, frontend.builtinTypes);
FrontendModuleResolver& resolver = getModuleResolver(frontend, opts);
/// Contraint Generator
ConstraintGenerator cg{
incrementalModule,
NotNull{&normalizer},
NotNull{simplifier.get()},
NotNull{&typeFunctionRuntime},
NotNull{&resolver},
frontend.builtinTypes,
iceHandler,
stale->getModuleScope(),
frontend.globals.globalTypeFunctionScope,
nullptr,
nullptr,
NotNull{&dfg},
{}
};
reportWaypoint(reporter, FragmentAutocompleteWaypoint::CloneAndSquashScopeStart);
incrementalModule->scopes.emplace_back(root->location, freshChildOfNearestScope);
cg.rootScope = freshChildOfNearestScope.get();
if (FFlag::LuauAllFreeTypesHaveScopes)
cloneAndSquashScopes(
cloneState, closestScope.get(), stale, NotNull{&incrementalModule->internalTypes}, NotNull{&dfg}, root, freshChildOfNearestScope.get()
);
else
cloneAndSquashScopes_DEPRECATED(
cloneState, closestScope.get(), stale, NotNull{&incrementalModule->internalTypes}, NotNull{&dfg}, root, freshChildOfNearestScope.get()
);
reportWaypoint(reporter, FragmentAutocompleteWaypoint::CloneAndSquashScopeEnd);
cg.visitFragmentRoot(freshChildOfNearestScope, root);
if (FFlag::LuauPersistConstraintGenerationScopes)
{
for (auto p : cg.scopes)
incrementalModule->scopes.emplace_back(std::move(p));
}
reportWaypoint(reporter, FragmentAutocompleteWaypoint::ConstraintSolverStart);
if (FFlag::LuauAllFreeTypesHaveScopes)
{
if (Scope* sc = freshChildOfNearestScope.get())
{
if (!sc->interiorFreeTypes.has_value())
sc->interiorFreeTypes.emplace();
if (!sc->interiorFreeTypePacks.has_value())
sc->interiorFreeTypePacks.emplace();
}
}
/// Initialize the constraint solver and run it
ConstraintSolver cs{
NotNull{&normalizer},
NotNull{simplifier.get()},
NotNull{&typeFunctionRuntime},
NotNull(cg.rootScope),
borrowConstraints(cg.constraints),
NotNull{&cg.scopeToFunction},
incrementalModule->name,
NotNull{&resolver},
{},
nullptr,
NotNull{&dfg},
limits
};
try
{
cs.run();
}
catch (const TimeLimitError&)
{
stale->timeout = true;
}
catch (const UserCancelError&)
{
stale->cancelled = true;
}
reportWaypoint(reporter, FragmentAutocompleteWaypoint::ConstraintSolverEnd);
// In frontend we would forbid internal types
// because this is just for autocomplete, we don't actually care
// We also don't even need to typecheck - just synthesize types as best as we can
freeze(incrementalModule->internalTypes);
freeze(incrementalModule->interfaceTypes);
return {std::move(incrementalModule), std::move(freshChildOfNearestScope)};
}
FragmentTypeCheckResult typecheckFragment_( FragmentTypeCheckResult typecheckFragment_(
Frontend& frontend, Frontend& frontend,
AstStatBlock* root, AstStatBlock* root,
@ -1609,12 +1253,7 @@ std::pair<FragmentTypeCheckStatus, FragmentTypeCheckResult> typecheckFragment(
FrontendOptions frontendOptions = opts.value_or(frontend.options); FrontendOptions frontendOptions = opts.value_or(frontend.options);
const ScopePtr& closestScope = FFlag::LuauBetterScopeSelection ? findClosestScope(module, parseResult.scopePos) const ScopePtr& closestScope = FFlag::LuauBetterScopeSelection ? findClosestScope(module, parseResult.scopePos)
: findClosestScope_DEPRECATED(module, parseResult.nearestStatement); : findClosestScope_DEPRECATED(module, parseResult.nearestStatement);
FragmentTypeCheckResult result = FragmentTypeCheckResult result = typecheckFragment_(frontend, parseResult.root, module, closestScope, cursorPos, std::move(parseResult.alloc), frontendOptions, reporter);
FFlag::LuauIncrementalAutocompleteDemandBasedCloning
? typecheckFragment_(frontend, parseResult.root, module, closestScope, cursorPos, std::move(parseResult.alloc), frontendOptions, reporter)
: typecheckFragmentHelper_DEPRECATED(
frontend, parseResult.root, module, closestScope, cursorPos, std::move(parseResult.alloc), frontendOptions, reporter
);
result.ancestry = std::move(parseResult.ancestry); result.ancestry = std::move(parseResult.ancestry);
reportFragmentString(reporter, tryParse->fragmentToParse); reportFragmentString(reporter, tryParse->fragmentToParse);
return {FragmentTypeCheckStatus::Success, result}; return {FragmentTypeCheckStatus::Success, result};

View file

@ -1627,6 +1627,10 @@ void pruneUnnecessaryGenerics(
return true; return true;
seen.insert(ty); seen.insert(ty);
auto state = counter.generics.find(ty);
if (state && state->count == 0)
return true;
return !get<GenericType>(ty); return !get<GenericType>(ty);
} }
); );
@ -1650,6 +1654,10 @@ void pruneUnnecessaryGenerics(
return true; return true;
seen2.insert(tp); seen2.insert(tp);
auto state = counter.genericPacks.find(tp);
if (state && state->count == 0)
return true;
return !get<GenericTypePack>(tp); return !get<GenericTypePack>(tp);
} }
); );

View file

@ -19,7 +19,6 @@ LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauAttribute) LUAU_FASTFLAG(LuauAttribute)
LUAU_FASTFLAGVARIABLE(LintRedundantNativeAttribute) LUAU_FASTFLAGVARIABLE(LintRedundantNativeAttribute)
LUAU_FASTFLAG(LuauDeprecatedAttribute)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
namespace Luau namespace Luau
@ -2295,53 +2294,36 @@ private:
bool visit(AstExprLocal* node) override bool visit(AstExprLocal* node) override
{ {
if (FFlag::LuauDeprecatedAttribute)
{
const FunctionType* fty = getFunctionType(node); const FunctionType* fty = getFunctionType(node);
bool shouldReport = fty && fty->isDeprecatedFunction && !inScope(fty); bool shouldReport = fty && fty->isDeprecatedFunction && !inScope(fty);
if (shouldReport) if (shouldReport)
report(node->location, node->local->name.value); report(node->location, node->local->name.value);
}
return true; return true;
} }
bool visit(AstExprGlobal* node) override bool visit(AstExprGlobal* node) override
{ {
if (FFlag::LuauDeprecatedAttribute)
{
const FunctionType* fty = getFunctionType(node); const FunctionType* fty = getFunctionType(node);
bool shouldReport = fty && fty->isDeprecatedFunction && !inScope(fty); bool shouldReport = fty && fty->isDeprecatedFunction && !inScope(fty);
if (shouldReport) if (shouldReport)
report(node->location, node->name.value); report(node->location, node->name.value);
}
return true; return true;
} }
bool visit(AstStatLocalFunction* node) override bool visit(AstStatLocalFunction* node) override
{ {
if (FFlag::LuauDeprecatedAttribute)
{
check(node->func); check(node->func);
return false; return false;
}
else
return true;
} }
bool visit(AstStatFunction* node) override bool visit(AstStatFunction* node) override
{ {
if (FFlag::LuauDeprecatedAttribute)
{
check(node->func); check(node->func);
return false; return false;
}
else
return true;
} }
bool visit(AstExprIndexName* node) override bool visit(AstExprIndexName* node) override
@ -2385,13 +2367,13 @@ private:
{ {
if (const ExternType* cty = get<ExternType>(ty)) if (const ExternType* cty = get<ExternType>(ty))
{ {
const Property* prop = lookupExternTypeProp(cty, node->index.value); if (const Property* prop = lookupExternTypeProp(cty, node->index.value))
if (prop && prop->deprecated)
report(node->location, *prop, cty->name.c_str(), node->index.value);
else if (FFlag::LuauDeprecatedAttribute && prop)
{ {
if (std::optional<TypeId> ty = prop->readTy) if (prop->deprecated)
{
report(node->location, *prop, cty->name.c_str(), node->index.value);
}
else if (std::optional<TypeId> ty = prop->readTy)
{ {
const FunctionType* fty = get<FunctionType>(follow(ty)); const FunctionType* fty = get<FunctionType>(follow(ty));
bool shouldReport = fty && fty->isDeprecatedFunction && !inScope(fty); bool shouldReport = fty && fty->isDeprecatedFunction && !inScope(fty);
@ -2423,7 +2405,7 @@ private:
else else
report(node->location, prop->second, tty->name ? tty->name->c_str() : nullptr, node->index.value); report(node->location, prop->second, tty->name ? tty->name->c_str() : nullptr, node->index.value);
} }
else if (FFlag::LuauDeprecatedAttribute) else
{ {
if (std::optional<TypeId> ty = prop->second.readTy) if (std::optional<TypeId> ty = prop->second.readTy)
{ {
@ -2462,7 +2444,6 @@ private:
void check(AstExprFunction* func) void check(AstExprFunction* func)
{ {
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
LUAU_ASSERT(func); LUAU_ASSERT(func);
const FunctionType* fty = getFunctionType(func); const FunctionType* fty = getFunctionType(func);
@ -2492,8 +2473,6 @@ private:
void report(const Location& location, const char* tableName, const char* functionName) void report(const Location& location, const char* tableName, const char* functionName)
{ {
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
if (tableName) if (tableName)
emitWarning(*context, LintWarning::Code_DeprecatedApi, location, "Member '%s.%s' is deprecated", tableName, functionName); emitWarning(*context, LintWarning::Code_DeprecatedApi, location, "Member '%s.%s' is deprecated", tableName, functionName);
else else
@ -2502,8 +2481,6 @@ private:
void report(const Location& location, const char* functionName) void report(const Location& location, const char* functionName)
{ {
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
emitWarning(*context, LintWarning::Code_DeprecatedApi, location, "Function '%s' is deprecated", functionName); emitWarning(*context, LintWarning::Code_DeprecatedApi, location, "Function '%s' is deprecated", functionName);
} }
@ -2511,7 +2488,6 @@ private:
void pushScope(const FunctionType* fty) void pushScope(const FunctionType* fty)
{ {
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
LUAU_ASSERT(fty); LUAU_ASSERT(fty);
functionTypeScopeStack.push_back(fty); functionTypeScopeStack.push_back(fty);
@ -2519,7 +2495,6 @@ private:
void popScope(const FunctionType* fty) void popScope(const FunctionType* fty)
{ {
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
LUAU_ASSERT(fty); LUAU_ASSERT(fty);
LUAU_ASSERT(fty == functionTypeScopeStack.back()); LUAU_ASSERT(fty == functionTypeScopeStack.back());
@ -2528,7 +2503,6 @@ private:
bool inScope(const FunctionType* fty) const bool inScope(const FunctionType* fty) const
{ {
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
LUAU_ASSERT(fty); LUAU_ASSERT(fty);
return std::find(functionTypeScopeStack.begin(), functionTypeScopeStack.end(), fty) != functionTypeScopeStack.end(); return std::find(functionTypeScopeStack.begin(), functionTypeScopeStack.end(), fty) != functionTypeScopeStack.end();
@ -2536,8 +2510,6 @@ private:
const FunctionType* getFunctionType(AstExpr* node) const FunctionType* getFunctionType(AstExpr* node)
{ {
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
std::optional<TypeId> ty = context->getType(node); std::optional<TypeId> ty = context->getType(node);
if (!ty) if (!ty)
return nullptr; return nullptr;

View file

@ -11,7 +11,6 @@ LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 10000)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTINTVARIABLE(LuauTarjanPreallocationSize, 256) LUAU_FASTINTVARIABLE(LuauTarjanPreallocationSize, 256)
LUAU_FASTFLAG(LuauSyntheticErrors) LUAU_FASTFLAG(LuauSyntheticErrors)
LUAU_FASTFLAG(LuauDeprecatedAttribute)
namespace Luau namespace Luau
{ {
@ -101,8 +100,7 @@ static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log)
clone.tags = a.tags; clone.tags = a.tags;
clone.argNames = a.argNames; clone.argNames = a.argNames;
clone.isCheckedFunction = a.isCheckedFunction; clone.isCheckedFunction = a.isCheckedFunction;
if (FFlag::LuauDeprecatedAttribute) clone.isDeprecatedFunction = a.isDeprecatedFunction;
clone.isDeprecatedFunction = a.isDeprecatedFunction;
return dest.addType(std::move(clone)); return dest.addType(std::move(clone));
} }
else if constexpr (std::is_same_v<T, TableType>) else if constexpr (std::is_same_v<T, TableType>)

View file

@ -22,6 +22,7 @@
LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity) LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity)
LUAU_FASTINTVARIABLE(LuauSubtypingReasoningLimit, 100) LUAU_FASTINTVARIABLE(LuauSubtypingReasoningLimit, 100)
LUAU_FASTFLAGVARIABLE(LuauSubtypingEnableReasoningLimit) LUAU_FASTFLAGVARIABLE(LuauSubtypingEnableReasoningLimit)
LUAU_FASTFLAGVARIABLE(LuauSubtypeGenericsAndNegations)
namespace Luau namespace Luau
{ {
@ -669,6 +670,18 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
result = {false}; result = {false};
else if (get<ErrorType>(subTy)) else if (get<ErrorType>(subTy))
result = {true}; result = {true};
else if (auto subGeneric = get<GenericType>(subTy); FFlag::LuauSubtypeGenericsAndNegations && subGeneric && variance == Variance::Covariant)
{
bool ok = bindGeneric(env, subTy, superTy);
result.isSubtype = ok;
result.isCacheable = false;
}
else if (auto superGeneric = get<GenericType>(superTy); FFlag::LuauSubtypeGenericsAndNegations && superGeneric && variance == Variance::Contravariant)
{
bool ok = bindGeneric(env, subTy, superTy);
result.isSubtype = ok;
result.isCacheable = false;
}
else if (auto p = get2<NegationType, NegationType>(subTy, superTy)) else if (auto p = get2<NegationType, NegationType>(subTy, superTy))
result = isCovariantWith(env, p.first->ty, p.second->ty, scope).withBothComponent(TypePath::TypeField::Negated); result = isCovariantWith(env, p.first->ty, p.second->ty, scope).withBothComponent(TypePath::TypeField::Negated);
else if (auto subNegation = get<NegationType>(subTy)) else if (auto subNegation = get<NegationType>(subTy))
@ -711,13 +724,13 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
result = isCovariantWith(env, subTy, superTypeFunctionInstance, scope); result = isCovariantWith(env, subTy, superTypeFunctionInstance, scope);
} }
else if (auto subGeneric = get<GenericType>(subTy); subGeneric && variance == Variance::Covariant) else if (auto subGeneric = get<GenericType>(subTy); !FFlag::LuauSubtypeGenericsAndNegations && subGeneric && variance == Variance::Covariant)
{ {
bool ok = bindGeneric(env, subTy, superTy); bool ok = bindGeneric(env, subTy, superTy);
result.isSubtype = ok; result.isSubtype = ok;
result.isCacheable = false; result.isCacheable = false;
} }
else if (auto superGeneric = get<GenericType>(superTy); superGeneric && variance == Variance::Contravariant) else if (auto superGeneric = get<GenericType>(superTy); !FFlag::LuauSubtypeGenericsAndNegations && superGeneric && variance == Variance::Contravariant)
{ {
bool ok = bindGeneric(env, subTy, superTy); bool ok = bindGeneric(env, subTy, superTy);
result.isSubtype = ok; result.isSubtype = ok;

View file

@ -13,8 +13,6 @@
#include "Luau/TypeUtils.h" #include "Luau/TypeUtils.h"
#include "Luau/Unifier2.h" #include "Luau/Unifier2.h"
LUAU_FASTFLAGVARIABLE(LuauBidirectionalInferenceCollectIndexerTypes)
LUAU_FASTFLAGVARIABLE(LuauBidirectionalFailsafe)
LUAU_FASTFLAGVARIABLE(LuauBidirectionalInferenceElideAssert) LUAU_FASTFLAGVARIABLE(LuauBidirectionalInferenceElideAssert)
namespace Luau namespace Luau
@ -148,22 +146,13 @@ TypeId matchLiteralType(
expectedType = follow(expectedType); expectedType = follow(expectedType);
exprType = follow(exprType); exprType = follow(exprType);
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes)
{ // The intent of `matchLiteralType` is to upcast values when it's safe
// The intent of `matchLiteralType` is to upcast values when it's safe // to do so. it's always safe to upcast to `any` or `unknown`, so we
// to do so. it's always safe to upcast to `any` or `unknown`, so we // can unconditionally do so here.
// can unconditionally do so here. if (is<AnyType, UnknownType>(expectedType))
if (is<AnyType, UnknownType>(expectedType)) return expectedType;
return expectedType;
}
else
{
if (get<AnyType>(expectedType) || get<UnknownType>(expectedType))
{
// "Narrowing" to unknown or any is not going to do anything useful.
return exprType;
}
}
if (expr->is<AstExprConstantString>()) if (expr->is<AstExprConstantString>())
@ -250,7 +239,7 @@ TypeId matchLiteralType(
// { x = {}, x = 42 } // { x = {}, x = 42 }
// //
// The type of this will be `{ x: number }` // The type of this will be `{ x: number }`
if (FFlag::LuauBidirectionalFailsafe && !tableTy) if (!tableTy)
return exprType; return exprType;
LUAU_ASSERT(tableTy); LUAU_ASSERT(tableTy);
@ -291,7 +280,7 @@ TypeId matchLiteralType(
auto it = tableTy->props.find(keyStr); auto it = tableTy->props.find(keyStr);
// This can occur, potentially, if we are re-entrant. // This can occur, potentially, if we are re-entrant.
if (FFlag::LuauBidirectionalFailsafe && it == tableTy->props.end()) if (it == tableTy->props.end())
continue; continue;
LUAU_ASSERT(it != tableTy->props.end()); LUAU_ASSERT(it != tableTy->props.end());
@ -330,18 +319,8 @@ TypeId matchLiteralType(
toBlock toBlock
); );
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes) indexerKeyTypes.insert(arena->addType(SingletonType{StringSingleton{keyStr}}));
{ indexerValueTypes.insert(matchedType);
indexerKeyTypes.insert(arena->addType(SingletonType{StringSingleton{keyStr}}));
indexerValueTypes.insert(matchedType);
}
else
{
if (tableTy->indexer)
unifier->unify(matchedType, tableTy->indexer->indexResultType);
else
tableTy->indexer = TableIndexer{expectedTableTy->indexer->indexType, matchedType};
}
keysToDelete.insert(item.key->as<AstExprConstantString>()); keysToDelete.insert(item.key->as<AstExprConstantString>());
} }
@ -409,7 +388,7 @@ TypeId matchLiteralType(
} }
else if (item.kind == AstExprTable::Item::List) else if (item.kind == AstExprTable::Item::List)
{ {
if (!FFlag::LuauBidirectionalInferenceCollectIndexerTypes || !FFlag::LuauBidirectionalInferenceElideAssert) if (!FFlag::LuauBidirectionalInferenceElideAssert)
LUAU_ASSERT(tableTy->indexer); LUAU_ASSERT(tableTy->indexer);
if (expectedTableTy->indexer) if (expectedTableTy->indexer)
@ -431,17 +410,8 @@ TypeId matchLiteralType(
toBlock toBlock
); );
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes) indexerKeyTypes.insert(builtinTypes->numberType);
{ indexerValueTypes.insert(matchedType);
indexerKeyTypes.insert(builtinTypes->numberType);
indexerValueTypes.insert(matchedType);
}
else
{
// if the index result type is the prop type, we can replace it with the matched type here.
if (tableTy->indexer->indexResultType == *propTy)
tableTy->indexer->indexResultType = matchedType;
}
} }
} }
else if (item.kind == AstExprTable::Item::General) else if (item.kind == AstExprTable::Item::General)
@ -464,11 +434,8 @@ TypeId matchLiteralType(
if (!item.key->as<AstExprConstantString>() && expectedTableTy->indexer) if (!item.key->as<AstExprConstantString>() && expectedTableTy->indexer)
(*astExpectedTypes)[item.key] = expectedTableTy->indexer->indexType; (*astExpectedTypes)[item.key] = expectedTableTy->indexer->indexType;
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes) indexerKeyTypes.insert(tKey);
{ indexerValueTypes.insert(tProp);
indexerKeyTypes.insert(tKey);
indexerValueTypes.insert(tProp);
}
} }
else else
LUAU_ASSERT(!"Unexpected"); LUAU_ASSERT(!"Unexpected");
@ -530,7 +497,7 @@ TypeId matchLiteralType(
// have one too. // have one too.
// TODO: If the expected table also has an indexer, we might want to // TODO: If the expected table also has an indexer, we might want to
// push the expected indexer's types into it. // push the expected indexer's types into it.
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes && expectedTableTy->indexer) if (expectedTableTy->indexer)
{ {
if (indexerValueTypes.size() > 0 && indexerKeyTypes.size() > 0) if (indexerValueTypes.size() > 0 && indexerKeyTypes.size() > 0)
{ {

View file

@ -11,10 +11,9 @@
#include <math.h> #include <math.h>
LUAU_FASTFLAG(LuauStoreCSTData2) LUAU_FASTFLAG(LuauStoreCSTData2)
LUAU_FASTFLAG(LuauAstTypeGroup3)
LUAU_FASTFLAG(LuauParseOptionalAsNode2)
LUAU_FASTFLAG(LuauFixFunctionWithAttributesStartLocation) LUAU_FASTFLAG(LuauFixFunctionWithAttributesStartLocation)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAG(LuauStoreLocalAnnotationColonPositions)
namespace namespace
{ {
@ -368,7 +367,7 @@ struct Printer_DEPRECATED
else if (typeCount == 1) else if (typeCount == 1)
{ {
bool shouldParenthesize = unconditionallyParenthesize && (list.types.size == 0 || !list.types.data[0]->is<AstTypeGroup>()); bool shouldParenthesize = unconditionallyParenthesize && (list.types.size == 0 || !list.types.data[0]->is<AstTypeGroup>());
if (FFlag::LuauAstTypeGroup3 ? shouldParenthesize : unconditionallyParenthesize) if (shouldParenthesize)
writer.symbol("("); writer.symbol("(");
// Only variadic tail // Only variadic tail
@ -381,7 +380,7 @@ struct Printer_DEPRECATED
visualizeTypeAnnotation(*list.types.data[0]); visualizeTypeAnnotation(*list.types.data[0]);
} }
if (FFlag::LuauAstTypeGroup3 ? shouldParenthesize : unconditionallyParenthesize) if (shouldParenthesize)
writer.symbol(")"); writer.symbol(")");
} }
else else
@ -1233,18 +1232,9 @@ struct Printer_DEPRECATED
AstType* l = a->types.data[0]; AstType* l = a->types.data[0];
AstType* r = a->types.data[1]; AstType* r = a->types.data[1];
if (FFlag::LuauParseOptionalAsNode2) auto lta = l->as<AstTypeReference>();
{ if (lta && lta->name == "nil" && !r->is<AstTypeOptional>())
auto lta = l->as<AstTypeReference>(); std::swap(l, r);
if (lta && lta->name == "nil" && !r->is<AstTypeOptional>())
std::swap(l, r);
}
else
{
auto lta = l->as<AstTypeReference>();
if (lta && lta->name == "nil")
std::swap(l, r);
}
// it's still possible that we had a (T | U) or (T | nil) and not (nil | T) // it's still possible that we had a (T | U) or (T | nil) and not (nil | T)
auto rta = r->as<AstTypeReference>(); auto rta = r->as<AstTypeReference>();
@ -1267,13 +1257,10 @@ struct Printer_DEPRECATED
for (size_t i = 0; i < a->types.size; ++i) for (size_t i = 0; i < a->types.size; ++i)
{ {
if (FFlag::LuauParseOptionalAsNode2) if (a->types.data[i]->is<AstTypeOptional>())
{ {
if (a->types.data[i]->is<AstTypeOptional>()) writer.symbol("?");
{ continue;
writer.symbol("?");
continue;
}
} }
if (i > 0) if (i > 0)
@ -1359,14 +1346,15 @@ struct Printer
return nullptr; return nullptr;
} }
void visualize(const AstLocal& local) void visualize(const AstLocal& local, Position colonPosition)
{ {
advance(local.location.begin); advance(local.location.begin);
writer.identifier(local.name.value); writer.identifier(local.name.value);
if (writeTypes && local.annotation) if (writeTypes && local.annotation)
{ {
// TODO: handle spacing for type annotation if (FFlag::LuauStoreLocalAnnotationColonPositions)
advance(colonPosition);
writer.symbol(":"); writer.symbol(":");
visualizeTypeAnnotation(*local.annotation); visualizeTypeAnnotation(*local.annotation);
} }
@ -1428,7 +1416,7 @@ struct Printer
else if (typeCount == 1) else if (typeCount == 1)
{ {
bool shouldParenthesize = unconditionallyParenthesize && (list.types.size == 0 || !list.types.data[0]->is<AstTypeGroup>()); bool shouldParenthesize = unconditionallyParenthesize && (list.types.size == 0 || !list.types.data[0]->is<AstTypeGroup>());
if (FFlag::LuauAstTypeGroup3 ? shouldParenthesize : unconditionallyParenthesize) if (shouldParenthesize)
{ {
if (openParenthesesPosition) if (openParenthesesPosition)
advance(*openParenthesesPosition); advance(*openParenthesesPosition);
@ -1447,7 +1435,7 @@ struct Printer
visualizeTypeAnnotation(*list.types.data[0]); visualizeTypeAnnotation(*list.types.data[0]);
} }
if (FFlag::LuauAstTypeGroup3 ? shouldParenthesize : unconditionallyParenthesize) if (shouldParenthesize)
{ {
if (closeParenthesesPosition) if (closeParenthesesPosition)
advance(*closeParenthesesPosition); advance(*closeParenthesesPosition);
@ -1973,10 +1961,16 @@ struct Printer
writer.keyword("local"); writer.keyword("local");
CommaSeparatorInserter varComma(writer, cstNode ? cstNode->varsCommaPositions.begin() : nullptr); CommaSeparatorInserter varComma(writer, cstNode ? cstNode->varsCommaPositions.begin() : nullptr);
for (const auto& local : a->vars) for (size_t i = 0; i < a->vars.size; i++)
{ {
varComma(); varComma();
visualize(*local); if (FFlag::LuauStoreLocalAnnotationColonPositions && cstNode)
{
LUAU_ASSERT(cstNode->varsAnnotationColonPositions.size > i);
visualize(*a->vars.data[i], cstNode->varsAnnotationColonPositions.data[i]);
}
else
visualize(*a->vars.data[i], Position{0, 0});
} }
if (a->equalsSignLocation) if (a->equalsSignLocation)
@ -1999,7 +1993,11 @@ struct Printer
writer.keyword("for"); writer.keyword("for");
visualize(*a->var); if (FFlag::LuauStoreLocalAnnotationColonPositions)
visualize(*a->var, cstNode ? cstNode->annotationColonPosition : Position{0, 0});
else
visualize(*a->var, Position{0,0});
if (cstNode) if (cstNode)
advance(cstNode->equalsPosition); advance(cstNode->equalsPosition);
writer.symbol("="); writer.symbol("=");
@ -2029,10 +2027,16 @@ struct Printer
writer.keyword("for"); writer.keyword("for");
CommaSeparatorInserter varComma(writer, cstNode ? cstNode->varsCommaPositions.begin() : nullptr); CommaSeparatorInserter varComma(writer, cstNode ? cstNode->varsCommaPositions.begin() : nullptr);
for (const auto& var : a->vars) for (size_t i = 0; i < a->vars.size; i++)
{ {
varComma(); varComma();
visualize(*var); if (FFlag::LuauStoreLocalAnnotationColonPositions && cstNode)
{
LUAU_ASSERT(cstNode->varsAnnotationColonPositions.size > i);
visualize(*a->vars.data[i], cstNode->varsAnnotationColonPositions.data[i]);
}
else
visualize(*a->vars.data[i], Position{0, 0});
} }
advance(a->inLocation.begin); advance(a->inLocation.begin);
@ -2363,6 +2367,11 @@ struct Printer
writer.identifier(local->name.value); writer.identifier(local->name.value);
if (writeTypes && local->annotation) if (writeTypes && local->annotation)
{ {
if (FFlag::LuauStoreReturnTypesAsPackOnAst && FFlag::LuauStoreLocalAnnotationColonPositions && cstNode)
{
LUAU_ASSERT(cstNode->argsAnnotationColonPositions.size > i);
advance(cstNode->argsAnnotationColonPositions.data[i]);
}
writer.symbol(":"); writer.symbol(":");
visualizeTypeAnnotation(*local->annotation); visualizeTypeAnnotation(*local->annotation);
} }
@ -2376,6 +2385,11 @@ struct Printer
if (func.varargAnnotation) if (func.varargAnnotation)
{ {
if (FFlag::LuauStoreReturnTypesAsPackOnAst && FFlag::LuauStoreLocalAnnotationColonPositions && cstNode)
{
LUAU_ASSERT(cstNode->varargAnnotationColonPosition != Position({0, 0}));
advance(cstNode->varargAnnotationColonPosition);
}
writer.symbol(":"); writer.symbol(":");
visualizeTypePackAnnotation(*func.varargAnnotation, true); visualizeTypePackAnnotation(*func.varargAnnotation, true);
} }
@ -2755,18 +2769,9 @@ struct Printer
AstType* l = a->types.data[0]; AstType* l = a->types.data[0];
AstType* r = a->types.data[1]; AstType* r = a->types.data[1];
if (FFlag::LuauParseOptionalAsNode2) auto lta = l->as<AstTypeReference>();
{ if (lta && lta->name == "nil" && !r->is<AstTypeOptional>())
auto lta = l->as<AstTypeReference>(); std::swap(l, r);
if (lta && lta->name == "nil" && !r->is<AstTypeOptional>())
std::swap(l, r);
}
else
{
auto lta = l->as<AstTypeReference>();
if (lta && lta->name == "nil")
std::swap(l, r);
}
// it's still possible that we had a (T | U) or (T | nil) and not (nil | T) // it's still possible that we had a (T | U) or (T | nil) and not (nil | T)
auto rta = r->as<AstTypeReference>(); auto rta = r->as<AstTypeReference>();
@ -2796,21 +2801,17 @@ struct Printer
size_t separatorIndex = 0; size_t separatorIndex = 0;
for (size_t i = 0; i < a->types.size; ++i) for (size_t i = 0; i < a->types.size; ++i)
{ {
if (FFlag::LuauParseOptionalAsNode2) if (const auto optional = a->types.data[i]->as<AstTypeOptional>())
{ {
if (const auto optional = a->types.data[i]->as<AstTypeOptional>()) advance(optional->location.begin);
{ writer.symbol("?");
advance(optional->location.begin); continue;
writer.symbol("?");
continue;
}
} }
if (i > 0) if (i > 0)
{ {
if (cstNode && FFlag::LuauParseOptionalAsNode2) if (cstNode)
{ {
// separatorIndex is only valid if `?` is handled as an AstTypeOptional
advance(cstNode->separatorPositions.data[separatorIndex]); advance(cstNode->separatorPositions.data[separatorIndex]);
separatorIndex++; separatorIndex++;
} }

View file

@ -30,11 +30,11 @@
LUAU_FASTFLAG(DebugLuauMagicTypes) LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAGVARIABLE(LuauImproveTypePathsInErrors)
LUAU_FASTFLAG(LuauUserTypeFunTypecheck) LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
LUAU_FASTFLAGVARIABLE(LuauTypeCheckerAcceptNumberConcats) LUAU_FASTFLAGVARIABLE(LuauTypeCheckerAcceptNumberConcats)
LUAU_FASTFLAGVARIABLE(LuauTypeCheckerStricterIndexCheck) LUAU_FASTFLAGVARIABLE(LuauTypeCheckerStricterIndexCheck)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAGVARIABLE(LuauReportSubtypingErrors)
namespace Luau namespace Luau
{ {
@ -1323,8 +1323,21 @@ void TypeChecker2::visit(AstExprConstantBool* expr)
NotNull<Scope> scope{findInnermostScope(expr->location)}; NotNull<Scope> scope{findInnermostScope(expr->location)};
const SubtypingResult r = subtyping->isSubtype(bestType, inferredType, scope); const SubtypingResult r = subtyping->isSubtype(bestType, inferredType, scope);
if (!r.isSubtype && !isErrorSuppressing(expr->location, inferredType)) if (FFlag::LuauReportSubtypingErrors)
reportError(TypeMismatch{inferredType, bestType}, expr->location); {
if (!isErrorSuppressing(expr->location, inferredType))
{
if (!r.isSubtype)
reportError(TypeMismatch{inferredType, bestType}, expr->location);
reportErrors(r.errors);
}
}
else
{
if (!r.isSubtype && !isErrorSuppressing(expr->location, inferredType))
reportError(TypeMismatch{inferredType, bestType}, expr->location);
}
} }
void TypeChecker2::visit(AstExprConstantNumber* expr) void TypeChecker2::visit(AstExprConstantNumber* expr)
@ -1348,8 +1361,21 @@ void TypeChecker2::visit(AstExprConstantString* expr)
NotNull<Scope> scope{findInnermostScope(expr->location)}; NotNull<Scope> scope{findInnermostScope(expr->location)};
const SubtypingResult r = subtyping->isSubtype(bestType, inferredType, scope); const SubtypingResult r = subtyping->isSubtype(bestType, inferredType, scope);
if (!r.isSubtype && !isErrorSuppressing(expr->location, inferredType)) if (FFlag::LuauReportSubtypingErrors)
reportError(TypeMismatch{inferredType, bestType}, expr->location); {
if (!isErrorSuppressing(expr->location, inferredType))
{
if (!r.isSubtype)
reportError(TypeMismatch{inferredType, bestType}, expr->location);
reportErrors(r.errors);
}
}
else
{
if (!r.isSubtype && !isErrorSuppressing(expr->location, inferredType))
reportError(TypeMismatch{inferredType, bestType}, expr->location);
}
} }
void TypeChecker2::visit(AstExprLocal* expr) void TypeChecker2::visit(AstExprLocal* expr)
@ -1417,6 +1443,12 @@ void TypeChecker2::visitCall(AstExprCall* call)
if (result.isSubtype) if (result.isSubtype)
fnTy = follow(*selectedOverloadTy); fnTy = follow(*selectedOverloadTy);
if (FFlag::LuauReportSubtypingErrors)
{
if (!isErrorSuppressing(call->location, *selectedOverloadTy))
reportErrors(std::move(result.errors));
}
if (result.normalizationTooComplex) if (result.normalizationTooComplex)
{ {
reportError(NormalizationTooComplex{}, call->func->location); reportError(NormalizationTooComplex{}, call->func->location);
@ -2728,61 +2760,41 @@ Reasonings TypeChecker2::explainReasonings_(TID subTy, TID superTy, Location loc
if (!subLeafTy && !superLeafTy && !subLeafTp && !superLeafTp) if (!subLeafTy && !superLeafTy && !subLeafTp && !superLeafTp)
ice->ice("Subtyping test returned a reasoning where one path ends at a type and the other ends at a pack.", location); ice->ice("Subtyping test returned a reasoning where one path ends at a type and the other ends at a pack.", location);
if (FFlag::LuauImproveTypePathsInErrors) std::string relation = "a subtype of";
{ if (reasoning.variance == SubtypingVariance::Invariant)
std::string relation = "a subtype of"; relation = "exactly";
if (reasoning.variance == SubtypingVariance::Invariant) else if (reasoning.variance == SubtypingVariance::Contravariant)
relation = "exactly"; relation = "a supertype of";
else if (reasoning.variance == SubtypingVariance::Contravariant)
relation = "a supertype of";
std::string subLeafAsString = toString(subLeaf); std::string subLeafAsString = toString(subLeaf);
// if the string is empty, it must be an empty type pack // if the string is empty, it must be an empty type pack
if (subLeafAsString.empty()) if (subLeafAsString.empty())
subLeafAsString = "()"; subLeafAsString = "()";
std::string superLeafAsString = toString(superLeaf); std::string superLeafAsString = toString(superLeaf);
// if the string is empty, it must be an empty type pack // if the string is empty, it must be an empty type pack
if (superLeafAsString.empty()) if (superLeafAsString.empty())
superLeafAsString = "()"; superLeafAsString = "()";
std::stringstream baseReasonBuilder; std::stringstream baseReasonBuilder;
baseReasonBuilder << "`" << subLeafAsString << "` is not " << relation << " `" << superLeafAsString << "`"; baseReasonBuilder << "`" << subLeafAsString << "` is not " << relation << " `" << superLeafAsString << "`";
std::string baseReason = baseReasonBuilder.str(); std::string baseReason = baseReasonBuilder.str();
std::stringstream reason; std::stringstream reason;
if (reasoning.subPath == reasoning.superPath) if (reasoning.subPath == reasoning.superPath)
reason << toStringHuman(reasoning.subPath) << "`" << subLeafAsString << "` in the former type and `" << superLeafAsString reason << toStringHuman(reasoning.subPath) << "`" << subLeafAsString << "` in the former type and `" << superLeafAsString
<< "` in the latter type, and " << baseReason; << "` in the latter type, and " << baseReason;
else if (!reasoning.subPath.empty() && !reasoning.superPath.empty()) else if (!reasoning.subPath.empty() && !reasoning.superPath.empty())
reason << toStringHuman(reasoning.subPath) << "`" << subLeafAsString << "` and " << toStringHuman(reasoning.superPath) << "`" reason << toStringHuman(reasoning.subPath) << "`" << subLeafAsString << "` and " << toStringHuman(reasoning.superPath) << "`"
<< superLeafAsString << "`, and " << baseReason; << superLeafAsString << "`, and " << baseReason;
else if (!reasoning.subPath.empty()) else if (!reasoning.subPath.empty())
reason << toStringHuman(reasoning.subPath) << "`" << subLeafAsString << "`, which is not " << relation << " `" << superLeafAsString reason << toStringHuman(reasoning.subPath) << "`" << subLeafAsString << "`, which is not " << relation << " `" << superLeafAsString
<< "`"; << "`";
else
reason << toStringHuman(reasoning.superPath) << "`" << superLeafAsString << "`, and " << baseReason;
reasons.push_back(reason.str());
}
else else
{ reason << toStringHuman(reasoning.superPath) << "`" << superLeafAsString << "`, and " << baseReason;
std::string relation = "a subtype of";
if (reasoning.variance == SubtypingVariance::Invariant)
relation = "exactly";
else if (reasoning.variance == SubtypingVariance::Contravariant)
relation = "a supertype of";
std::string reason; reasons.push_back(reason.str());
if (reasoning.subPath == reasoning.superPath)
reason = "at " + toString(reasoning.subPath) + ", " + toString(subLeaf) + " is not " + relation + " " + toString(superLeaf);
else
reason = "type " + toString(subTy) + toString(reasoning.subPath, /* prefixDot */ true) + " (" + toString(subLeaf) + ") is not " +
relation + " " + toString(superTy) + toString(reasoning.superPath, /* prefixDot */ true) + " (" + toString(superLeaf) + ")";
reasons.push_back(reason);
}
// if we haven't already proved this isn't suppressing, we have to keep checking. // if we haven't already proved this isn't suppressing, we have to keep checking.
if (suppressed) if (suppressed)
@ -2850,6 +2862,12 @@ bool TypeChecker2::testIsSubtype(TypeId subTy, TypeId superTy, Location location
NotNull<Scope> scope{findInnermostScope(location)}; NotNull<Scope> scope{findInnermostScope(location)};
SubtypingResult r = subtyping->isSubtype(subTy, superTy, scope); SubtypingResult r = subtyping->isSubtype(subTy, superTy, scope);
if (FFlag::LuauReportSubtypingErrors)
{
if (!isErrorSuppressing(location, subTy))
reportErrors(std::move(r.errors));
}
if (r.normalizationTooComplex) if (r.normalizationTooComplex)
reportError(NormalizationTooComplex{}, location); reportError(NormalizationTooComplex{}, location);
@ -2864,6 +2882,12 @@ bool TypeChecker2::testIsSubtype(TypePackId subTy, TypePackId superTy, Location
NotNull<Scope> scope{findInnermostScope(location)}; NotNull<Scope> scope{findInnermostScope(location)};
SubtypingResult r = subtyping->isSubtype(subTy, superTy, scope); SubtypingResult r = subtyping->isSubtype(subTy, superTy, scope);
if (FFlag::LuauReportSubtypingErrors)
{
if (!isErrorSuppressing(location, subTy))
reportErrors(std::move(r.errors));
}
if (r.normalizationTooComplex) if (r.normalizationTooComplex)
reportError(NormalizationTooComplex{}, location); reportError(NormalizationTooComplex{}, location);

View file

@ -56,17 +56,16 @@ LUAU_FASTFLAGVARIABLE(LuauMetatableTypeFunctions)
LUAU_FASTFLAGVARIABLE(LuauIndexTypeFunctionImprovements) LUAU_FASTFLAGVARIABLE(LuauIndexTypeFunctionImprovements)
LUAU_FASTFLAGVARIABLE(LuauIndexTypeFunctionFunctionMetamethods) LUAU_FASTFLAGVARIABLE(LuauIndexTypeFunctionFunctionMetamethods)
LUAU_FASTFLAGVARIABLE(LuauIntersectNotNil) LUAU_FASTFLAGVARIABLE(LuauIntersectNotNil)
LUAU_FASTFLAGVARIABLE(LuauSkipNoRefineDuringRefinement)
LUAU_FASTFLAGVARIABLE(LuauMetatablesHaveLength) LUAU_FASTFLAGVARIABLE(LuauMetatablesHaveLength)
LUAU_FASTFLAGVARIABLE(LuauIndexAnyIsAny) LUAU_FASTFLAGVARIABLE(LuauIndexAnyIsAny)
LUAU_FASTFLAGVARIABLE(LuauFixCyclicIndexInIndexer) LUAU_FASTFLAGVARIABLE(LuauFixCyclicIndexInIndexer)
LUAU_FASTFLAGVARIABLE(LuauSimplyRefineNotNil) LUAU_FASTFLAGVARIABLE(LuauSimplyRefineNotNil)
LUAU_FASTFLAGVARIABLE(LuauIndexDeferPendingIndexee) LUAU_FASTFLAGVARIABLE(LuauIndexDeferPendingIndexee)
LUAU_FASTFLAGVARIABLE(LuauNewTypeFunReductionChecks2) LUAU_FASTFLAGVARIABLE(LuauNewTypeFunReductionChecks2)
LUAU_FASTFLAGVARIABLE(LuauReduceUnionFollowUnionType)
LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect) LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect)
LUAU_FASTFLAGVARIABLE(LuauNarrowIntersectionNevers) LUAU_FASTFLAGVARIABLE(LuauNarrowIntersectionNevers)
LUAU_FASTFLAGVARIABLE(LuauRefineWaitForBlockedTypesInTarget) LUAU_FASTFLAGVARIABLE(LuauRefineWaitForBlockedTypesInTarget)
LUAU_FASTFLAGVARIABLE(LuauNotAllBinaryTypeFunsHaveDefaults)
namespace Luau namespace Luau
{ {
@ -2492,25 +2491,14 @@ struct CollectUnionTypeOptions : TypeOnceVisitor
bool visit(TypeId ty, const UnionType& ut) override bool visit(TypeId ty, const UnionType& ut) override
{ {
if (FFlag::LuauReduceUnionFollowUnionType) // If we have something like:
{ //
// If we have something like: // union<A | B, C | D>
// //
// union<A | B, C | D> // We probably just want to consider this to be the same as
// //
// We probably just want to consider this to be the same as // union<A, B, C, D>
// return true;
// union<A, B, C, D>
return true;
}
else
{
// Copy of the default visit method.
options.insert(ty);
if (isPending(ty, ctx->solver))
blockingTypes.insert(ty);
return false;
}
} }
bool visit(TypeId ty, const TypeFunctionInstanceType& tfit) override bool visit(TypeId ty, const TypeFunctionInstanceType& tfit) override
@ -3600,8 +3588,8 @@ void BuiltinTypeFunctions::addToScope(NotNull<TypeArena> arena, NotNull<Scope> s
return TypeFun{{genericT}, arena->addType(TypeFunctionInstanceType{NotNull{tf}, {t}, {}})}; return TypeFun{{genericT}, arena->addType(TypeFunctionInstanceType{NotNull{tf}, {t}, {}})};
}; };
// make a type function for a two-argument type function // make a type function for a two-argument type function with a default argument for the second type being the first
auto mkBinaryTypeFunction = [&](const TypeFunction* tf) auto mkBinaryTypeFunctionWithDefault = [&](const TypeFunction* tf)
{ {
TypeId t = arena->addType(GenericType{"T", Polarity::Negative}); TypeId t = arena->addType(GenericType{"T", Polarity::Negative});
TypeId u = arena->addType(GenericType{"U", Polarity::Negative}); TypeId u = arena->addType(GenericType{"U", Polarity::Negative});
@ -3611,31 +3599,53 @@ void BuiltinTypeFunctions::addToScope(NotNull<TypeArena> arena, NotNull<Scope> s
return TypeFun{{genericT, genericU}, arena->addType(TypeFunctionInstanceType{NotNull{tf}, {t, u}, {}})}; return TypeFun{{genericT, genericU}, arena->addType(TypeFunctionInstanceType{NotNull{tf}, {t, u}, {}})};
}; };
// make a two-argument type function without the default arguments
auto mkBinaryTypeFunction = [&](const TypeFunction* tf)
{
TypeId t = arena->addType(GenericType{"T", Polarity::Negative});
TypeId u = arena->addType(GenericType{"U", Polarity::Negative});
GenericTypeDefinition genericT{t};
GenericTypeDefinition genericU{u};
return TypeFun{{genericT, genericU}, arena->addType(TypeFunctionInstanceType{NotNull{tf}, {t, u}, {}})};
};
scope->exportedTypeBindings[lenFunc.name] = mkUnaryTypeFunction(&lenFunc); scope->exportedTypeBindings[lenFunc.name] = mkUnaryTypeFunction(&lenFunc);
scope->exportedTypeBindings[unmFunc.name] = mkUnaryTypeFunction(&unmFunc); scope->exportedTypeBindings[unmFunc.name] = mkUnaryTypeFunction(&unmFunc);
scope->exportedTypeBindings[addFunc.name] = mkBinaryTypeFunction(&addFunc); scope->exportedTypeBindings[addFunc.name] = mkBinaryTypeFunctionWithDefault(&addFunc);
scope->exportedTypeBindings[subFunc.name] = mkBinaryTypeFunction(&subFunc); scope->exportedTypeBindings[subFunc.name] = mkBinaryTypeFunctionWithDefault(&subFunc);
scope->exportedTypeBindings[mulFunc.name] = mkBinaryTypeFunction(&mulFunc); scope->exportedTypeBindings[mulFunc.name] = mkBinaryTypeFunctionWithDefault(&mulFunc);
scope->exportedTypeBindings[divFunc.name] = mkBinaryTypeFunction(&divFunc); scope->exportedTypeBindings[divFunc.name] = mkBinaryTypeFunctionWithDefault(&divFunc);
scope->exportedTypeBindings[idivFunc.name] = mkBinaryTypeFunction(&idivFunc); scope->exportedTypeBindings[idivFunc.name] = mkBinaryTypeFunctionWithDefault(&idivFunc);
scope->exportedTypeBindings[powFunc.name] = mkBinaryTypeFunction(&powFunc); scope->exportedTypeBindings[powFunc.name] = mkBinaryTypeFunctionWithDefault(&powFunc);
scope->exportedTypeBindings[modFunc.name] = mkBinaryTypeFunction(&modFunc); scope->exportedTypeBindings[modFunc.name] = mkBinaryTypeFunctionWithDefault(&modFunc);
scope->exportedTypeBindings[concatFunc.name] = mkBinaryTypeFunction(&concatFunc); scope->exportedTypeBindings[concatFunc.name] = mkBinaryTypeFunctionWithDefault(&concatFunc);
scope->exportedTypeBindings[ltFunc.name] = mkBinaryTypeFunction(&ltFunc); scope->exportedTypeBindings[ltFunc.name] = mkBinaryTypeFunctionWithDefault(&ltFunc);
scope->exportedTypeBindings[leFunc.name] = mkBinaryTypeFunction(&leFunc); scope->exportedTypeBindings[leFunc.name] = mkBinaryTypeFunctionWithDefault(&leFunc);
scope->exportedTypeBindings[eqFunc.name] = mkBinaryTypeFunction(&eqFunc); scope->exportedTypeBindings[eqFunc.name] = mkBinaryTypeFunctionWithDefault(&eqFunc);
scope->exportedTypeBindings[keyofFunc.name] = mkUnaryTypeFunction(&keyofFunc); scope->exportedTypeBindings[keyofFunc.name] = mkUnaryTypeFunction(&keyofFunc);
scope->exportedTypeBindings[rawkeyofFunc.name] = mkUnaryTypeFunction(&rawkeyofFunc); scope->exportedTypeBindings[rawkeyofFunc.name] = mkUnaryTypeFunction(&rawkeyofFunc);
scope->exportedTypeBindings[indexFunc.name] = mkBinaryTypeFunction(&indexFunc); if (FFlag::LuauNotAllBinaryTypeFunsHaveDefaults)
scope->exportedTypeBindings[rawgetFunc.name] = mkBinaryTypeFunction(&rawgetFunc); {
scope->exportedTypeBindings[indexFunc.name] = mkBinaryTypeFunction(&indexFunc);
scope->exportedTypeBindings[rawgetFunc.name] = mkBinaryTypeFunction(&rawgetFunc);
}
else
{
scope->exportedTypeBindings[indexFunc.name] = mkBinaryTypeFunctionWithDefault(&indexFunc);
scope->exportedTypeBindings[rawgetFunc.name] = mkBinaryTypeFunctionWithDefault(&rawgetFunc);
}
if (FFlag::LuauMetatableTypeFunctions) if (FFlag::LuauMetatableTypeFunctions)
{ {
scope->exportedTypeBindings[setmetatableFunc.name] = mkBinaryTypeFunction(&setmetatableFunc); if (FFlag::LuauNotAllBinaryTypeFunsHaveDefaults)
scope->exportedTypeBindings[setmetatableFunc.name] = mkBinaryTypeFunction(&setmetatableFunc);
else
scope->exportedTypeBindings[setmetatableFunc.name] = mkBinaryTypeFunctionWithDefault(&setmetatableFunc);
scope->exportedTypeBindings[getmetatableFunc.name] = mkUnaryTypeFunction(&getmetatableFunc); scope->exportedTypeBindings[getmetatableFunc.name] = mkUnaryTypeFunction(&getmetatableFunc);
} }
} }

View file

@ -37,7 +37,6 @@ LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAG(LuauRetainDefinitionAliasLocations) LUAU_FASTFLAG(LuauRetainDefinitionAliasLocations)
LUAU_FASTFLAGVARIABLE(LuauStatForInFix) LUAU_FASTFLAGVARIABLE(LuauStatForInFix)
LUAU_FASTFLAGVARIABLE(LuauReduceCheckBinaryExprStackPressure) LUAU_FASTFLAGVARIABLE(LuauReduceCheckBinaryExprStackPressure)
LUAU_FASTFLAGVARIABLE(LuauLimitIterationWhenCheckingArgumentCounts)
namespace Luau namespace Luau
{ {
@ -4120,15 +4119,12 @@ void TypeChecker::checkArgumentList(
int loopCount = 0; int loopCount = 0;
auto exceedsLoopCount = [&]() auto exceedsLoopCount = [&]()
{ {
if (FFlag::LuauLimitIterationWhenCheckingArgumentCounts) ++loopCount;
if (loopCount > FInt::LuauTypeInferTypePackLoopLimit)
{ {
++loopCount; state.reportError(TypeError{state.location, CodeTooComplex{}});
if (loopCount > FInt::LuauTypeInferTypePackLoopLimit) reportErrorCodeTooComplex(state.location);
{ return true;
state.reportError(TypeError{state.location, CodeTooComplex{}});
reportErrorCodeTooComplex(state.location);
return true;
}
} }
return false; return false;

View file

@ -19,6 +19,7 @@
LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauNonReentrantGeneralization2) LUAU_FASTFLAG(LuauNonReentrantGeneralization2)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
namespace Luau namespace Luau
{ {
@ -330,6 +331,10 @@ bool Unifier2::unify(TypeId subTy, const FunctionType* superFn)
{ {
if (FFlag::LuauNonReentrantGeneralization2) if (FFlag::LuauNonReentrantGeneralization2)
{ {
if (FFlag::DebugLuauGreedyGeneralization)
genericPack = follow(genericPack);
// TODO: Clip this follow() with DebugLuauGreedyGeneralization
const GenericTypePack* gen = get<GenericTypePack>(follow(genericPack)); const GenericTypePack* gen = get<GenericTypePack>(follow(genericPack));
if (gen) if (gen)
genericPackSubstitutions[genericPack] = freshTypePack(scope, gen->polarity); genericPackSubstitutions[genericPack] = freshTypePack(scope, gen->polarity);

View file

@ -116,7 +116,9 @@ public:
Position openGenericsPosition{0, 0}; Position openGenericsPosition{0, 0};
AstArray<Position> genericsCommaPositions; AstArray<Position> genericsCommaPositions;
Position closeGenericsPosition{0, 0}; Position closeGenericsPosition{0, 0};
AstArray<Position> argsAnnotationColonPositions;
AstArray<Position> argsCommaPositions; AstArray<Position> argsCommaPositions;
Position varargAnnotationColonPosition{0, 0};
Position returnSpecifierPosition{0, 0}; Position returnSpecifierPosition{0, 0};
}; };
@ -224,8 +226,9 @@ class CstStatLocal : public CstNode
public: public:
LUAU_CST_RTTI(CstStatLocal) LUAU_CST_RTTI(CstStatLocal)
CstStatLocal(AstArray<Position> varsCommaPositions, AstArray<Position> valuesCommaPositions); CstStatLocal(AstArray<Position> varsAnnotationColonPositions, AstArray<Position> varsCommaPositions, AstArray<Position> valuesCommaPositions);
AstArray<Position> varsAnnotationColonPositions;
AstArray<Position> varsCommaPositions; AstArray<Position> varsCommaPositions;
AstArray<Position> valuesCommaPositions; AstArray<Position> valuesCommaPositions;
}; };
@ -235,8 +238,9 @@ class CstStatFor : public CstNode
public: public:
LUAU_CST_RTTI(CstStatFor) LUAU_CST_RTTI(CstStatFor)
CstStatFor(Position equalsPosition, Position endCommaPosition, std::optional<Position> stepCommaPosition); CstStatFor(Position annotationColonPosition, Position equalsPosition, Position endCommaPosition, std::optional<Position> stepCommaPosition);
Position annotationColonPosition;
Position equalsPosition; Position equalsPosition;
Position endCommaPosition; Position endCommaPosition;
std::optional<Position> stepCommaPosition; std::optional<Position> stepCommaPosition;
@ -247,8 +251,9 @@ class CstStatForIn : public CstNode
public: public:
LUAU_CST_RTTI(CstStatForIn) LUAU_CST_RTTI(CstStatForIn)
CstStatForIn(AstArray<Position> varsCommaPositions, AstArray<Position> valuesCommaPositions); CstStatForIn(AstArray<Position> varsAnnotationColonPositions, AstArray<Position> varsCommaPositions, AstArray<Position> valuesCommaPositions);
AstArray<Position> varsAnnotationColonPositions;
AstArray<Position> varsCommaPositions; AstArray<Position> varsCommaPositions;
AstArray<Position> valuesCommaPositions; AstArray<Position> valuesCommaPositions;
}; };

View file

@ -196,6 +196,7 @@ private:
// binding ::= Name [`:` Type] // binding ::= Name [`:` Type]
Binding parseBinding(); Binding parseBinding();
AstArray<Position> extractAnnotationColonPositions(const TempVector<Binding>& bindings);
// bindinglist ::= (binding | `...') {`,' bindinglist} // bindinglist ::= (binding | `...') {`,' bindinglist}
// Returns the location of the vararg ..., or std::nullopt if the function is not vararg. // Returns the location of the vararg ..., or std::nullopt if the function is not vararg.
@ -203,7 +204,8 @@ private:
TempVector<Binding>& result, TempVector<Binding>& result,
bool allowDot3 = false, bool allowDot3 = false,
AstArray<Position>* commaPositions = nullptr, AstArray<Position>* commaPositions = nullptr,
std::optional<Position> initialCommaPosition = std::nullopt Position* initialCommaPosition = nullptr,
Position* varargAnnotationColonPosition = nullptr
); );
AstType* parseOptionalType(); AstType* parseOptionalType();
@ -452,10 +454,12 @@ private:
{ {
Name name; Name name;
AstType* annotation; AstType* annotation;
Position colonPosition;
explicit Binding(const Name& name, AstType* annotation = nullptr) explicit Binding(const Name& name, AstType* annotation = nullptr, Position colonPosition = {0,0})
: name(name) : name(name)
, annotation(annotation) , annotation(annotation)
, colonPosition(colonPosition)
{ {
} }
}; };

View file

@ -3,7 +3,6 @@
#include "Luau/Common.h" #include "Luau/Common.h"
LUAU_FASTFLAG(LuauDeprecatedAttribute);
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
namespace Luau namespace Luau
@ -11,8 +10,6 @@ namespace Luau
static bool hasAttributeInArray(const AstArray<AstAttr*> attributes, AstAttr::Type attributeType) static bool hasAttributeInArray(const AstArray<AstAttr*> attributes, AstAttr::Type attributeType)
{ {
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
for (const auto attribute : attributes) for (const auto attribute : attributes)
{ {
if (attribute->type == attributeType) if (attribute->type == attributeType)
@ -338,8 +335,6 @@ bool AstExprFunction::hasNativeAttribute() const
bool AstExprFunction::hasAttribute(const AstAttr::Type attributeType) const bool AstExprFunction::hasAttribute(const AstAttr::Type attributeType) const
{ {
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
return hasAttributeInArray(attributes, attributeType); return hasAttributeInArray(attributes, attributeType);
} }
@ -1026,8 +1021,6 @@ bool AstStatDeclareFunction::isCheckedFunction() const
bool AstStatDeclareFunction::hasAttribute(AstAttr::Type attributeType) const bool AstStatDeclareFunction::hasAttribute(AstAttr::Type attributeType) const
{ {
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
return hasAttributeInArray(attributes, attributeType); return hasAttributeInArray(attributes, attributeType);
} }
@ -1244,8 +1237,6 @@ bool AstTypeFunction::isCheckedFunction() const
bool AstTypeFunction::hasAttribute(AstAttr::Type attributeType) const bool AstTypeFunction::hasAttribute(AstAttr::Type attributeType) const
{ {
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
return hasAttributeInArray(attributes, attributeType); return hasAttributeInArray(attributes, attributeType);
} }

View file

@ -94,23 +94,26 @@ CstStatReturn::CstStatReturn(AstArray<Position> commaPositions)
{ {
} }
CstStatLocal::CstStatLocal(AstArray<Position> varsCommaPositions, AstArray<Position> valuesCommaPositions) CstStatLocal::CstStatLocal(AstArray<Position> varsAnnotationColonPositions, AstArray<Position> varsCommaPositions, AstArray<Position> valuesCommaPositions)
: CstNode(CstClassIndex()) : CstNode(CstClassIndex())
, varsAnnotationColonPositions(varsAnnotationColonPositions)
, varsCommaPositions(varsCommaPositions) , varsCommaPositions(varsCommaPositions)
, valuesCommaPositions(valuesCommaPositions) , valuesCommaPositions(valuesCommaPositions)
{ {
} }
CstStatFor::CstStatFor(Position equalsPosition, Position endCommaPosition, std::optional<Position> stepCommaPosition) CstStatFor::CstStatFor(Position annotationColonPosition, Position equalsPosition, Position endCommaPosition, std::optional<Position> stepCommaPosition)
: CstNode(CstClassIndex()) : CstNode(CstClassIndex())
, annotationColonPosition(annotationColonPosition)
, equalsPosition(equalsPosition) , equalsPosition(equalsPosition)
, endCommaPosition(endCommaPosition) , endCommaPosition(endCommaPosition)
, stepCommaPosition(stepCommaPosition) , stepCommaPosition(stepCommaPosition)
{ {
} }
CstStatForIn::CstStatForIn(AstArray<Position> varsCommaPositions, AstArray<Position> valuesCommaPositions) CstStatForIn::CstStatForIn(AstArray<Position> varsAnnotationColonPositions, AstArray<Position> varsCommaPositions, AstArray<Position> valuesCommaPositions)
: CstNode(CstClassIndex()) : CstNode(CstClassIndex())
, varsAnnotationColonPositions(varsAnnotationColonPositions)
, varsCommaPositions(varsCommaPositions) , varsCommaPositions(varsCommaPositions)
, valuesCommaPositions(valuesCommaPositions) , valuesCommaPositions(valuesCommaPositions)
{ {

View file

@ -19,14 +19,12 @@ LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
// See docs/SyntaxChanges.md for an explanation. // See docs/SyntaxChanges.md for an explanation.
LUAU_FASTFLAGVARIABLE(LuauSolverV2) LUAU_FASTFLAGVARIABLE(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauStoreCSTData2) LUAU_FASTFLAGVARIABLE(LuauStoreCSTData2)
LUAU_FASTFLAGVARIABLE(LuauAstTypeGroup3)
LUAU_FASTFLAGVARIABLE(LuauParseOptionalAsNode2)
LUAU_FASTFLAGVARIABLE(LuauDeclareExternType) LUAU_FASTFLAGVARIABLE(LuauDeclareExternType)
LUAU_FASTFLAGVARIABLE(LuauParseStringIndexer) LUAU_FASTFLAGVARIABLE(LuauParseStringIndexer)
LUAU_FASTFLAGVARIABLE(LuauTypeFunResultInAutocomplete) LUAU_FASTFLAGVARIABLE(LuauTypeFunResultInAutocomplete)
LUAU_FASTFLAGVARIABLE(LuauDeprecatedAttribute)
LUAU_FASTFLAGVARIABLE(LuauStoreReturnTypesAsPackOnAst) LUAU_FASTFLAGVARIABLE(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAGVARIABLE(LuauFixFunctionWithAttributesStartLocation) LUAU_FASTFLAGVARIABLE(LuauFixFunctionWithAttributesStartLocation)
LUAU_FASTFLAGVARIABLE(LuauStoreLocalAnnotationColonPositions)
LUAU_DYNAMIC_FASTFLAGVARIABLE(DebugLuauReportReturnTypeVariadicWithTypeSuffix, false) LUAU_DYNAMIC_FASTFLAGVARIABLE(DebugLuauReportReturnTypeVariadicWithTypeSuffix, false)
// Clip with DebugLuauReportReturnTypeVariadicWithTypeSuffix // Clip with DebugLuauReportReturnTypeVariadicWithTypeSuffix
@ -644,7 +642,8 @@ AstStat* Parser::parseFor()
{ {
AstStatFor* node = allocator.alloc<AstStatFor>(Location(start, end), var, from, to, step, body, hasDo, matchDo.location); AstStatFor* node = allocator.alloc<AstStatFor>(Location(start, end), var, from, to, step, body, hasDo, matchDo.location);
if (options.storeCstData) if (options.storeCstData)
cstNodeMap[node] = allocator.alloc<CstStatFor>(equalsPosition, endCommaPosition, stepCommaPosition); cstNodeMap[node] = allocator.alloc<CstStatFor>(varname.colonPosition, equalsPosition, endCommaPosition, stepCommaPosition);
return node; return node;
} }
else else
@ -664,7 +663,7 @@ AstStat* Parser::parseFor()
{ {
Position initialCommaPosition = lexer.current().location.begin; Position initialCommaPosition = lexer.current().location.begin;
nextLexeme(); nextLexeme();
parseBindingList(names, false, &varsCommaPosition, initialCommaPosition); parseBindingList(names, false, &varsCommaPosition, &initialCommaPosition);
} }
else else
{ {
@ -709,7 +708,12 @@ AstStat* Parser::parseFor()
AstStatForIn* node = AstStatForIn* node =
allocator.alloc<AstStatForIn>(Location(start, end), copy(vars), copy(values), body, hasIn, inLocation, hasDo, matchDo.location); allocator.alloc<AstStatForIn>(Location(start, end), copy(vars), copy(values), body, hasIn, inLocation, hasDo, matchDo.location);
if (options.storeCstData) if (options.storeCstData)
cstNodeMap[node] = allocator.alloc<CstStatForIn>(varsCommaPosition, copy(valuesCommaPositions)); {
if (FFlag::LuauStoreLocalAnnotationColonPositions)
cstNodeMap[node] = allocator.alloc<CstStatForIn>(extractAnnotationColonPositions(names), varsCommaPosition, copy(valuesCommaPositions));
else
cstNodeMap[node] = allocator.alloc<CstStatForIn>(AstArray<Position>{}, varsCommaPosition, copy(valuesCommaPositions));
}
return node; return node;
} }
else else
@ -825,7 +829,7 @@ std::pair<bool, AstAttr::Type> Parser::validateAttribute(const char* attributeNa
} }
} }
if (!found || (!FFlag::LuauDeprecatedAttribute && type == AstAttr::Type::Deprecated)) if (!found)
{ {
if (strlen(attributeName) == 1) if (strlen(attributeName) == 1)
report(lexer.current().location, "Attribute name is missing"); report(lexer.current().location, "Attribute name is missing");
@ -1010,7 +1014,13 @@ AstStat* Parser::parseLocal(const AstArray<AstAttr*>& attributes)
{ {
AstStatLocal* node = allocator.alloc<AstStatLocal>(Location(start, end), copy(vars), copy(values), equalsSignLocation); AstStatLocal* node = allocator.alloc<AstStatLocal>(Location(start, end), copy(vars), copy(values), equalsSignLocation);
if (options.storeCstData) if (options.storeCstData)
cstNodeMap[node] = allocator.alloc<CstStatLocal>(varsCommaPositions, copy(valuesCommaPositions)); {
if (FFlag::LuauStoreLocalAnnotationColonPositions)
cstNodeMap[node] = allocator.alloc<CstStatLocal>(extractAnnotationColonPositions(names), varsCommaPositions, copy(valuesCommaPositions));
else
cstNodeMap[node] = allocator.alloc<CstStatLocal>(AstArray<Position>{}, varsCommaPositions, copy(valuesCommaPositions));
}
return node; return node;
} }
else else
@ -1140,8 +1150,6 @@ AstStat* Parser::parseTypeFunction(const Location& start, bool exported, Positio
AstDeclaredExternTypeProperty Parser::parseDeclaredExternTypeMethod(const AstArray<AstAttr*>& attributes) AstDeclaredExternTypeProperty Parser::parseDeclaredExternTypeMethod(const AstArray<AstAttr*>& attributes)
{ {
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
Location start = lexer.current().location; Location start = lexer.current().location;
nextLexeme(); nextLexeme();
@ -1225,85 +1233,6 @@ AstDeclaredExternTypeProperty Parser::parseDeclaredExternTypeMethod(const AstArr
return AstDeclaredExternTypeProperty{fnName.name, fnName.location, fnType, true, Location(start, end)}; return AstDeclaredExternTypeProperty{fnName.name, fnName.location, fnType, true, Location(start, end)};
} }
AstDeclaredExternTypeProperty Parser::parseDeclaredExternTypeMethod_DEPRECATED()
{
Location start = lexer.current().location;
nextLexeme();
Name fnName = parseName("function name");
// TODO: generic method declarations CLI-39909
AstArray<AstGenericType*> generics;
AstArray<AstGenericTypePack*> genericPacks;
generics.size = 0;
generics.data = nullptr;
genericPacks.size = 0;
genericPacks.data = nullptr;
MatchLexeme matchParen = lexer.current();
expectAndConsume('(', "function parameter list start");
TempVector<Binding> args(scratchBinding);
bool vararg = false;
Location varargLocation;
AstTypePack* varargAnnotation = nullptr;
if (lexer.current().type != ')')
std::tie(vararg, varargLocation, varargAnnotation) = parseBindingList(args, /* allowDot3 */ true);
expectMatchAndConsume(')', matchParen);
AstTypePack* retTypes;
AstTypeList retTypes_DEPRECATED;
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
retTypes = parseOptionalReturnType();
if (!retTypes)
retTypes = allocator.alloc<AstTypePackExplicit>(lexer.current().location, AstTypeList{copy<AstType*>(nullptr, 0), nullptr});
}
else
{
retTypes_DEPRECATED = parseOptionalReturnType_DEPRECATED().value_or(AstTypeList{copy<AstType*>(nullptr, 0), nullptr});
}
Location end = lexer.previousLocation();
TempVector<AstType*> vars(scratchType);
TempVector<std::optional<AstArgumentName>> varNames(scratchOptArgName);
if (args.size() == 0 || args[0].name.name != "self" || args[0].annotation != nullptr)
{
return AstDeclaredExternTypeProperty{
fnName.name, fnName.location, reportTypeError(Location(start, end), {}, "'self' must be present as the unannotated first parameter"), true
};
}
// Skip the first index.
for (size_t i = 1; i < args.size(); ++i)
{
varNames.push_back(AstArgumentName{args[i].name.name, args[i].name.location});
if (args[i].annotation)
vars.push_back(args[i].annotation);
else
vars.push_back(reportTypeError(Location(start, end), {}, "All declaration parameters aside from 'self' must be annotated"));
}
if (vararg && !varargAnnotation)
report(start, "All declaration parameters aside from 'self' must be annotated");
AstType* fnType =
FFlag::LuauStoreReturnTypesAsPackOnAst
? allocator.alloc<AstTypeFunction>(
Location(start, end), generics, genericPacks, AstTypeList{copy(vars), varargAnnotation}, copy(varNames), retTypes
)
: allocator.alloc<AstTypeFunction>(
Location(start, end), generics, genericPacks, AstTypeList{copy(vars), varargAnnotation}, copy(varNames), retTypes_DEPRECATED
);
return AstDeclaredExternTypeProperty{fnName.name, fnName.location, fnType, true, Location(start, end)};
}
AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*>& attributes) AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*>& attributes)
{ {
// `declare` token is already parsed at this point // `declare` token is already parsed at this point
@ -1445,7 +1374,7 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
{ {
AstArray<AstAttr*> attributes{nullptr, 0}; AstArray<AstAttr*> attributes{nullptr, 0};
if (FFlag::LuauDeprecatedAttribute && (lexer.current().type == Lexeme::Attribute)) if (lexer.current().type == Lexeme::Attribute)
{ {
attributes = Parser::parseAttributes(); attributes = Parser::parseAttributes();
@ -1464,10 +1393,7 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
// There are two possibilities: Either it's a property or a function. // There are two possibilities: Either it's a property or a function.
if (lexer.current().type == Lexeme::ReservedFunction) if (lexer.current().type == Lexeme::ReservedFunction)
{ {
if (FFlag::LuauDeprecatedAttribute) props.push_back(parseDeclaredExternTypeMethod(attributes));
props.push_back(parseDeclaredExternTypeMethod(attributes));
else
props.push_back(parseDeclaredExternTypeMethod_DEPRECATED());
} }
else if (lexer.current().type == '[') else if (lexer.current().type == '[')
{ {
@ -1543,10 +1469,7 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
// There are two possibilities: Either it's a property or a function. // There are two possibilities: Either it's a property or a function.
if (lexer.current().type == Lexeme::ReservedFunction) if (lexer.current().type == Lexeme::ReservedFunction)
{ {
if (FFlag::LuauDeprecatedAttribute) props.push_back(parseDeclaredExternTypeMethod(attributes));
props.push_back(parseDeclaredExternTypeMethod(attributes));
else
props.push_back(parseDeclaredExternTypeMethod_DEPRECATED());
} }
else if (lexer.current().type == '[' && else if (lexer.current().type == '[' &&
(lexer.lookahead().type == Lexeme::RawString || lexer.lookahead().type == Lexeme::QuotedString)) (lexer.lookahead().type == Lexeme::RawString || lexer.lookahead().type == Lexeme::QuotedString))
@ -1786,8 +1709,19 @@ std::pair<AstExprFunction*, AstLocal*> Parser::parseFunctionBody(
AstTypePack* varargAnnotation = nullptr; AstTypePack* varargAnnotation = nullptr;
if (lexer.current().type != ')') if (lexer.current().type != ')')
std::tie(vararg, varargLocation, varargAnnotation) = {
parseBindingList(args, /* allowDot3= */ true, cstNode ? &cstNode->argsCommaPositions : nullptr); if (FFlag::LuauStoreLocalAnnotationColonPositions)
{
if (cstNode)
std::tie(vararg, varargLocation, varargAnnotation) =
parseBindingList(args, /* allowDot3= */ true, &cstNode->argsCommaPositions, nullptr, &cstNode->varargAnnotationColonPosition);
else
std::tie(vararg, varargLocation, varargAnnotation) = parseBindingList(args, /* allowDot3= */ true);
}
else
std::tie(vararg, varargLocation, varargAnnotation) =
parseBindingList(args, /* allowDot3= */ true, cstNode ? &cstNode->argsCommaPositions : nullptr);
}
std::optional<Location> argLocation; std::optional<Location> argLocation;
@ -1846,6 +1780,8 @@ std::pair<AstExprFunction*, AstLocal*> Parser::parseFunctionBody(
if (options.storeCstData) if (options.storeCstData)
{ {
cstNode->functionKeywordPosition = matchFunction.location.begin; cstNode->functionKeywordPosition = matchFunction.location.begin;
if (FFlag::LuauStoreLocalAnnotationColonPositions)
cstNode->argsAnnotationColonPositions = extractAnnotationColonPositions(args);
cstNodeMap[node] = cstNode; cstNodeMap[node] = cstNode;
} }
@ -2042,9 +1978,22 @@ Parser::Binding Parser::parseBinding()
if (!name) if (!name)
name = Name(nameError, lexer.current().location); name = Name(nameError, lexer.current().location);
Position colonPosition = lexer.current().location.begin;
AstType* annotation = parseOptionalType(); AstType* annotation = parseOptionalType();
return Binding(*name, annotation); if (FFlag::LuauStoreLocalAnnotationColonPositions && options.storeCstData)
return Binding(*name, annotation, colonPosition);
else
return Binding(*name, annotation);
}
AstArray<Position> Parser::extractAnnotationColonPositions(const TempVector<Binding>& bindings)
{
LUAU_ASSERT(FFlag::LuauStoreLocalAnnotationColonPositions);
TempVector<Position> annotationColonPositions(scratchPosition);
for (size_t i = 0; i < bindings.size(); ++i)
annotationColonPositions.push_back(bindings[i].colonPosition);
return copy(annotationColonPositions);
} }
// bindinglist ::= (binding | `...') [`,' bindinglist] // bindinglist ::= (binding | `...') [`,' bindinglist]
@ -2052,7 +2001,8 @@ std::tuple<bool, Location, AstTypePack*> Parser::parseBindingList(
TempVector<Binding>& result, TempVector<Binding>& result,
bool allowDot3, bool allowDot3,
AstArray<Position>* commaPositions, AstArray<Position>* commaPositions,
std::optional<Position> initialCommaPosition Position* initialCommaPosition,
Position* varargAnnotationColonPosition
) )
{ {
TempVector<Position> localCommaPositions(scratchPosition); TempVector<Position> localCommaPositions(scratchPosition);
@ -2070,6 +2020,9 @@ std::tuple<bool, Location, AstTypePack*> Parser::parseBindingList(
AstTypePack* tailAnnotation = nullptr; AstTypePack* tailAnnotation = nullptr;
if (lexer.current().type == ':') if (lexer.current().type == ':')
{ {
if (FFlag::LuauStoreLocalAnnotationColonPositions && varargAnnotationColonPosition)
*varargAnnotationColonPosition = lexer.current().location.begin;
nextLexeme(); nextLexeme();
tailAnnotation = parseVariadicArgumentTypePack(); tailAnnotation = parseVariadicArgumentTypePack();
} }
@ -2256,8 +2209,6 @@ AstTypePack* Parser::parseReturnType()
nextLexeme(); nextLexeme();
Location innerBegin = lexer.current().location;
matchRecoveryStopOnToken[Lexeme::SkinnyArrow]++; matchRecoveryStopOnToken[Lexeme::SkinnyArrow]++;
TempVector<AstType*> result(scratchType); TempVector<AstType*> result(scratchType);
@ -2283,47 +2234,25 @@ AstTypePack* Parser::parseReturnType()
if (lexer.current().type != Lexeme::SkinnyArrow && resultNames.empty()) if (lexer.current().type != Lexeme::SkinnyArrow && resultNames.empty())
{ {
// If it turns out that it's just '(A)', it's possible that there are unions/intersections to follow, so fold over it. // If it turns out that it's just '(A)', it's possible that there are unions/intersections to follow, so fold over it.
if (FFlag::LuauAstTypeGroup3) if (result.size() == 1)
{ {
if (result.size() == 1) // TODO(CLI-140667): stop parsing type suffix when varargAnnotation != nullptr - this should be a parse error
{ AstType* inner = varargAnnotation == nullptr ? allocator.alloc<AstTypeGroup>(location, result[0]) : result[0];
// TODO(CLI-140667): stop parsing type suffix when varargAnnotation != nullptr - this should be a parse error AstType* returnType = parseTypeSuffix(inner, begin.location);
AstType* inner = varargAnnotation == nullptr ? allocator.alloc<AstTypeGroup>(location, result[0]) : result[0];
AstType* returnType = parseTypeSuffix(inner, begin.location);
if (DFFlag::DebugLuauReportReturnTypeVariadicWithTypeSuffix && varargAnnotation != nullptr && if (DFFlag::DebugLuauReportReturnTypeVariadicWithTypeSuffix && varargAnnotation != nullptr &&
(returnType->is<AstTypeUnion>() || returnType->is<AstTypeIntersection>())) (returnType->is<AstTypeUnion>() || returnType->is<AstTypeIntersection>()))
luau_telemetry_parsed_return_type_variadic_with_type_suffix = true; luau_telemetry_parsed_return_type_variadic_with_type_suffix = true;
// If parseType parses nothing, then returnType->location.end only points at the last non-type-pack // If parseType parses nothing, then returnType->location.end only points at the last non-type-pack
// type to successfully parse. We need the span of the whole annotation. // type to successfully parse. We need the span of the whole annotation.
Position endPos = result.size() == 1 ? location.end : returnType->location.end; Position endPos = result.size() == 1 ? location.end : returnType->location.end;
AstTypePackExplicit* node = allocator.alloc<AstTypePackExplicit>(Location{location.begin, endPos}, AstTypeList{copy(&returnType, 1), varargAnnotation}); AstTypePackExplicit* node =
if (options.storeCstData) allocator.alloc<AstTypePackExplicit>(Location{location.begin, endPos}, AstTypeList{copy(&returnType, 1), varargAnnotation});
cstNodeMap[node] = allocator.alloc<CstTypePackExplicit>(); if (options.storeCstData)
return node; cstNodeMap[node] = allocator.alloc<CstTypePackExplicit>();
} return node;
}
else
{
if (result.size() == 1)
{
AstType* returnType = parseTypeSuffix(result[0], innerBegin);
if (DFFlag::DebugLuauReportReturnTypeVariadicWithTypeSuffix && varargAnnotation != nullptr &&
(returnType->is<AstTypeUnion>() || returnType->is<AstTypeIntersection>()))
luau_telemetry_parsed_return_type_variadic_with_type_suffix = true;
// If parseType parses nothing, then returnType->location.end only points at the last non-type-pack
// type to successfully parse. We need the span of the whole annotation.
Position endPos = result.size() == 1 ? location.end : returnType->location.end;
AstTypePackExplicit* node = allocator.alloc<AstTypePackExplicit>(Location{location.begin, endPos}, AstTypeList{copy(&returnType, 1), varargAnnotation});
if (options.storeCstData)
cstNodeMap[node] = allocator.alloc<CstTypePackExplicit>();
return node;
}
} }
AstTypePackExplicit* node = allocator.alloc<AstTypePackExplicit>(location, AstTypeList{copy(result), varargAnnotation}); AstTypePackExplicit* node = allocator.alloc<AstTypePackExplicit>(location, AstTypeList{copy(result), varargAnnotation});
@ -2366,8 +2295,6 @@ std::pair<Location, AstTypeList> Parser::parseReturnType_DEPRECATED()
nextLexeme(); nextLexeme();
Location innerBegin = lexer.current().location;
matchRecoveryStopOnToken[Lexeme::SkinnyArrow]++; matchRecoveryStopOnToken[Lexeme::SkinnyArrow]++;
TempVector<AstType*> result(scratchType); TempVector<AstType*> result(scratchType);
@ -2387,42 +2314,23 @@ std::pair<Location, AstTypeList> Parser::parseReturnType_DEPRECATED()
if (lexer.current().type != Lexeme::SkinnyArrow && resultNames.empty()) if (lexer.current().type != Lexeme::SkinnyArrow && resultNames.empty())
{ {
// If it turns out that it's just '(A)', it's possible that there are unions/intersections to follow, so fold over it. // If it turns out that it's just '(A)', it's possible that there are unions/intersections to follow, so fold over it.
if (FFlag::LuauAstTypeGroup3) if (result.size() == 1)
{ {
if (result.size() == 1) // TODO(CLI-140667): stop parsing type suffix when varargAnnotation != nullptr - this should be a parse error
{ AstType* inner = varargAnnotation == nullptr ? allocator.alloc<AstTypeGroup>(location, result[0]) : result[0];
// TODO(CLI-140667): stop parsing type suffix when varargAnnotation != nullptr - this should be a parse error AstType* returnType = parseTypeSuffix(inner, begin.location);
AstType* inner = varargAnnotation == nullptr ? allocator.alloc<AstTypeGroup>(location, result[0]) : result[0];
AstType* returnType = parseTypeSuffix(inner, begin.location);
if (DFFlag::DebugLuauReportReturnTypeVariadicWithTypeSuffix && varargAnnotation != nullptr && if (DFFlag::DebugLuauReportReturnTypeVariadicWithTypeSuffix && varargAnnotation != nullptr &&
(returnType->is<AstTypeUnion>() || returnType->is<AstTypeIntersection>())) (returnType->is<AstTypeUnion>() || returnType->is<AstTypeIntersection>()))
luau_telemetry_parsed_return_type_variadic_with_type_suffix = true; luau_telemetry_parsed_return_type_variadic_with_type_suffix = true;
// If parseType parses nothing, then returnType->location.end only points at the last non-type-pack // If parseType parses nothing, then returnType->location.end only points at the last non-type-pack
// type to successfully parse. We need the span of the whole annotation. // type to successfully parse. We need the span of the whole annotation.
Position endPos = result.size() == 1 ? location.end : returnType->location.end; Position endPos = result.size() == 1 ? location.end : returnType->location.end;
return {Location{location.begin, endPos}, AstTypeList{copy(&returnType, 1), varargAnnotation}}; return {Location{location.begin, endPos}, AstTypeList{copy(&returnType, 1), varargAnnotation}};
}
} }
else
{
if (result.size() == 1)
{
AstType* returnType = parseTypeSuffix(result[0], innerBegin);
if (DFFlag::DebugLuauReportReturnTypeVariadicWithTypeSuffix && varargAnnotation != nullptr &&
(returnType->is<AstTypeUnion>() || returnType->is<AstTypeIntersection>()))
luau_telemetry_parsed_return_type_variadic_with_type_suffix = true;
// If parseType parses nothing, then returnType->location.end only points at the last non-type-pack
// type to successfully parse. We need the span of the whole annotation.
Position endPos = result.size() == 1 ? location.end : returnType->location.end;
return {Location{location.begin, endPos}, AstTypeList{copy(&returnType, 1), varargAnnotation}};
}
}
return {location, AstTypeList{copy(result), varargAnnotation}}; return {location, AstTypeList{copy(result), varargAnnotation}};
} }
@ -2888,10 +2796,7 @@ AstTypeOrPack Parser::parseFunctionType(bool allowPack, const AstArray<AstAttr*>
} }
else else
{ {
if (FFlag::LuauAstTypeGroup3) return {allocator.alloc<AstTypeGroup>(Location(parameterStart.location, closeArgsLocation), params[0]), {}};
return {allocator.alloc<AstTypeGroup>(Location(parameterStart.location, closeArgsLocation), params[0]), {}};
else
return {params[0], {}};
} }
} }
@ -3012,8 +2917,6 @@ AstType* Parser::parseTypeSuffix(AstType* type, const Location& begin)
bool isUnion = false; bool isUnion = false;
bool isIntersection = false; bool isIntersection = false;
// Clip with FFlag::LuauParseOptionalAsNode2
bool hasOptional_DEPRECATED = false;
unsigned int optionalCount = 0; unsigned int optionalCount = 0;
Location location = begin; Location location = begin;
@ -3047,19 +2950,10 @@ AstType* Parser::parseTypeSuffix(AstType* type, const Location& begin)
Location loc = lexer.current().location; Location loc = lexer.current().location;
nextLexeme(); nextLexeme();
if (FFlag::LuauParseOptionalAsNode2) parts.push_back(allocator.alloc<AstTypeOptional>(Location(loc)));
{ optionalCount++;
parts.push_back(allocator.alloc<AstTypeOptional>(Location(loc)));
optionalCount++;
}
else
{
if (!hasOptional_DEPRECATED)
parts.push_back(allocator.alloc<AstTypeReference>(loc, std::nullopt, nameNil, std::nullopt, loc));
}
isUnion = true; isUnion = true;
hasOptional_DEPRECATED = true;
} }
else if (c == '&') else if (c == '&')
{ {
@ -3087,16 +2981,8 @@ AstType* Parser::parseTypeSuffix(AstType* type, const Location& begin)
else else
break; break;
if (FFlag::LuauParseOptionalAsNode2) if (parts.size() > unsigned(FInt::LuauTypeLengthLimit) + optionalCount)
{ ParseError::raise(parts.back()->location, "Exceeded allowed type length; simplify your type annotation to make the code compile");
if (parts.size() > unsigned(FInt::LuauTypeLengthLimit) + optionalCount)
ParseError::raise(parts.back()->location, "Exceeded allowed type length; simplify your type annotation to make the code compile");
}
else
{
if (parts.size() > unsigned(FInt::LuauTypeLengthLimit) + hasOptional_DEPRECATED)
ParseError::raise(parts.back()->location, "Exceeded allowed type length; simplify your type annotation to make the code compile");
}
} }
if (parts.size() == 1 && !isUnion && !isIntersection) if (parts.size() == 1 && !isUnion && !isIntersection)
@ -4520,15 +4406,10 @@ AstArray<AstTypeOrPack> Parser::parseTypeParams(Position* openingPosition, TempV
// the next lexeme is one that follows a type // the next lexeme is one that follows a type
// (&, |, ?), then assume that this was actually a // (&, |, ?), then assume that this was actually a
// parenthesized type. // parenthesized type.
if (FFlag::LuauAstTypeGroup3) auto parenthesizedType = explicitTypePack->typeList.types.data[0];
{ parameters.push_back(
auto parenthesizedType = explicitTypePack->typeList.types.data[0]; {parseTypeSuffix(allocator.alloc<AstTypeGroup>(parenthesizedType->location, parenthesizedType), begin), {}}
parameters.push_back( );
{parseTypeSuffix(allocator.alloc<AstTypeGroup>(parenthesizedType->location, parenthesizedType), begin), {}}
);
}
else
parameters.push_back({parseTypeSuffix(explicitTypePack->typeList.types.data[0], begin), {}});
} }
else else
{ {

View file

@ -10,6 +10,7 @@ using AddCompletionCallback = std::function<void(const std::string& completion,
// Note: These are internal functions which are being exposed in a header // Note: These are internal functions which are being exposed in a header
// so they can be included by unit tests. // so they can be included by unit tests.
void* createCliRequireContext(lua_State* L);
void setupState(lua_State* L); void setupState(lua_State* L);
std::string runCode(lua_State* L, const std::string& source); std::string runCode(lua_State* L, const std::string& source);
void getCompletions(lua_State* L, const std::string& editBuffer, const AddCompletionCallback& addCompletionCallback); void getCompletions(lua_State* L, const std::string& editBuffer, const AddCompletionCallback& addCompletionCallback);

View file

@ -7,24 +7,27 @@
#include "lua.h" #include "lua.h"
#include <functional>
#include <string> #include <string>
void requireConfigInit(luarequire_Configuration* config); void requireConfigInit(luarequire_Configuration* config);
struct ReplRequirer struct ReplRequirer
{ {
using CompileOptions = Luau::CompileOptions(*)();
using BoolCheck = bool(*)();
using Coverage = void(*)(lua_State*, int);
ReplRequirer( ReplRequirer(
std::function<Luau::CompileOptions()> copts, CompileOptions copts,
std::function<bool()> coverageActive, BoolCheck coverageActive,
std::function<bool()> codegenEnabled, BoolCheck codegenEnabled,
std::function<void(lua_State*, int)> coverageTrack Coverage coverageTrack
); );
std::function<Luau::CompileOptions()> copts; CompileOptions copts;
std::function<bool()> coverageActive; BoolCheck coverageActive;
std::function<bool()> codegenEnabled; BoolCheck codegenEnabled;
std::function<void(lua_State*, int)> coverageTrack; Coverage coverageTrack;
std::string absPath; std::string absPath;
std::string relPath; std::string relPath;

View file

@ -164,7 +164,7 @@ static int lua_callgrind(lua_State* L)
} }
#endif #endif
static void* createCliRequireContext(lua_State* L) void* createCliRequireContext(lua_State* L)
{ {
void* ctx = lua_newuserdatadtor( void* ctx = lua_newuserdatadtor(
L, L,

View file

@ -3,6 +3,7 @@
#include "Luau/CodeGen.h" #include "Luau/CodeGen.h"
#include "Luau/CodeGenOptions.h" #include "Luau/CodeGenOptions.h"
#include "Luau/FileUtils.h"
#include "Luau/Require.h" #include "Luau/Require.h"
#include "Luau/RequirerUtils.h" #include "Luau/RequirerUtils.h"
@ -101,18 +102,18 @@ static bool is_module_present(lua_State* L, void* ctx)
return isFilePresent(req->absPath, req->suffix); return isFilePresent(req->absPath, req->suffix);
} }
static luarequire_WriteResult get_contents(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out)
{
ReplRequirer* req = static_cast<ReplRequirer*>(ctx);
return write(getFileContents(req->absPath, req->suffix), buffer, buffer_size, size_out);
}
static luarequire_WriteResult get_chunkname(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out) static luarequire_WriteResult get_chunkname(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out)
{ {
ReplRequirer* req = static_cast<ReplRequirer*>(ctx); ReplRequirer* req = static_cast<ReplRequirer*>(ctx);
return write("@" + req->relPath, buffer, buffer_size, size_out); return write("@" + req->relPath, buffer, buffer_size, size_out);
} }
static luarequire_WriteResult get_loadname(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out)
{
ReplRequirer* req = static_cast<ReplRequirer*>(ctx);
return write(req->absPath + req->suffix, buffer, buffer_size, size_out);
}
static luarequire_WriteResult get_cache_key(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out) static luarequire_WriteResult get_cache_key(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out)
{ {
ReplRequirer* req = static_cast<ReplRequirer*>(ctx); ReplRequirer* req = static_cast<ReplRequirer*>(ctx);
@ -131,7 +132,7 @@ static luarequire_WriteResult get_config(lua_State* L, void* ctx, char* buffer,
return write(getFileContents(req->absPath, "/.luaurc"), buffer, buffer_size, size_out); return write(getFileContents(req->absPath, "/.luaurc"), buffer, buffer_size, size_out);
} }
static int load(lua_State* L, void* ctx, const char* path, const char* chunkname, const char* contents) static int load(lua_State* L, void* ctx, const char* path, const char* chunkname, const char* loadname)
{ {
ReplRequirer* req = static_cast<ReplRequirer*>(ctx); ReplRequirer* req = static_cast<ReplRequirer*>(ctx);
@ -144,8 +145,12 @@ static int load(lua_State* L, void* ctx, const char* path, const char* chunkname
// new thread needs to have the globals sandboxed // new thread needs to have the globals sandboxed
luaL_sandboxthread(ML); luaL_sandboxthread(ML);
std::optional<std::string> contents = readFile(loadname);
if (!contents)
luaL_error(L, "could not read file '%s'", loadname);
// now we can compile & run module on the new thread // now we can compile & run module on the new thread
std::string bytecode = Luau::compile(contents, req->copts()); std::string bytecode = Luau::compile(*contents, req->copts());
if (luau_load(ML, chunkname, bytecode.data(), bytecode.size(), 0) == 0) if (luau_load(ML, chunkname, bytecode.data(), bytecode.size(), 0) == 0)
{ {
if (req->codegenEnabled()) if (req->codegenEnabled())
@ -199,23 +204,18 @@ void requireConfigInit(luarequire_Configuration* config)
config->to_parent = to_parent; config->to_parent = to_parent;
config->to_child = to_child; config->to_child = to_child;
config->is_module_present = is_module_present; config->is_module_present = is_module_present;
config->get_contents = get_contents;
config->is_config_present = is_config_present; config->is_config_present = is_config_present;
config->get_chunkname = get_chunkname; config->get_chunkname = get_chunkname;
config->get_loadname = get_loadname;
config->get_cache_key = get_cache_key; config->get_cache_key = get_cache_key;
config->get_config = get_config; config->get_config = get_config;
config->load = load; config->load = load;
} }
ReplRequirer::ReplRequirer( ReplRequirer::ReplRequirer(CompileOptions copts, BoolCheck coverageActive, BoolCheck codegenEnabled, Coverage coverageTrack)
std::function<Luau::CompileOptions()> copts, : copts(copts)
std::function<bool()> coverageActive, , coverageActive(coverageActive)
std::function<bool()> codegenEnabled, , codegenEnabled(codegenEnabled)
std::function<void(lua_State*, int)> coverageTrack , coverageTrack(coverageTrack)
)
: copts(std::move(copts))
, coverageActive(std::move(coverageActive))
, codegenEnabled(std::move(codegenEnabled))
, coverageTrack(std::move(coverageTrack))
{ {
} }

View file

@ -83,15 +83,15 @@ struct luarequire_Configuration
// Returns whether the context is currently pointing at a module. // Returns whether the context is currently pointing at a module.
bool (*is_module_present)(lua_State* L, void* ctx); bool (*is_module_present)(lua_State* L, void* ctx);
// Provides the contents of the current module. This function is only called
// if is_module_present returns true.
luarequire_WriteResult (*get_contents)(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out);
// Provides a chunkname for the current module. This will be accessible // Provides a chunkname for the current module. This will be accessible
// through the debug library. This function is only called if // through the debug library. This function is only called if
// is_module_present returns true. // is_module_present returns true.
luarequire_WriteResult (*get_chunkname)(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out); luarequire_WriteResult (*get_chunkname)(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out);
// Provides a loadname that identifies the current module and is passed to
// load. This function is only called if is_module_present returns true.
luarequire_WriteResult (*get_loadname)(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out);
// Provides a cache key representing the current module. This function is // Provides a cache key representing the current module. This function is
// only called if is_module_present returns true. // only called if is_module_present returns true.
luarequire_WriteResult (*get_cache_key)(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out); luarequire_WriteResult (*get_cache_key)(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out);
@ -109,7 +109,7 @@ struct luarequire_Configuration
// number of results placed on the stack. Returning -1 directs the requiring // number of results placed on the stack. Returning -1 directs the requiring
// thread to yield. In this case, this thread should be resumed with the // thread to yield. In this case, this thread should be resumed with the
// module result pushed onto its stack. // module result pushed onto its stack.
int (*load)(lua_State* L, void* ctx, const char* path, const char* chunkname, const char* contents); int (*load)(lua_State* L, void* ctx, const char* path, const char* chunkname, const char* loadname);
}; };
// Populates function pointers in the given luarequire_Configuration. // Populates function pointers in the given luarequire_Configuration.

View file

@ -62,16 +62,16 @@ bool RuntimeNavigationContext::isModulePresent() const
return config->is_module_present(L, ctx); return config->is_module_present(L, ctx);
} }
std::optional<std::string> RuntimeNavigationContext::getContents() const
{
return getStringFromCWriter(config->get_contents, initalFileBufferSize);
}
std::optional<std::string> RuntimeNavigationContext::getChunkname() const std::optional<std::string> RuntimeNavigationContext::getChunkname() const
{ {
return getStringFromCWriter(config->get_chunkname, initalIdentifierBufferSize); return getStringFromCWriter(config->get_chunkname, initalIdentifierBufferSize);
} }
std::optional<std::string> RuntimeNavigationContext::getLoadname() const
{
return getStringFromCWriter(config->get_loadname, initalIdentifierBufferSize);
}
std::optional<std::string> RuntimeNavigationContext::getCacheKey() const std::optional<std::string> RuntimeNavigationContext::getCacheKey() const
{ {
return getStringFromCWriter(config->get_cache_key, initalIdentifierBufferSize); return getStringFromCWriter(config->get_cache_key, initalIdentifierBufferSize);

View file

@ -31,8 +31,8 @@ public:
// Custom capabilities // Custom capabilities
bool isModulePresent() const; bool isModulePresent() const;
std::optional<std::string> getContents() const;
std::optional<std::string> getChunkname() const; std::optional<std::string> getChunkname() const;
std::optional<std::string> getLoadname() const;
std::optional<std::string> getCacheKey() const; std::optional<std::string> getCacheKey() const;
private: private:

View file

@ -21,10 +21,10 @@ static void validateConfig(lua_State* L, const luarequire_Configuration& config)
luaL_error(L, "require configuration is missing required function pointer: to_child"); luaL_error(L, "require configuration is missing required function pointer: to_child");
if (!config.is_module_present) if (!config.is_module_present)
luaL_error(L, "require configuration is missing required function pointer: is_module_present"); luaL_error(L, "require configuration is missing required function pointer: is_module_present");
if (!config.get_contents)
luaL_error(L, "require configuration is missing required function pointer: get_contents");
if (!config.get_chunkname) if (!config.get_chunkname)
luaL_error(L, "require configuration is missing required function pointer: get_chunkname"); luaL_error(L, "require configuration is missing required function pointer: get_chunkname");
if (!config.get_loadname)
luaL_error(L, "require configuration is missing required function pointer: get_loadname");
if (!config.get_cache_key) if (!config.get_cache_key)
luaL_error(L, "require configuration is missing required function pointer: get_cache_key"); luaL_error(L, "require configuration is missing required function pointer: get_cache_key");
if (!config.is_config_present) if (!config.is_config_present)

View file

@ -29,8 +29,8 @@ struct ResolvedRequire
}; };
Status status; Status status;
std::string contents;
std::string chunkname; std::string chunkname;
std::string loadname;
std::string cacheKey; std::string cacheKey;
}; };
@ -89,17 +89,17 @@ static ResolvedRequire resolveRequire(luarequire_Configuration* lrc, lua_State*
return ResolvedRequire{ResolvedRequire::Status::ErrorReported}; return ResolvedRequire{ResolvedRequire::Status::ErrorReported};
} }
std::optional<std::string> contents = navigationContext.getContents(); std::optional<std::string> loadname = navigationContext.getLoadname();
if (!contents) if (!loadname)
{ {
errorHandler.reportError("could not get contents for module"); errorHandler.reportError("could not get loadname for module");
return ResolvedRequire{ResolvedRequire::Status::ErrorReported}; return ResolvedRequire{ResolvedRequire::Status::ErrorReported};
} }
return ResolvedRequire{ return ResolvedRequire{
ResolvedRequire::Status::ModuleRead, ResolvedRequire::Status::ModuleRead,
std::move(*contents),
std::move(*chunkname), std::move(*chunkname),
std::move(*loadname),
std::move(*cacheKey), std::move(*cacheKey),
}; };
} }
@ -181,7 +181,7 @@ int lua_requireinternal(lua_State* L, const char* requirerChunkname)
lua_pushinteger(L, numArgsBeforeLoad); lua_pushinteger(L, numArgsBeforeLoad);
lua_insert(L, 1); lua_insert(L, 1);
int numResults = lrc->load(L, ctx, path, resolvedRequire.chunkname.c_str(), resolvedRequire.contents.c_str()); int numResults = lrc->load(L, ctx, path, resolvedRequire.chunkname.c_str(), resolvedRequire.loadname.c_str());
if (numResults == -1) if (numResults == -1)
{ {
if (lua_gettop(L) != numArgsBeforeLoad) if (lua_gettop(L) != numArgsBeforeLoad)

View file

@ -11,7 +11,6 @@
#include <string.h> #include <string.h>
LUAU_FASTFLAGVARIABLE(LuauLibWhereErrorAutoreserve)
LUAU_FASTFLAG(LuauYieldableContinuations) LUAU_FASTFLAG(LuauYieldableContinuations)
// convert a stack index to positive // convert a stack index to positive
@ -80,9 +79,7 @@ void luaL_where(lua_State* L, int level)
return; return;
} }
if (FFlag::LuauLibWhereErrorAutoreserve) lua_rawcheckstack(L, 1);
lua_rawcheckstack(L, 1);
lua_pushliteral(L, ""); // else, no information available... lua_pushliteral(L, ""); // else, no information available...
} }

View file

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

View file

@ -31,7 +31,6 @@ extern int optimizationLevel;
void luaC_fullgc(lua_State* L); void luaC_fullgc(lua_State* L);
void luaC_validate(lua_State* L); void luaC_validate(lua_State* L);
LUAU_FASTFLAG(LuauLibWhereErrorAutoreserve)
LUAU_FASTFLAG(DebugLuauAbortingChecks) LUAU_FASTFLAG(DebugLuauAbortingChecks)
LUAU_FASTINT(CodegenHeuristicsInstructionLimit) LUAU_FASTINT(CodegenHeuristicsInstructionLimit)
LUAU_DYNAMIC_FASTFLAG(LuauStringFormatFixC) LUAU_DYNAMIC_FASTFLAG(LuauStringFormatFixC)
@ -1942,8 +1941,6 @@ int slowlyOverflowStack(lua_State* L)
TEST_CASE("ApiStack") TEST_CASE("ApiStack")
{ {
ScopedFastFlag luauLibWhereErrorAutoreserve{FFlag::LuauLibWhereErrorAutoreserve, true};
StateRef globalState(luaL_newstate(), lua_close); StateRef globalState(luaL_newstate(), lua_close);
lua_State* L = globalState.get(); lua_State* L = globalState.get();

View file

@ -33,12 +33,12 @@ LUAU_FASTFLAG(LuauClonedTableAndFunctionTypesMustHaveScopes)
LUAU_FASTFLAG(LuauDisableNewSolverAssertsInMixedMode) LUAU_FASTFLAG(LuauDisableNewSolverAssertsInMixedMode)
LUAU_FASTFLAG(LuauCloneTypeAliasBindings) LUAU_FASTFLAG(LuauCloneTypeAliasBindings)
LUAU_FASTFLAG(LuauDoNotClonePersistentBindings) LUAU_FASTFLAG(LuauDoNotClonePersistentBindings)
LUAU_FASTFLAG(LuauIncrementalAutocompleteDemandBasedCloning)
LUAU_FASTFLAG(LuauUserTypeFunTypecheck) LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
LUAU_FASTFLAG(LuauBetterScopeSelection) LUAU_FASTFLAG(LuauBetterScopeSelection)
LUAU_FASTFLAG(LuauBlockDiffFragmentSelection) LUAU_FASTFLAG(LuauBlockDiffFragmentSelection)
LUAU_FASTFLAG(LuauFragmentAcMemoryLeak) LUAU_FASTFLAG(LuauFragmentAcMemoryLeak)
LUAU_FASTFLAG(LuauGlobalVariableModuleIsolation) LUAU_FASTFLAG(LuauGlobalVariableModuleIsolation)
LUAU_FASTFLAG(LuauFragmentAutocompleteIfRecommendations)
static std::optional<AutocompleteEntryMap> nullCallback(std::string tag, std::optional<const ExternType*> ptr, std::optional<std::string> contents) static std::optional<AutocompleteEntryMap> nullCallback(std::string tag, std::optional<const ExternType*> ptr, std::optional<std::string> contents)
{ {
@ -73,12 +73,12 @@ struct FragmentAutocompleteFixtureImpl : BaseType
ScopedFastFlag luauDisableNewSolverAssertsInMixedMode{FFlag::LuauDisableNewSolverAssertsInMixedMode, true}; ScopedFastFlag luauDisableNewSolverAssertsInMixedMode{FFlag::LuauDisableNewSolverAssertsInMixedMode, true};
ScopedFastFlag luauCloneTypeAliasBindings{FFlag::LuauCloneTypeAliasBindings, true}; ScopedFastFlag luauCloneTypeAliasBindings{FFlag::LuauCloneTypeAliasBindings, true};
ScopedFastFlag luauDoNotClonePersistentBindings{FFlag::LuauDoNotClonePersistentBindings, true}; ScopedFastFlag luauDoNotClonePersistentBindings{FFlag::LuauDoNotClonePersistentBindings, true};
ScopedFastFlag luauIncrementalAutocompleteDemandBasedCloning{FFlag::LuauIncrementalAutocompleteDemandBasedCloning, true};
ScopedFastFlag luauBetterScopeSelection{FFlag::LuauBetterScopeSelection, true}; ScopedFastFlag luauBetterScopeSelection{FFlag::LuauBetterScopeSelection, true};
ScopedFastFlag luauBlockDiffFragmentSelection{FFlag::LuauBlockDiffFragmentSelection, true}; ScopedFastFlag luauBlockDiffFragmentSelection{FFlag::LuauBlockDiffFragmentSelection, true};
ScopedFastFlag luauAutocompleteUsesModuleForTypeCompatibility{FFlag::LuauAutocompleteUsesModuleForTypeCompatibility, true}; ScopedFastFlag luauAutocompleteUsesModuleForTypeCompatibility{FFlag::LuauAutocompleteUsesModuleForTypeCompatibility, true};
ScopedFastFlag luauFragmentAcMemoryLeak{FFlag::LuauFragmentAcMemoryLeak, true}; ScopedFastFlag luauFragmentAcMemoryLeak{FFlag::LuauFragmentAcMemoryLeak, true};
ScopedFastFlag luauGlobalVariableModuleIsolation{FFlag::LuauGlobalVariableModuleIsolation, true}; ScopedFastFlag luauGlobalVariableModuleIsolation{FFlag::LuauGlobalVariableModuleIsolation, true};
ScopedFastFlag luauFragmentAutocompleteIfRecommendations{FFlag::LuauFragmentAutocompleteIfRecommendations, true};
FragmentAutocompleteFixtureImpl() FragmentAutocompleteFixtureImpl()
: BaseType(true) : BaseType(true)
@ -746,11 +746,10 @@ TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "if_partial")
{ {
auto region = getAutocompleteRegion( auto region = getAutocompleteRegion(
R"( R"(
if if)",
)", Position{1, 2}
Position{1, 3}
); );
CHECK_EQ(Location{{1, 0}, {1, 3}}, region.fragmentLocation); CHECK_EQ(Location{{1, 2}, {1, 2}}, region.fragmentLocation);
REQUIRE(region.parentBlock); REQUIRE(region.parentBlock);
CHECK(region.nearestStatement->as<AstStatIf>()); CHECK(region.nearestStatement->as<AstStatIf>());
} }
@ -763,7 +762,7 @@ if true
)", )",
Position{1, 7} Position{1, 7}
); );
CHECK_EQ(Location{{1, 0}, {1, 7}}, region.fragmentLocation); CHECK_EQ(Location{{1, 3}, {1, 7}}, region.fragmentLocation);
REQUIRE(region.parentBlock); REQUIRE(region.parentBlock);
CHECK(region.nearestStatement->as<AstStatIf>()); CHECK(region.nearestStatement->as<AstStatIf>());
} }
@ -776,7 +775,7 @@ if true
)", )",
Position{1, 8} Position{1, 8}
); );
CHECK_EQ(Location{{1, 8}, {1, 8}}, region.fragmentLocation); CHECK_EQ(Location{{1, 3}, {1, 8}}, region.fragmentLocation);
REQUIRE(region.parentBlock); REQUIRE(region.parentBlock);
CHECK(region.nearestStatement->as<AstStatIf>()); CHECK(region.nearestStatement->as<AstStatIf>());
} }
@ -849,7 +848,7 @@ elseif
)", )",
Position{2, 8} Position{2, 8}
); );
CHECK_EQ(Location{{2, 0}, {2, 8}}, region.fragmentLocation); CHECK_EQ(Location{{2, 8}, {2, 8}}, region.fragmentLocation);
REQUIRE(region.parentBlock); REQUIRE(region.parentBlock);
CHECK(region.nearestStatement->as<AstStatIf>()); CHECK(region.nearestStatement->as<AstStatIf>());
} }
@ -1071,7 +1070,7 @@ TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "statement_in_empty_fragment_is_n
); );
REQUIRE(fragment.has_value()); REQUIRE(fragment.has_value());
CHECK_EQ("", fragment->fragmentToParse); CHECK_EQ("", fragment->fragmentToParse);
CHECK_EQ(2, fragment->ancestry.size()); CHECK_EQ(1, fragment->ancestry.size());
REQUIRE(fragment->root); REQUIRE(fragment->root);
CHECK_EQ(0, fragment->root->body.size); CHECK_EQ(0, fragment->root->body.size);
auto statBody = fragment->root->as<AstStatBlock>(); auto statBody = fragment->root->as<AstStatBlock>();
@ -1104,7 +1103,7 @@ local z = x + y
CHECK_EQ(Location{Position{3, 0}, Position{3, 15}}, fragment->root->location); CHECK_EQ(Location{Position{3, 0}, Position{3, 15}}, fragment->root->location);
CHECK_EQ("local z = x + y", fragment->fragmentToParse); CHECK_EQ("local z = x + y", fragment->fragmentToParse);
CHECK_EQ(5, fragment->ancestry.size()); CHECK_EQ(4, fragment->ancestry.size());
REQUIRE(fragment->root); REQUIRE(fragment->root);
CHECK_EQ(1, fragment->root->body.size); CHECK_EQ(1, fragment->root->body.size);
auto stat = fragment->root->body.data[0]->as<AstStatLocal>(); auto stat = fragment->root->body.data[0]->as<AstStatLocal>();
@ -1149,7 +1148,7 @@ local y = 5
REQUIRE(fragment.has_value()); REQUIRE(fragment.has_value());
CHECK_EQ("local z = x + y", fragment->fragmentToParse); CHECK_EQ("local z = x + y", fragment->fragmentToParse);
CHECK_EQ(5, fragment->ancestry.size()); CHECK_EQ(4, fragment->ancestry.size());
REQUIRE(fragment->root); REQUIRE(fragment->root);
CHECK_EQ(Location{Position{2, 0}, Position{2, 15}}, fragment->root->location); CHECK_EQ(Location{Position{2, 0}, Position{2, 15}}, fragment->root->location);
CHECK_EQ(1, fragment->root->body.size); CHECK_EQ(1, fragment->root->body.size);
@ -1244,7 +1243,7 @@ abc("bar")
REQUIRE(stringFragment.has_value()); REQUIRE(stringFragment.has_value());
CHECK_EQ("abc(\"foo\")", stringFragment->fragmentToParse); CHECK_EQ("abc(\"foo\"", stringFragment->fragmentToParse);
CHECK(stringFragment->nearestStatement); CHECK(stringFragment->nearestStatement);
CHECK(stringFragment->nearestStatement->is<AstStatExpr>()); CHECK(stringFragment->nearestStatement->is<AstStatExpr>());
@ -3605,6 +3604,126 @@ end)
)"; )";
autocompleteFragmentInBothSolvers(source, dest, Position{5, 11}, [](auto& result) {}); autocompleteFragmentInBothSolvers(source, dest, Position{5, 11}, [](auto& result) {});
} }
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "if_cond_no_then_recs_then")
{
const std::string source = R"(
)";
const std::string dest = R"(
if x t
)";
autocompleteFragmentInBothSolvers(
source,
dest,
Position{1, 6},
[](auto& result)
{
REQUIRE(result.result);
CHECK(result.result->acResults.entryMap.count("then"));
}
);
}
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "if_then_recs_else")
{
const std::string source = R"(
if x then
end
)";
const std::string dest = R"(
if x then
e
end
)";
autocompleteFragmentInBothSolvers(
source,
dest,
Position{2, 1},
[](auto& result)
{
REQUIRE(result.result);
CHECK(result.result->acResults.entryMap.count("else"));
CHECK(result.result->acResults.entryMap.count("elseif"));
}
);
}
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "if_else_if_table_prop_recs_no_then")
{
const std::string source = R"(
type T = {xa : number, y : number}
local t : T = {xa = 3, y = 3}
if t.x then
elseif
end
)";
const std::string dest = R"(
type T = {xa : number, y : number}
local t : T = {xa = 3, y = 3}
if t.x then
elseif t.xa t
end
)";
autocompleteFragmentInBothSolvers(
source,
dest,
Position{5, 13},
[](auto& result)
{
REQUIRE(result.result);
CHECK(!result.result->acResults.entryMap.empty());
CHECK(!result.result->acResults.entryMap.count("xa"));
CHECK(!result.result->acResults.entryMap.count("y"));
CHECK(result.result->acResults.entryMap.count("then"));
}
);
}
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "if_else_if_table_prop_recs_with_then")
{
const std::string source = R"(
type T = {xa : number, y : number}
local t : T = {xa = 3, y = 3}
if t.x then
elseif then
end
)";
const std::string dest = R"(
type T = {xa : number, y : number}
local t : T = {xa = 3, y = 3}
if t.x then
elseif t. then
end
)";
autocompleteFragmentInBothSolvers(
source,
dest,
Position{5, 9},
[](auto& result)
{
REQUIRE(result.result);
CHECK(!result.result->acResults.entryMap.empty());
CHECK(result.result->acResults.entryMap.count("xa"));
CHECK(result.result->acResults.entryMap.count("y"));
CHECK(!result.result->acResults.entryMap.count("then"));
}
);
}
// NOLINTEND(bugprone-unchecked-optional-access) // NOLINTEND(bugprone-unchecked-optional-access)
TEST_SUITE_END(); TEST_SUITE_END();

View file

@ -16,40 +16,10 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAG(DebugLuauFreezeArena) LUAU_FASTFLAG(DebugLuauFreezeArena)
LUAU_FASTFLAG(DebugLuauMagicTypes) LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
LUAU_FASTFLAG(LuauNewNonStrictVisitTypes2) LUAU_FASTFLAG(LuauNewNonStrictVisitTypes2)
namespace namespace
{ {
struct NaiveModuleResolver : ModuleResolver
{
std::optional<ModuleInfo> resolveModuleInfo(const ModuleName& currentModuleName, const AstExpr& pathExpr) override
{
if (auto name = pathExprToModuleName(currentModuleName, pathExpr))
return {{*name, false}};
return std::nullopt;
}
const ModulePtr getModule(const ModuleName& moduleName) const override
{
return nullptr;
}
bool moduleExists(const ModuleName& moduleName) const override
{
return false;
}
std::string getHumanReadableModuleName(const ModuleName& moduleName) const override
{
return moduleName;
}
};
NaiveModuleResolver naiveModuleResolver;
struct NaiveFileResolver : NullFileResolver struct NaiveFileResolver : NullFileResolver
{ {
std::optional<ModuleInfo> resolveModule(const ModuleInfo* context, AstExpr* expr) override std::optional<ModuleInfo> resolveModule(const ModuleInfo* context, AstExpr* expr) override
@ -920,7 +890,7 @@ TEST_CASE_FIXTURE(FrontendFixture, "it_should_be_safe_to_stringify_errors_when_f
// When this test fails, it is because the TypeIds needed by the error have been deallocated. // When this test fails, it is because the TypeIds needed by the error have been deallocated.
// It is thus basically impossible to predict what will happen when this assert is evaluated. // It is thus basically impossible to predict what will happen when this assert is evaluated.
// It could segfault, or you could see weird type names like the empty string or <VALUELESS BY EXCEPTION> // It could segfault, or you could see weird type names like the empty string or <VALUELESS BY EXCEPTION>
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) if (FFlag::LuauSolverV2)
{ {
REQUIRE_EQ( REQUIRE_EQ(
"Type\n\t" "Type\n\t"
@ -930,14 +900,6 @@ TEST_CASE_FIXTURE(FrontendFixture, "it_should_be_safe_to_stringify_errors_when_f
toString(result.errors[0]) toString(result.errors[0])
); );
} }
else if (FFlag::LuauSolverV2)
REQUIRE_EQ(
R"(Type
'{ count: string }'
could not be converted into
'{ Count: number }')",
toString(result.errors[0])
);
else else
REQUIRE_EQ( REQUIRE_EQ(
"Table type 'a' not compatible with type '{| Count: number |}' because the former is missing field 'Count'", toString(result.errors[0]) "Table type 'a' not compatible with type '{| Count: number |}' because the former is missing field 'Count'", toString(result.errors[0])

View file

@ -1628,7 +1628,7 @@ static void checkDeprecatedWarning(const Luau::LintWarning& warning, const Luau:
TEST_CASE_FIXTURE(Fixture, "DeprecatedAttribute") TEST_CASE_FIXTURE(Fixture, "DeprecatedAttribute")
{ {
ScopedFastFlag sff[] = {{FFlag::LuauDeprecatedAttribute, true}, {FFlag::LuauSolverV2, true}}; ScopedFastFlag _{FFlag::LuauSolverV2, true};
// @deprecated works on local functions // @deprecated works on local functions
{ {
@ -1877,7 +1877,7 @@ end
TEST_CASE_FIXTURE(Fixture, "DeprecatedAttributeFunctionDeclaration") TEST_CASE_FIXTURE(Fixture, "DeprecatedAttributeFunctionDeclaration")
{ {
ScopedFastFlag sff[] = {{FFlag::LuauDeprecatedAttribute, true}, {FFlag::LuauSolverV2, true}}; ScopedFastFlag _{FFlag::LuauSolverV2, true};
// @deprecated works on function type declarations // @deprecated works on function type declarations
@ -1895,7 +1895,7 @@ bar(2)
TEST_CASE_FIXTURE(Fixture, "DeprecatedAttributeTableDeclaration") TEST_CASE_FIXTURE(Fixture, "DeprecatedAttributeTableDeclaration")
{ {
ScopedFastFlag sff[] = {{FFlag::LuauDeprecatedAttribute, true}, {FFlag::LuauSolverV2, true}}; ScopedFastFlag _{FFlag::LuauSolverV2, true};
// @deprecated works on table type declarations // @deprecated works on table type declarations
@ -1915,7 +1915,7 @@ print(Hooty:tooty(2.0))
TEST_CASE_FIXTURE(Fixture, "DeprecatedAttributeMethodDeclaration") TEST_CASE_FIXTURE(Fixture, "DeprecatedAttributeMethodDeclaration")
{ {
ScopedFastFlag sff[] = {{FFlag::LuauDeprecatedAttribute, true}, {FFlag::LuauSolverV2, true}}; ScopedFastFlag _{FFlag::LuauSolverV2, true};
// @deprecated works on table type declarations // @deprecated works on table type declarations

View file

@ -17,8 +17,6 @@ LUAU_FASTINT(LuauRecursionLimit)
LUAU_FASTINT(LuauTypeLengthLimit) LUAU_FASTINT(LuauTypeLengthLimit)
LUAU_FASTINT(LuauParseErrorLimit) LUAU_FASTINT(LuauParseErrorLimit)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauAstTypeGroup3)
LUAU_FASTFLAG(LuauParseOptionalAsNode2)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAG(LuauParseStringIndexer) LUAU_FASTFLAG(LuauParseStringIndexer)
LUAU_FASTFLAG(LuauFixFunctionWithAttributesStartLocation) LUAU_FASTFLAG(LuauFixFunctionWithAttributesStartLocation)
@ -457,10 +455,7 @@ TEST_CASE_FIXTURE(Fixture, "return_type_is_an_intersection_type_if_led_with_one_
returnAnnotation = annotation->returnTypes_DEPRECATED.types.data[0]->as<AstTypeIntersection>(); returnAnnotation = annotation->returnTypes_DEPRECATED.types.data[0]->as<AstTypeIntersection>();
} }
REQUIRE(returnAnnotation != nullptr); REQUIRE(returnAnnotation != nullptr);
if (FFlag::LuauAstTypeGroup3) CHECK(returnAnnotation->types.data[0]->as<AstTypeGroup>());
CHECK(returnAnnotation->types.data[0]->as<AstTypeGroup>());
else
CHECK(returnAnnotation->types.data[0]->as<AstTypeReference>());
CHECK(returnAnnotation->types.data[1]->as<AstTypeFunction>()); CHECK(returnAnnotation->types.data[1]->as<AstTypeFunction>());
} }
@ -2780,8 +2775,6 @@ TEST_CASE_FIXTURE(Fixture, "leading_union_intersection_with_single_type_preserve
TEST_CASE_FIXTURE(Fixture, "parse_simple_ast_type_group") TEST_CASE_FIXTURE(Fixture, "parse_simple_ast_type_group")
{ {
ScopedFastFlag _{FFlag::LuauAstTypeGroup3, true};
AstStatBlock* stat = parse(R"( AstStatBlock* stat = parse(R"(
type Foo = (string) type Foo = (string)
)"); )");
@ -2798,8 +2791,6 @@ TEST_CASE_FIXTURE(Fixture, "parse_simple_ast_type_group")
TEST_CASE_FIXTURE(Fixture, "parse_nested_ast_type_group") TEST_CASE_FIXTURE(Fixture, "parse_nested_ast_type_group")
{ {
ScopedFastFlag _{FFlag::LuauAstTypeGroup3, true};
AstStatBlock* stat = parse(R"( AstStatBlock* stat = parse(R"(
type Foo = ((string)) type Foo = ((string))
)"); )");
@ -2819,8 +2810,6 @@ TEST_CASE_FIXTURE(Fixture, "parse_nested_ast_type_group")
TEST_CASE_FIXTURE(Fixture, "parse_return_type_ast_type_group") TEST_CASE_FIXTURE(Fixture, "parse_return_type_ast_type_group")
{ {
ScopedFastFlag _{FFlag::LuauAstTypeGroup3, true};
AstStatBlock* stat = parse(R"( AstStatBlock* stat = parse(R"(
type Foo = () -> (string) type Foo = () -> (string)
)"); )");
@ -4206,18 +4195,10 @@ TEST_CASE_FIXTURE(Fixture, "grouped_function_type")
auto unionTy = paramTy.type->as<AstTypeUnion>(); auto unionTy = paramTy.type->as<AstTypeUnion>();
LUAU_ASSERT(unionTy); LUAU_ASSERT(unionTy);
CHECK_EQ(unionTy->types.size, 2); CHECK_EQ(unionTy->types.size, 2);
if (FFlag::LuauAstTypeGroup3) auto groupTy = unionTy->types.data[0]->as<AstTypeGroup>(); // (() -> ())
{ REQUIRE(groupTy);
auto groupTy = unionTy->types.data[0]->as<AstTypeGroup>(); // (() -> ()) CHECK(groupTy->type->is<AstTypeFunction>()); // () -> ()
REQUIRE(groupTy); CHECK(unionTy->types.data[1]->is<AstTypeOptional>()); // ?
CHECK(groupTy->type->is<AstTypeFunction>()); // () -> ()
}
else
CHECK(unionTy->types.data[0]->is<AstTypeFunction>()); // () -> ()
if (FFlag::LuauParseOptionalAsNode2)
CHECK(unionTy->types.data[1]->is<AstTypeOptional>()); // ?
else
CHECK(unionTy->types.data[1]->is<AstTypeReference>()); // nil
} }
TEST_CASE_FIXTURE(Fixture, "complex_union_in_generic_ty") TEST_CASE_FIXTURE(Fixture, "complex_union_in_generic_ty")

View file

@ -7,6 +7,7 @@
#include "lualib.h" #include "lualib.h"
#include "Luau/Repl.h" #include "Luau/Repl.h"
#include "Luau/ReplRequirer.h"
#include "Luau/Require.h" #include "Luau/Require.h"
#include "Luau/FileUtils.h" #include "Luau/FileUtils.h"
@ -546,6 +547,16 @@ TEST_CASE_FIXTURE(ReplWithPathFixture, "RegisterRuntimeModule")
assertOutputContainsAll({"true"}); assertOutputContainsAll({"true"});
} }
TEST_CASE_FIXTURE(ReplWithPathFixture, "ProxyRequire")
{
luarequire_pushproxyrequire(L, requireConfigInit, createCliRequireContext(L));
lua_setglobal(L, "proxyrequire");
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/proxy_requirer";
runProtectedRequire(path);
assertOutputContainsAll({"true", "result from dependency", "required into proxy_requirer"});
}
TEST_CASE_FIXTURE(ReplWithPathFixture, "LoadStringRelative") TEST_CASE_FIXTURE(ReplWithPathFixture, "LoadStringRelative")
{ {
runCode(L, "return pcall(function() return loadstring(\"require('a/relative/path')\")() end)"); runCode(L, "return pcall(function() return loadstring(\"require('a/relative/path')\")() end)");

View file

@ -16,6 +16,7 @@
#include <initializer_list> #include <initializer_list>
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauSubtypeGenericsAndNegations)
using namespace Luau; using namespace Luau;
@ -1615,4 +1616,35 @@ TEST_CASE_FIXTURE(SubtypeFixture, "multiple_reasonings")
); );
} }
TEST_CASE_FIXTURE(SubtypeFixture, "substitute_a_generic_for_a_negation")
{
ScopedFastFlag sff{FFlag::LuauSubtypeGenericsAndNegations, true};
// <A, B>(x: A, y: B) -> (A & ~(false?)) | B
// (~(false?), ~(false?)) -> (~(false?) & ~(false?)) | ~(false?)
TypeId aTy = arena.addType(GenericType{"A"});
getMutable<GenericType>(aTy)->scope = moduleScope.get();
TypeId bTy = arena.addType(GenericType{"B"});
getMutable<GenericType>(bTy)->scope = moduleScope.get();
TypeId genericFunctionTy = arena.addType(FunctionType{
{aTy, bTy},
{},
arena.addTypePack({aTy, bTy}),
arena.addTypePack({join(meet(aTy, builtinTypes->truthyType), bTy)})
});
const TypeId truthyTy = builtinTypes->truthyType;
TypeId actualFunctionTy = fn(
{truthyTy, truthyTy},
{join(meet(truthyTy, builtinTypes->truthyType), truthyTy)}
);
SubtypingResult result = isSubtype(genericFunctionTy, actualFunctionTy);
CHECK(result.isSubtype);
}
TEST_SUITE_END(); TEST_SUITE_END();

View file

@ -14,7 +14,6 @@ using namespace Luau;
LUAU_FASTFLAG(LuauRecursiveTypeParameterRestriction) LUAU_FASTFLAG(LuauRecursiveTypeParameterRestriction)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauAttributeSyntax) LUAU_FASTFLAG(LuauAttributeSyntax)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
TEST_SUITE_BEGIN("ToString"); TEST_SUITE_BEGIN("ToString");
@ -873,15 +872,12 @@ TEST_CASE_FIXTURE(Fixture, "tostring_error_mismatch")
)"); )");
std::string expected; std::string expected;
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) if (FFlag::LuauSolverV2)
expected = expected =
"Type pack '{ a: number, b: string, c: { d: string } }' could not be converted into '{ a: number, b: string, c: { d: number } }'; \n" "Type pack '{ a: number, b: string, c: { d: string } }' could not be converted into '{ a: number, b: string, c: { d: number } }'; \n"
"this is because in the 1st entry in the type pack, accessing `c.d` results in `string` in the former type and `number` in the latter " "this is because in the 1st entry in the type pack, accessing `c.d` results in `string` in the former type and `number` in the latter "
"type, and `string` is not exactly `number`"; "type, and `string` is not exactly `number`";
else if (FFlag::LuauSolverV2) else
expected =
R"(Type pack '{ a: number, b: string, c: { d: string } }' could not be converted into '{ a: number, b: string, c: { d: number } }'; at [0][read "c"][read "d"], string is not exactly number)";
else if (FFlag::LuauImproveTypePathsInErrors)
expected = R"(Type expected = R"(Type
'{ a: number, b: string, c: { d: string } }' '{ a: number, b: string, c: { d: string } }'
could not be converted into could not be converted into
@ -895,20 +891,6 @@ could not be converted into
caused by: caused by:
Property 'd' is not compatible. Property 'd' is not compatible.
Type 'string' could not be converted into 'number' in an invariant context)"; Type 'string' could not be converted into 'number' in an invariant context)";
else
expected = R"(Type
'{ a: number, b: string, c: { d: string } }'
could not be converted into
'{| a: number, b: string, c: {| d: number |} |}'
caused by:
Property 'c' is not compatible.
Type
'{ d: string }'
could not be converted into
'{| d: number |}'
caused by:
Property 'd' is not compatible.
Type 'string' could not be converted into 'number' in an invariant context)";
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);

View file

@ -13,9 +13,8 @@
using namespace Luau; using namespace Luau;
LUAU_FASTFLAG(LuauStoreCSTData2) LUAU_FASTFLAG(LuauStoreCSTData2)
LUAU_FASTFLAG(LuauAstTypeGroup3);
LUAU_FASTFLAG(LuauParseOptionalAsNode2)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAG(LuauStoreLocalAnnotationColonPositions)
TEST_SUITE_BEGIN("TranspilerTests"); TEST_SUITE_BEGIN("TranspilerTests");
@ -343,7 +342,8 @@ TEST_CASE("function_with_types_spaces_around_tokens")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag sffs[] = {
{FFlag::LuauStoreCSTData2, true}, {FFlag::LuauStoreCSTData2, true},
{FFlag::LuauStoreReturnTypesAsPackOnAst, true} {FFlag::LuauStoreReturnTypesAsPackOnAst, true},
{FFlag::LuauStoreLocalAnnotationColonPositions, true},
}; };
std::string code = R"( function p<X, Y, Z...>(o: string, m: number, ...: any): string end )"; std::string code = R"( function p<X, Y, Z...>(o: string, m: number, ...: any): string end )";
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);
@ -369,9 +369,8 @@ TEST_CASE("function_with_types_spaces_around_tokens")
code = R"( function p<X, Y, Z...> (o: string, m: number, ...: any): string end )"; code = R"( function p<X, Y, Z...> (o: string, m: number, ...: any): string end )";
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);
// TODO(CLI-139347): re-enable test once colon positions are supported code = R"( function p<X, Y, Z...>(o : string, m: number, ...: any): string end )";
// code = R"( function p<X, Y, Z...>(o : string, m: number, ...: any): string end )"; CHECK_EQ(code, transpile(code, {}, true).code);
// CHECK_EQ(code, transpile(code, {}, true).code);
code = R"( function p<X, Y, Z...>(o: string, m: number, ...: any): string end )"; code = R"( function p<X, Y, Z...>(o: string, m: number, ...: any): string end )";
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);
@ -385,9 +384,8 @@ TEST_CASE("function_with_types_spaces_around_tokens")
code = R"( function p<X, Y, Z...>(o: string, m: number, ...: any): string end )"; code = R"( function p<X, Y, Z...>(o: string, m: number, ...: any): string end )";
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);
// TODO(CLI-139347): re-enable test once colon positions are supported code = R"( function p<X, Y, Z...>(o: string, m: number, ... : any): string end )";
// code = R"( function p<X, Y, Z...>(o: string, m: number, ... : any): string end )"; CHECK_EQ(code, transpile(code, {}, true).code);
// CHECK_EQ(code, transpile(code, {}, true).code);
code = R"( function p<X, Y, Z...>(o: string, m: number, ...: any): string end )"; code = R"( function p<X, Y, Z...>(o: string, m: number, ...: any): string end )";
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);
@ -1249,6 +1247,59 @@ TEST_CASE_FIXTURE(Fixture, "transpile_type_reference_spaces_around_tokens")
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);
} }
TEST_CASE_FIXTURE(Fixture, "transpile_type_annotation_spaces_around_tokens")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauStoreCSTData2, true},
{FFlag::LuauStoreLocalAnnotationColonPositions, true},
};
std::string code = R"( local _: Type )";
CHECK_EQ(code, transpile(code, {}, true).code);
code = R"( local _ : Type )";
CHECK_EQ(code, transpile(code, {}, true).code);
code = R"( local _: Type )";
CHECK_EQ(code, transpile(code, {}, true).code);
code = R"( local x: Type, y = 1 )";
CHECK_EQ(code, transpile(code, {}, true).code);
code = R"( local x : Type, y = 1 )";
CHECK_EQ(code, transpile(code, {}, true).code);
}
TEST_CASE_FIXTURE(Fixture, "transpile_for_loop_annotation_spaces_around_tokens")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauStoreCSTData2, true},
{FFlag::LuauStoreLocalAnnotationColonPositions, true},
};
std::string code = R"( for i: number = 1, 10 do end )";
CHECK_EQ(code, transpile(code, {}, true).code);
code = R"( for i : number = 1, 10 do end )";
CHECK_EQ(code, transpile(code, {}, true).code);
code = R"( for i: number = 1, 10 do end )";
CHECK_EQ(code, transpile(code, {}, true).code);
code = R"( for x: number, y: number in ... do end )";
CHECK_EQ(code, transpile(code, {}, true).code);
code = R"( for x : number, y: number in ... do end )";
CHECK_EQ(code, transpile(code, {}, true).code);
code = R"( for x: number, y: number in ... do end )";
CHECK_EQ(code, transpile(code, {}, true).code);
code = R"( for x: number, y : number in ... do end )";
CHECK_EQ(code, transpile(code, {}, true).code);
code = R"( for x: number, y: number in ... do end )";
CHECK_EQ(code, transpile(code, {}, true).code);
}
TEST_CASE_FIXTURE(Fixture, "transpile_type_packs") TEST_CASE_FIXTURE(Fixture, "transpile_type_packs")
{ {
std::string code = R"( std::string code = R"(
@ -1323,10 +1374,8 @@ TEST_CASE_FIXTURE(Fixture, "transpile_union_type_nested_3")
if (FFlag::LuauStoreCSTData2) if (FFlag::LuauStoreCSTData2)
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);
else if (FFlag::LuauAstTypeGroup3)
CHECK_EQ("local a: (string & number)?", transpile(code, {}, true).code);
else else
CHECK_EQ("local a: ( string & number)?", transpile(code, {}, true).code); CHECK_EQ("local a: (string & number)?", transpile(code, {}, true).code);
} }
TEST_CASE_FIXTURE(Fixture, "transpile_intersection_type_nested") TEST_CASE_FIXTURE(Fixture, "transpile_intersection_type_nested")
@ -1358,7 +1407,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_leading_union_pipe")
{ {
ScopedFastFlag flags[] = { ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true}, {FFlag::LuauStoreCSTData2, true},
{FFlag::LuauParseOptionalAsNode2, true},
}; };
std::string code = "local a: | string | number"; std::string code = "local a: | string | number";
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);
@ -1371,7 +1419,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_union_spaces_around_tokens")
{ {
ScopedFastFlag flags[] = { ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true}, {FFlag::LuauStoreCSTData2, true},
{FFlag::LuauParseOptionalAsNode2, true},
}; };
std::string code = "local a: string | number"; std::string code = "local a: string | number";
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);
@ -1408,8 +1455,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_mixed_union_intersection")
{ {
ScopedFastFlag flags[] = { ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true}, {FFlag::LuauStoreCSTData2, true},
{FFlag::LuauAstTypeGroup3, true},
{FFlag::LuauParseOptionalAsNode2, true},
}; };
std::string code = "local a: string | (Foo & Bar)"; std::string code = "local a: string | (Foo & Bar)";
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);
@ -1437,7 +1482,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_preserve_union_optional_style")
{ {
ScopedFastFlag flags[] = { ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true}, {FFlag::LuauStoreCSTData2, true},
{FFlag::LuauParseOptionalAsNode2, true},
}; };
std::string code = "local a: string | nil"; std::string code = "local a: string | nil";
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);
@ -2029,7 +2073,6 @@ TEST_CASE("transpile_types_preserve_parentheses_style")
{ {
ScopedFastFlag flags[] = { ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true}, {FFlag::LuauStoreCSTData2, true},
{FFlag::LuauAstTypeGroup3, true},
}; };
std::string code = R"( type Foo = number )"; std::string code = R"( type Foo = number )";
@ -2176,7 +2219,6 @@ TEST_CASE("transpile_type_function_return_types")
{ {
ScopedFastFlag fflags[] = { ScopedFastFlag fflags[] = {
{FFlag::LuauStoreCSTData2, true}, {FFlag::LuauStoreCSTData2, true},
{FFlag::LuauAstTypeGroup3, true},
{FFlag::LuauStoreReturnTypesAsPackOnAst, true}, {FFlag::LuauStoreReturnTypesAsPackOnAst, true},
}; };
std::string code = R"( type Foo = () -> () )"; std::string code = R"( type Foo = () -> () )";
@ -2224,8 +2266,6 @@ TEST_CASE("transpile_type_function_return_types")
TEST_CASE("fuzzer_nil_optional") TEST_CASE("fuzzer_nil_optional")
{ {
ScopedFastFlag _{FFlag::LuauParseOptionalAsNode2, true};
const std::string code = R"( local x: nil? )"; const std::string code = R"( local x: nil? )";
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);
} }

View file

@ -10,7 +10,6 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(DebugLuauEqSatSimplification) LUAU_FASTFLAG(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauTypeFunReadWriteParents) LUAU_FASTFLAG(LuauTypeFunReadWriteParents)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
LUAU_FASTFLAG(LuauUserTypeFunTypecheck) LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
LUAU_FASTFLAG(LuauNewTypeFunReductionChecks2) LUAU_FASTFLAG(LuauNewTypeFunReductionChecks2)
LUAU_FASTFLAG(LuauNoTypeFunctionsNamedTypeOf) LUAU_FASTFLAG(LuauNoTypeFunctionsNamedTypeOf)
@ -1337,35 +1336,21 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tag_field")
LUAU_REQUIRE_ERROR_COUNT(3, result); LUAU_REQUIRE_ERROR_COUNT(3, result);
if (FFlag::LuauImproveTypePathsInErrors) CHECK(
{ toString(result.errors[0]) ==
"Type pack '\"number\"' could not be converted into 'never'; \n"
CHECK( R"(this is because the 1st entry in the type pack is `"number"` in the former type and `never` in the latter type, and `"number"` is not a subtype of `never`)"
toString(result.errors[0]) == );
"Type pack '\"number\"' could not be converted into 'never'; \n" CHECK(
R"(this is because the 1st entry in the type pack is `"number"` in the former type and `never` in the latter type, and `"number"` is not a subtype of `never`)" toString(result.errors[1]) ==
); "Type pack '\"string\"' could not be converted into 'never'; \n"
CHECK( R"(this is because the 1st entry in the type pack is `"string"` in the former type and `never` in the latter type, and `"string"` is not a subtype of `never`)"
toString(result.errors[1]) == );
"Type pack '\"string\"' could not be converted into 'never'; \n" CHECK(
R"(this is because the 1st entry in the type pack is `"string"` in the former type and `never` in the latter type, and `"string"` is not a subtype of `never`)" toString(result.errors[2]) ==
); "Type pack '\"table\"' could not be converted into 'never'; \n"
CHECK( R"(this is because the 1st entry in the type pack is `"table"` in the former type and `never` in the latter type, and `"table"` is not a subtype of `never`)"
toString(result.errors[2]) == );
"Type pack '\"table\"' could not be converted into 'never'; \n"
R"(this is because the 1st entry in the type pack is `"table"` in the former type and `never` in the latter type, and `"table"` is not a subtype of `never`)"
);
}
else
{
CHECK(
toString(result.errors[0]) == R"(Type pack '"number"' could not be converted into 'never'; at [0], "number" is not a subtype of never)"
);
CHECK(
toString(result.errors[1]) == R"(Type pack '"string"' could not be converted into 'never'; at [0], "string" is not a subtype of never)"
);
CHECK(toString(result.errors[2]) == R"(Type pack '"table"' could not be converted into 'never'; at [0], "table" is not a subtype of never)");
}
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "metatable_serialization") TEST_CASE_FIXTURE(BuiltinsFixture, "metatable_serialization")

View file

@ -11,10 +11,9 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauFixInfiniteRecursionInNormalization) LUAU_FASTFLAG(LuauFixInfiniteRecursionInNormalization)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
LUAU_FASTFLAG(LuauBidirectionalInferenceCollectIndexerTypes)
LUAU_FASTFLAG(LuauRetainDefinitionAliasLocations) LUAU_FASTFLAG(LuauRetainDefinitionAliasLocations)
LUAU_FASTFLAG(LuauNewNonStrictVisitTypes2) LUAU_FASTFLAG(LuauNewNonStrictVisitTypes2)
LUAU_FASTFLAG(LuauGuardAgainstMalformedTypeAliasExpansion)
TEST_SUITE_BEGIN("TypeAliases"); TEST_SUITE_BEGIN("TypeAliases");
@ -220,11 +219,10 @@ TEST_CASE_FIXTURE(Fixture, "generic_aliases")
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = (FFlag::LuauImproveTypePathsInErrors) const std::string expected =
? "Type '{ v: string }' could not be converted into 'T<number>'; \n" "Type '{ v: string }' could not be converted into 'T<number>'; \n"
"this is because accessing `v` results in `string` in the former type and `number` in the latter type, and " "this is because accessing `v` results in `string` in the former type and `number` in the latter type, and "
"`string` is not exactly `number`" "`string` is not exactly `number`";
: R"(Type '{ v: string }' could not be converted into 'T<number>'; at [read "v"], string is not exactly number)";
CHECK(result.errors[0].location == Location{{4, 31}, {4, 44}}); CHECK(result.errors[0].location == Location{{4, 31}, {4, 44}});
CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(expected, toString(result.errors[0]));
} }
@ -244,11 +242,9 @@ TEST_CASE_FIXTURE(Fixture, "dependent_generic_aliases")
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = const std::string expected =
(FFlag::LuauImproveTypePathsInErrors) "Type '{ t: { v: string } }' could not be converted into 'U<number>'; \n"
? "Type '{ t: { v: string } }' could not be converted into 'U<number>'; \n" "this is because accessing `t.v` results in `string` in the former type and `number` in the latter type, and `string` is not exactly "
"this is because accessing `t.v` results in `string` in the former type and `number` in the latter type, and `string` is not exactly " "`number`";
"`number`"
: R"(Type '{ t: { v: string } }' could not be converted into 'U<number>'; at [read "t"][read "v"], string is not exactly number)";
CHECK(result.errors[0].location == Location{{4, 31}, {4, 52}}); CHECK(result.errors[0].location == Location{{4, 31}, {4, 52}});
CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(expected, toString(result.errors[0]));
@ -256,8 +252,6 @@ TEST_CASE_FIXTURE(Fixture, "dependent_generic_aliases")
TEST_CASE_FIXTURE(Fixture, "mutually_recursive_generic_aliases") TEST_CASE_FIXTURE(Fixture, "mutually_recursive_generic_aliases")
{ {
ScopedFastFlag _{FFlag::LuauBidirectionalInferenceCollectIndexerTypes, true};
CheckResult result = check(R"( CheckResult result = check(R"(
--!strict --!strict
type T<a> = { f: a, g: U<a> } type T<a> = { f: a, g: U<a> }
@ -1268,4 +1262,17 @@ TEST_CASE_FIXTURE(Fixture, "exported_type_function_location_is_accessible_on_mod
CHECK_EQ(tfun->second.definitionLocation, Location{{1, 8}, {2, 11}}); CHECK_EQ(tfun->second.definitionLocation, Location{{1, 8}, {2, 11}});
} }
TEST_CASE_FIXTURE(Fixture, "fuzzer_cursed_type_aliases")
{
ScopedFastFlag _{FFlag::LuauGuardAgainstMalformedTypeAliasExpansion, true};
// This used to crash under the new solver: we would like this to continue
// to not crash.
LUAU_REQUIRE_ERRORS(check(R"(
export type t1<t0...> = t4<t0...>
export type t4<t2, t0...> = t4<t0...>
)"));
}
TEST_SUITE_END(); TEST_SUITE_END();

View file

@ -11,7 +11,6 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauTableCloneClonesType3) LUAU_FASTFLAG(LuauTableCloneClonesType3)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization) LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAG(LuauArityMismatchOnUndersaturatedUnknownArguments) LUAU_FASTFLAG(LuauArityMismatchOnUndersaturatedUnknownArguments)
@ -147,32 +146,20 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "sort_with_bad_predicate")
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = (FFlag::LuauImproveTypePathsInErrors) ? "Type\n\t" const std::string expected =
"'(number, number) -> boolean'" "Type\n\t"
"\ncould not be converted into\n\t" "'(number, number) -> boolean'"
"'((string, string) -> boolean)?'" "\ncould not be converted into\n\t"
"\ncaused by:\n" "'((string, string) -> boolean)?'"
" None of the union options are compatible. For example:\n" "\ncaused by:\n"
"Type\n\t" " None of the union options are compatible. For example:\n"
"'(number, number) -> boolean'" "Type\n\t"
"\ncould not be converted into\n\t" "'(number, number) -> boolean'"
"'(string, string) -> boolean'" "\ncould not be converted into\n\t"
"\ncaused by:\n" "'(string, string) -> boolean'"
" Argument #1 type is not compatible.\n" "\ncaused by:\n"
"Type 'string' could not be converted into 'number'" " Argument #1 type is not compatible.\n"
: R"(Type "Type 'string' could not be converted into 'number'";
'(number, number) -> boolean'
could not be converted into
'((string, string) -> boolean)?'
caused by:
None of the union options are compatible. For example:
Type
'(number, number) -> boolean'
could not be converted into
'(string, string) -> boolean'
caused by:
Argument #1 type is not compatible.
Type 'string' could not be converted into 'number')";
CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(expected, toString(result.errors[0]));
} }
@ -1008,17 +995,12 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tonumber_returns_optional_number_type")
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) if (FFlag::LuauSolverV2)
CHECK_EQ( CHECK_EQ(
"Type 'number?' could not be converted into 'number'; \n" "Type 'number?' could not be converted into 'number'; \n"
"this is because the 2nd component of the union is `nil`, which is not a subtype of `number`", "this is because the 2nd component of the union is `nil`, which is not a subtype of `number`",
toString(result.errors[0]) toString(result.errors[0])
); );
else if (FFlag::LuauSolverV2)
CHECK_EQ(
"Type 'number?' could not be converted into 'number'; type number?[1] (nil) is not a subtype of number (number)",
toString(result.errors[0])
);
else else
CHECK_EQ("Type 'number?' could not be converted into 'number'", toString(result.errors[0])); CHECK_EQ("Type 'number?' could not be converted into 'number'", toString(result.errors[0]));
} }

View file

@ -15,7 +15,6 @@ using namespace Luau;
using std::nullopt; using std::nullopt;
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
TEST_SUITE_BEGIN("TypeInferExternTypes"); TEST_SUITE_BEGIN("TypeInferExternTypes");
@ -547,14 +546,12 @@ local b: B = a
LUAU_REQUIRE_ERRORS(result); LUAU_REQUIRE_ERRORS(result);
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) if (FFlag::LuauSolverV2)
CHECK( CHECK(
"Type 'A' could not be converted into 'B'; \n" "Type 'A' could not be converted into 'B'; \n"
"this is because accessing `x` results in `ChildClass` in the former type and `BaseClass` in the latter type, and `ChildClass` is not " "this is because accessing `x` results in `ChildClass` in the former type and `BaseClass` in the latter type, and `ChildClass` is not "
"exactly `BaseClass`" == toString(result.errors.at(0)) "exactly `BaseClass`" == toString(result.errors.at(0))
); );
else if (FFlag::LuauSolverV2)
CHECK(toString(result.errors.at(0)) == "Type 'A' could not be converted into 'B'; at [read \"x\"], ChildClass is not exactly BaseClass");
else else
{ {
const std::string expected = R"(Type 'A' could not be converted into 'B' const std::string expected = R"(Type 'A' could not be converted into 'B'

View file

@ -23,9 +23,7 @@ LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTINT(LuauTarjanChildLimit) LUAU_FASTINT(LuauTarjanChildLimit)
LUAU_FASTFLAG(DebugLuauEqSatSimplification) LUAU_FASTFLAG(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauUngeneralizedTypesForRecursiveFunctions) LUAU_FASTFLAG(LuauUngeneralizedTypesForRecursiveFunctions)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization) LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAG(LuauReduceUnionFollowUnionType)
LUAU_FASTFLAG(LuauArityMismatchOnUndersaturatedUnknownArguments) LUAU_FASTFLAG(LuauArityMismatchOnUndersaturatedUnknownArguments)
LUAU_FASTFLAG(LuauHasPropProperBlock) LUAU_FASTFLAG(LuauHasPropProperBlock)
LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect) LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect)
@ -1313,7 +1311,7 @@ f(function(a, b, c, ...) return a + b end)
LUAU_REQUIRE_ERRORS(result); LUAU_REQUIRE_ERRORS(result);
std::string expected; std::string expected;
if (FFlag::LuauInstantiateInSubtyping && FFlag::LuauImproveTypePathsInErrors) if (FFlag::LuauInstantiateInSubtyping)
{ {
expected = "Type\n\t" expected = "Type\n\t"
"'<a>(number, number, a) -> number'" "'<a>(number, number, a) -> number'"
@ -1322,16 +1320,7 @@ f(function(a, b, c, ...) return a + b end)
"\ncaused by:\n" "\ncaused by:\n"
" Argument count mismatch. Function expects 3 arguments, but only 2 are specified"; " Argument count mismatch. Function expects 3 arguments, but only 2 are specified";
} }
else if (FFlag::LuauInstantiateInSubtyping) else
{
expected = R"(Type
'<a>(number, number, a) -> number'
could not be converted into
'(number, number) -> number'
caused by:
Argument count mismatch. Function expects 3 arguments, but only 2 are specified)";
}
else if (FFlag::LuauImproveTypePathsInErrors)
{ {
expected = "Type\n\t" expected = "Type\n\t"
"'(number, number, *error-type*) -> number'" "'(number, number, *error-type*) -> number'"
@ -1340,15 +1329,6 @@ caused by:
"\ncaused by:\n" "\ncaused by:\n"
" Argument count mismatch. Function expects 3 arguments, but only 2 are specified"; " Argument count mismatch. Function expects 3 arguments, but only 2 are specified";
} }
else
{
expected = R"(Type
'(number, number, *error-type*) -> number'
could not be converted into
'(number, number) -> number'
caused by:
Argument count mismatch. Function expects 3 arguments, but only 2 are specified)";
}
CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(expected, toString(result.errors[0]));
@ -1544,19 +1524,13 @@ local b: B = a
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = (FFlag::LuauImproveTypePathsInErrors) const std::string expected =
? "Type\n\t" "Type\n\t"
"'(number, number) -> string'" "'(number, number) -> string'"
"\ncould not be converted into\n\t" "\ncould not be converted into\n\t"
"'(number) -> string'" "'(number) -> string'"
"\ncaused by:\n" "\ncaused by:\n"
" Argument count mismatch. Function expects 2 arguments, but only 1 is specified" " Argument count mismatch. Function expects 2 arguments, but only 1 is specified";
: R"(Type
'(number, number) -> string'
could not be converted into
'(number) -> string'
caused by:
Argument count mismatch. Function expects 2 arguments, but only 1 is specified)";
CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(expected, toString(result.errors[0]));
} }
@ -1574,20 +1548,14 @@ local b: B = a
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = (FFlag::LuauImproveTypePathsInErrors) ? "Type\n\t" const std::string expected =
"'(number, number) -> string'" "Type\n\t"
"\ncould not be converted into\n\t" "'(number, number) -> string'"
"'(number, string) -> string'" "\ncould not be converted into\n\t"
"\ncaused by:\n" "'(number, string) -> string'"
" Argument #2 type is not compatible.\n" "\ncaused by:\n"
"Type 'string' could not be converted into 'number'" " Argument #2 type is not compatible.\n"
: R"(Type "Type 'string' could not be converted into 'number'";
'(number, number) -> string'
could not be converted into
'(number, string) -> string'
caused by:
Argument #2 type is not compatible.
Type 'string' could not be converted into 'number')";
CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(expected, toString(result.errors[0]));
} }
@ -1605,18 +1573,13 @@ local b: B = a
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = (FFlag::LuauImproveTypePathsInErrors) ? "Type\n\t" const std::string expected =
"'(number, number) -> number'" "Type\n\t"
"\ncould not be converted into\n\t" "'(number, number) -> number'"
"'(number, number) -> (number, boolean)'" "\ncould not be converted into\n\t"
"\ncaused by:\n" "'(number, number) -> (number, boolean)'"
" Function only returns 1 value, but 2 are required here" "\ncaused by:\n"
: R"(Type " Function only returns 1 value, but 2 are required here";
'(number, number) -> number'
could not be converted into
'(number, number) -> (number, boolean)'
caused by:
Function only returns 1 value, but 2 are required here)";
CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(expected, toString(result.errors[0]));
} }
@ -1634,20 +1597,14 @@ local b: B = a
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = (FFlag::LuauImproveTypePathsInErrors) ? "Type\n\t" const std::string expected =
"'(number, number) -> string'" "Type\n\t"
"\ncould not be converted into\n\t" "'(number, number) -> string'"
"'(number, number) -> number'" "\ncould not be converted into\n\t"
"\ncaused by:\n" "'(number, number) -> number'"
" Return type is not compatible.\n" "\ncaused by:\n"
"Type 'string' could not be converted into 'number'" " Return type is not compatible.\n"
: R"(Type "Type 'string' could not be converted into 'number'";
'(number, number) -> string'
could not be converted into
'(number, number) -> number'
caused by:
Return type is not compatible.
Type 'string' could not be converted into 'number')";
CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(expected, toString(result.errors[0]));
} }
@ -1665,20 +1622,14 @@ local b: B = a
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = (FFlag::LuauImproveTypePathsInErrors) ? "Type\n\t" const std::string expected =
"'(number, number) -> (number, string)'" "Type\n\t"
"\ncould not be converted into\n\t" "'(number, number) -> (number, string)'"
"'(number, number) -> (number, boolean)'" "\ncould not be converted into\n\t"
"\ncaused by:\n" "'(number, number) -> (number, boolean)'"
" Return #2 type is not compatible.\n" "\ncaused by:\n"
"Type 'string' could not be converted into 'boolean'" " Return #2 type is not compatible.\n"
: R"(Type "Type 'string' could not be converted into 'boolean'";
'(number, number) -> (number, string)'
could not be converted into
'(number, number) -> (number, boolean)'
caused by:
Return #2 type is not compatible.
Type 'string' could not be converted into 'boolean')";
CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(expected, toString(result.errors[0]));
} }
@ -1828,7 +1779,7 @@ end
R"(Type function instance add<a, number> depends on generic function parameters but does not appear in the function signature; this construct cannot be type-checked at this time)" R"(Type function instance add<a, number> depends on generic function parameters but does not appear in the function signature; this construct cannot be type-checked at this time)"
); );
} }
else if (FFlag::LuauImproveTypePathsInErrors) else
{ {
LUAU_REQUIRE_ERROR_COUNT(2, result); LUAU_REQUIRE_ERROR_COUNT(2, result);
CHECK_EQ(toString(result.errors[0]), R"(Type CHECK_EQ(toString(result.errors[0]), R"(Type
@ -1843,24 +1794,6 @@ could not be converted into
'(number) -> number' '(number) -> number'
caused by: caused by:
Argument #1 type is not compatible. Argument #1 type is not compatible.
Type 'number' could not be converted into 'string')");
CHECK_EQ(toString(result.errors[1]), R"(Type 'string' could not be converted into 'number')");
}
else
{
LUAU_REQUIRE_ERROR_COUNT(2, result);
CHECK_EQ(toString(result.errors[0]), R"(Type
'(string) -> string'
could not be converted into
'((number) -> number)?'
caused by:
None of the union options are compatible. For example:
Type
'(string) -> string'
could not be converted into
'(number) -> number'
caused by:
Argument #1 type is not compatible.
Type 'number' could not be converted into 'string')"); Type 'number' could not be converted into 'string')");
CHECK_EQ(toString(result.errors[1]), R"(Type 'string' could not be converted into 'number')"); CHECK_EQ(toString(result.errors[1]), R"(Type 'string' could not be converted into 'number')");
} }
@ -1890,30 +1823,15 @@ function t:b() return 2 end -- not OK
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauImproveTypePathsInErrors) CHECK_EQ(
{ "Type\n\t"
CHECK_EQ( "'(*error-type*) -> number'"
"Type\n\t" "\ncould not be converted into\n\t"
"'(*error-type*) -> number'" "'() -> number'\n"
"\ncould not be converted into\n\t" "caused by:\n"
"'() -> number'\n" " Argument count mismatch. Function expects 1 argument, but none are specified",
"caused by:\n" toString(result.errors[0])
" Argument count mismatch. Function expects 1 argument, but none are specified", );
toString(result.errors[0])
);
}
else
{
CHECK_EQ(
R"(Type
'(*error-type*) -> number'
could not be converted into
'() -> number'
caused by:
Argument count mismatch. Function expects 1 argument, but none are specified)",
toString(result.errors[0])
);
}
} }
TEST_CASE_FIXTURE(Fixture, "too_few_arguments_variadic") TEST_CASE_FIXTURE(Fixture, "too_few_arguments_variadic")
@ -2187,15 +2105,10 @@ z = y -- Not OK, so the line is colorable
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = const std::string expected =
(FFlag::LuauImproveTypePathsInErrors) "Type\n\t"
? "Type\n\t" R"('(("blue" | "red") -> ("blue" | "red") -> ("blue" | "red") -> boolean) & (("blue" | "red") -> ("blue") -> ("blue") -> false) & (("blue" | "red") -> ("red") -> ("red") -> false) & (("blue") -> ("blue") -> ("blue" | "red") -> false) & (("red") -> ("red") -> ("blue" | "red") -> false)')"
R"('(("blue" | "red") -> ("blue" | "red") -> ("blue" | "red") -> boolean) & (("blue" | "red") -> ("blue") -> ("blue") -> false) & (("blue" | "red") -> ("red") -> ("red") -> false) & (("blue") -> ("blue") -> ("blue" | "red") -> false) & (("red") -> ("red") -> ("blue" | "red") -> false)')" "\ncould not be converted into\n\t"
"\ncould not be converted into\n\t" R"('("blue" | "red") -> ("blue" | "red") -> ("blue" | "red") -> false'; none of the intersection parts are compatible)";
R"('("blue" | "red") -> ("blue" | "red") -> ("blue" | "red") -> false'; none of the intersection parts are compatible)"
: R"(Type
'(("blue" | "red") -> ("blue" | "red") -> ("blue" | "red") -> boolean) & (("blue" | "red") -> ("blue") -> ("blue") -> false) & (("blue" | "red") -> ("red") -> ("red") -> false) & (("blue") -> ("blue") -> ("blue" | "red") -> false) & (("red") -> ("red") -> ("blue" | "red") -> false)'
could not be converted into
'("blue" | "red") -> ("blue" | "red") -> ("blue" | "red") -> false'; none of the intersection parts are compatible)";
CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(expected, toString(result.errors[0]));
} }
@ -2525,14 +2438,12 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "num_is_solved_before_num_or_str")
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) if (FFlag::LuauSolverV2)
CHECK( CHECK(
"Type pack 'string' could not be converted into 'number'; \n" "Type pack 'string' could not be converted into 'number'; \n"
"this is because the 1st entry in the type pack is `string` in the former type and `number` in the latter type, and `string` is not a " "this is because the 1st entry in the type pack is `string` in the former type and `number` in the latter type, and `string` is not a "
"subtype of `number`" == toString(result.errors.at(0)) "subtype of `number`" == toString(result.errors.at(0))
); );
else if (FFlag::LuauSolverV2)
CHECK(toString(result.errors.at(0)) == "Type pack 'string' could not be converted into 'number'; at [0], string is not a subtype of number");
else else
CHECK_EQ("Type 'string' could not be converted into 'number'", toString(result.errors[0])); CHECK_EQ("Type 'string' could not be converted into 'number'", toString(result.errors[0]));
@ -2556,14 +2467,12 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "num_is_solved_after_num_or_str")
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) if (FFlag::LuauSolverV2)
CHECK( CHECK(
"Type pack 'string' could not be converted into 'number'; \n" "Type pack 'string' could not be converted into 'number'; \n"
"this is because the 1st entry in the type pack is `string` in the former type and `number` in the latter type, and `string` is not a " "this is because the 1st entry in the type pack is `string` in the former type and `number` in the latter type, and `string` is not a "
"subtype of `number`" == toString(result.errors.at(0)) "subtype of `number`" == toString(result.errors.at(0))
); );
else if (FFlag::LuauSolverV2)
CHECK(toString(result.errors.at(0)) == "Type pack 'string' could not be converted into 'number'; at [0], string is not a subtype of number");
else else
CHECK_EQ("Type 'string' could not be converted into 'number'", toString(result.errors[0])); CHECK_EQ("Type 'string' could not be converted into 'number'", toString(result.errors[0]));
CHECK_EQ("() -> number", toString(requireType("num_or_str"))); CHECK_EQ("() -> number", toString(requireType("num_or_str")));

View file

@ -12,7 +12,6 @@
LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions) LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions)
LUAU_FASTFLAG(LuauIntersectNotNil) LUAU_FASTFLAG(LuauIntersectNotNil)
@ -867,12 +866,10 @@ y.a.c = y
CHECK_EQ(toString(mismatch->givenType), "{ a: { c: T<string>?, d: number }, b: number }"); CHECK_EQ(toString(mismatch->givenType), "{ a: { c: T<string>?, d: number }, b: number }");
CHECK_EQ(toString(mismatch->wantedType), "T<string>"); CHECK_EQ(toString(mismatch->wantedType), "T<string>");
std::string reason = std::string reason =
(FFlag::LuauImproveTypePathsInErrors) "\nthis is because \n\t"
? "\nthis is because \n\t" " * accessing `a.d` results in `number` in the former type and `string` in the latter type, and `number` is not exactly "
" * accessing `a.d` results in `number` in the former type and `string` in the latter type, and `number` is not exactly " "`string`\n\t"
"`string`\n\t" " * accessing `b` results in `number` in the former type and `string` in the latter type, and `number` is not exactly `string`";
" * accessing `b` results in `number` in the former type and `string` in the latter type, and `number` is not exactly `string`"
: "at [read \"a\"][read \"d\"], number is not exactly string\n\tat [read \"b\"], number is not exactly string";
CHECK_EQ(mismatch->reason, reason); CHECK_EQ(mismatch->reason, reason);
} }
else else

View file

@ -10,7 +10,6 @@
using namespace Luau; using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
LUAU_FASTFLAG(LuauNarrowIntersectionNevers) LUAU_FASTFLAG(LuauNarrowIntersectionNevers)
TEST_SUITE_BEGIN("IntersectionTypes"); TEST_SUITE_BEGIN("IntersectionTypes");
@ -360,19 +359,13 @@ TEST_CASE_FIXTURE(Fixture, "table_intersection_write_sealed_indirect")
{ {
LUAU_REQUIRE_ERROR_COUNT(4, result); LUAU_REQUIRE_ERROR_COUNT(4, result);
const std::string expected = (FFlag::LuauImproveTypePathsInErrors) const std::string expected =
? "Type\n\t" "Type\n\t"
"'(string, number) -> string'" "'(string, number) -> string'"
"\ncould not be converted into\n\t" "\ncould not be converted into\n\t"
"'(string) -> string'\n" "'(string) -> string'\n"
"caused by:\n" "caused by:\n"
" Argument count mismatch. Function expects 2 arguments, but only 1 is specified" " Argument count mismatch. Function expects 2 arguments, but only 1 is specified";
: R"(Type
'(string, number) -> string'
could not be converted into
'(string) -> string'
caused by:
Argument count mismatch. Function expects 2 arguments, but only 1 is specified)";
CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(expected, toString(result.errors[0]));
CHECK_EQ(toString(result.errors[1]), "Cannot add property 'z' to table 'X & Y'"); CHECK_EQ(toString(result.errors[1]), "Cannot add property 'z' to table 'X & Y'");
@ -398,19 +391,13 @@ TEST_CASE_FIXTURE(Fixture, "table_write_sealed_indirect")
)"); )");
LUAU_REQUIRE_ERROR_COUNT(4, result); LUAU_REQUIRE_ERROR_COUNT(4, result);
const std::string expected = (FFlag::LuauImproveTypePathsInErrors) const std::string expected =
? "Type\n\t" "Type\n\t"
"'(string, number) -> string'" "'(string, number) -> string'"
"\ncould not be converted into\n\t" "\ncould not be converted into\n\t"
"'(string) -> string'\n" "'(string) -> string'\n"
"caused by:\n" "caused by:\n"
" Argument count mismatch. Function expects 2 arguments, but only 1 is specified" " Argument count mismatch. Function expects 2 arguments, but only 1 is specified";
: R"(Type
'(string, number) -> string'
could not be converted into
'(string) -> string'
caused by:
Argument count mismatch. Function expects 2 arguments, but only 1 is specified)";
CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(expected, toString(result.errors[0]));
CHECK_EQ(toString(result.errors[1]), "Cannot add property 'z' to table 'XY'"); CHECK_EQ(toString(result.errors[1]), "Cannot add property 'z' to table 'XY'");
@ -440,31 +427,30 @@ local a: XYZ = 3
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = R"(Type 'number' could not be converted into 'X & Y & Z'
if (FFlag::LuauSolverV2)
{
const std::string expected =
"Type "
"'number'"
" could not be converted into "
"'X & Y & Z'; \n"
"this is because \n\t"
" * the 1st component of the intersection is `X`, and `number` is not a subtype of `X`\n\t"
" * the 2nd component of the intersection is `Y`, and `number` is not a subtype of `Y`\n\t"
" * the 3rd component of the intersection is `Z`, and `number` is not a subtype of `Z`";
CHECK_EQ(expected, toString(result.errors[0]));
}
else
{
const std::string expected = R"(Type 'number' could not be converted into 'X & Y & Z'
caused by: caused by:
Not all intersection parts are compatible. Not all intersection parts are compatible.
Type 'number' could not be converted into 'X')"; Type 'number' could not be converted into 'X')";
const std::string dcrExprected =
R"(Type 'number' could not be converted into 'X & Y & Z'; type number (number) is not a subtype of X & Y & Z[0] (X)
type number (number) is not a subtype of X & Y & Z[1] (Y)
type number (number) is not a subtype of X & Y & Z[2] (Z))";
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
{
const std::string expected = "Type "
"'number'"
" could not be converted into "
"'X & Y & Z'; \n"
"this is because \n\t"
" * the 1st component of the intersection is `X`, and `number` is not a subtype of `X`\n\t"
" * the 2nd component of the intersection is `Y`, and `number` is not a subtype of `Y`\n\t"
" * the 3rd component of the intersection is `Z`, and `number` is not a subtype of `Z`";
CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(expected, toString(result.errors[0]));
} }
else if (FFlag::LuauSolverV2)
CHECK_EQ(dcrExprected, toString(result.errors[0]));
else
CHECK_EQ(expected, toString(result.errors[0]));
} }
TEST_CASE_FIXTURE(Fixture, "error_detailed_intersection_all") TEST_CASE_FIXTURE(Fixture, "error_detailed_intersection_all")
@ -482,30 +468,22 @@ end
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) if (FFlag::LuauSolverV2)
{ {
const std::string expected = "Type pack " const std::string expected =
"'X & Y & Z'" "Type pack "
" could not be converted into " "'X & Y & Z'"
"'number'; \n" " could not be converted into "
"this is because \n\t" "'number'; \n"
" * in the 1st entry in the type pack has the 1st component of the intersection as `X` and the 1st entry in the " "this is because \n\t"
"type pack is `number`, and `X` is not a subtype of `number`\n\t" " * in the 1st entry in the type pack has the 1st component of the intersection as `X` and the 1st entry in the "
" * in the 1st entry in the type pack has the 2nd component of the intersection as `Y` and the 1st entry in the " "type pack is `number`, and `X` is not a subtype of `number`\n\t"
"type pack is `number`, and `Y` is not a subtype of `number`\n\t" " * in the 1st entry in the type pack has the 2nd component of the intersection as `Y` and the 1st entry in the "
" * in the 1st entry in the type pack has the 3rd component of the intersection as `Z` and the 1st entry in the " "type pack is `number`, and `Y` is not a subtype of `number`\n\t"
"type pack is `number`, and `Z` is not a subtype of `number`"; " * in the 1st entry in the type pack has the 3rd component of the intersection as `Z` and the 1st entry in the "
"type pack is `number`, and `Z` is not a subtype of `number`";
CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(expected, toString(result.errors[0]));
} }
else if (FFlag::LuauSolverV2)
{
CHECK_EQ(
R"(Type pack 'X & Y & Z' could not be converted into 'number'; type X & Y & Z[0][0] (X) is not a subtype of number[0] (number)
type X & Y & Z[0][1] (Y) is not a subtype of number[0] (number)
type X & Y & Z[0][2] (Z) is not a subtype of number[0] (number))",
toString(result.errors[0])
);
}
else else
CHECK_EQ( CHECK_EQ(
toString(result.errors[0]), R"(Type 'X & Y & Z' could not be converted into 'number'; none of the intersection parts are compatible)" toString(result.errors[0]), R"(Type 'X & Y & Z' could not be converted into 'number'; none of the intersection parts are compatible)"
@ -551,25 +529,18 @@ TEST_CASE_FIXTURE(Fixture, "intersect_bool_and_false")
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) if (FFlag::LuauSolverV2)
{ {
const std::string expected = "Type " const std::string expected =
"'boolean & false'" "Type "
" could not be converted into " "'boolean & false'"
"'true'; \n" " could not be converted into "
"this is because \n\t" "'true'; \n"
" * the 1st component of the intersection is `boolean`, which is not a subtype of `true`\n\t" "this is because \n\t"
" * the 2nd component of the intersection is `false`, which is not a subtype of `true`"; " * the 1st component of the intersection is `boolean`, which is not a subtype of `true`\n\t"
" * the 2nd component of the intersection is `false`, which is not a subtype of `true`";
CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(expected, toString(result.errors[0]));
} }
else if (FFlag::LuauSolverV2)
{
CHECK_EQ(
R"(Type 'boolean & false' could not be converted into 'true'; type boolean & false[0] (boolean) is not a subtype of true (true)
type boolean & false[1] (false) is not a subtype of true (true))",
toString(result.errors[0])
);
}
else else
CHECK_EQ( CHECK_EQ(
toString(result.errors[0]), "Type 'boolean & false' could not be converted into 'true'; none of the intersection parts are compatible" toString(result.errors[0]), "Type 'boolean & false' could not be converted into 'true'; none of the intersection parts are compatible"
@ -588,25 +559,19 @@ TEST_CASE_FIXTURE(Fixture, "intersect_false_and_bool_and_false")
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
// TODO: odd stringification of `false & (boolean & false)`.) // TODO: odd stringification of `false & (boolean & false)`.)
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) if (FFlag::LuauSolverV2)
{ {
const std::string expected = "Type " const std::string expected =
"'boolean & false & false'" "Type "
" could not be converted into " "'boolean & false & false'"
"'true'; \n" " could not be converted into "
"this is because \n\t" "'true'; \n"
" * the 1st component of the intersection is `false`, which is not a subtype of `true`\n\t" "this is because \n\t"
" * the 2nd component of the intersection is `boolean`, which is not a subtype of `true`\n\t" " * the 1st component of the intersection is `false`, which is not a subtype of `true`\n\t"
" * the 3rd component of the intersection is `false`, which is not a subtype of `true`"; " * the 2nd component of the intersection is `boolean`, which is not a subtype of `true`\n\t"
" * the 3rd component of the intersection is `false`, which is not a subtype of `true`";
CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(expected, toString(result.errors[0]));
} }
else if (FFlag::LuauSolverV2)
CHECK_EQ(
R"(Type 'boolean & false & false' could not be converted into 'true'; type boolean & false & false[0] (false) is not a subtype of true (true)
type boolean & false & false[1] (boolean) is not a subtype of true (true)
type boolean & false & false[2] (false) is not a subtype of true (true))",
toString(result.errors[0])
);
else else
CHECK_EQ( CHECK_EQ(
toString(result.errors[0]), toString(result.errors[0]),
@ -623,7 +588,7 @@ TEST_CASE_FIXTURE(Fixture, "intersect_saturate_overloaded_functions")
end end
)"); )");
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) if (FFlag::LuauSolverV2)
{ {
const std::string expected1 = const std::string expected1 =
"Type\n\t" "Type\n\t"
@ -654,25 +619,7 @@ TEST_CASE_FIXTURE(Fixture, "intersect_saturate_overloaded_functions")
CHECK_EQ(expected1, toString(result.errors[0])); CHECK_EQ(expected1, toString(result.errors[0]));
CHECK_EQ(expected2, toString(result.errors[1])); CHECK_EQ(expected2, toString(result.errors[1]));
} }
else if (FFlag::LuauSolverV2) else
{
LUAU_REQUIRE_ERROR_COUNT(2, result);
const std::string expected1 = R"(Type
'((number?) -> number?) & ((string?) -> string?)'
could not be converted into
'(nil) -> nil'; type ((number?) -> number?) & ((string?) -> string?)[0].returns()[0][0] (number) is not a subtype of (nil) -> nil.returns()[0] (nil)
type ((number?) -> number?) & ((string?) -> string?)[1].returns()[0][0] (string) is not a subtype of (nil) -> nil.returns()[0] (nil))";
const std::string expected2 = R"(Type
'((number?) -> number?) & ((string?) -> string?)'
could not be converted into
'(number) -> number'; type ((number?) -> number?) & ((string?) -> string?)[0].returns()[0][1] (nil) is not a subtype of (number) -> number.returns()[0] (number)
type ((number?) -> number?) & ((string?) -> string?)[1].arguments()[0] (string?) is not a supertype of (number) -> number.arguments()[0] (number)
type ((number?) -> number?) & ((string?) -> string?)[1].returns()[0][0] (string) is not a subtype of (number) -> number.returns()[0] (number)
type ((number?) -> number?) & ((string?) -> string?)[1].returns()[0][1] (nil) is not a subtype of (number) -> number.returns()[0] (number))";
CHECK_EQ(expected1, toString(result.errors[0]));
CHECK_EQ(expected2, toString(result.errors[1]));
}
else if (FFlag::LuauImproveTypePathsInErrors)
{ {
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = R"(Type const std::string expected = R"(Type
@ -681,15 +628,6 @@ could not be converted into
'(number) -> number'; none of the intersection parts are compatible)"; '(number) -> number'; none of the intersection parts are compatible)";
CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(expected, toString(result.errors[0]));
} }
else
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = R"(Type
'((number?) -> number?) & ((string?) -> string?)'
could not be converted into
'(number) -> number'; none of the intersection parts are compatible)";
CHECK_EQ(expected, toString(result.errors[0]));
}
} }
TEST_CASE_FIXTURE(Fixture, "union_saturate_overloaded_functions") TEST_CASE_FIXTURE(Fixture, "union_saturate_overloaded_functions")
@ -706,22 +644,13 @@ TEST_CASE_FIXTURE(Fixture, "union_saturate_overloaded_functions")
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauImproveTypePathsInErrors) const std::string expected =
{ "Type\n\t"
const std::string expected = "Type\n\t" "'((number) -> number) & ((string) -> string)'"
"'((number) -> number) & ((string) -> string)'" "\ncould not be converted into\n\t"
"\ncould not be converted into\n\t" "'(boolean | number) -> boolean | number'; none of the intersection parts are compatible";
"'(boolean | number) -> boolean | number'; none of the intersection parts are compatible"; CHECK_EQ(expected, toString(result.errors[0]));
CHECK_EQ(expected, toString(result.errors[0]));
}
else
{
const std::string expected = R"(Type
'((number) -> number) & ((string) -> string)'
could not be converted into
'(boolean | number) -> boolean | number'; none of the intersection parts are compatible)";
CHECK_EQ(expected, toString(result.errors[0]));
}
} }
TEST_CASE_FIXTURE(Fixture, "intersection_of_tables") TEST_CASE_FIXTURE(Fixture, "intersection_of_tables")
@ -735,20 +664,21 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables")
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) if (FFlag::LuauSolverV2)
{ {
const std::string expected = "Type " const std::string expected =
"'{ p: number?, q: number?, r: number? } & { p: number?, q: string? }'" "Type "
" could not be converted into " "'{ p: number?, q: number?, r: number? } & { p: number?, q: string? }'"
"'{ p: nil }'; \n" " could not be converted into "
"this is because \n\t" "'{ p: nil }'; \n"
" * in the 1st component of the intersection, accessing `p` has the 1st component of the union as `number` and " "this is because \n\t"
"accessing `p` results in `nil`, and `number` is not exactly `nil`\n\t" " * in the 1st component of the intersection, accessing `p` has the 1st component of the union as `number` and "
" * in the 2nd component of the intersection, accessing `p` has the 1st component of the union as `number` and " "accessing `p` results in `nil`, and `number` is not exactly `nil`\n\t"
"accessing `p` results in `nil`, and `number` is not exactly `nil`"; " * in the 2nd component of the intersection, accessing `p` has the 1st component of the union as `number` and "
"accessing `p` results in `nil`, and `number` is not exactly `nil`";
CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(expected, toString(result.errors[0]));
} }
else if (FFlag::LuauImproveTypePathsInErrors) else
{ {
const std::string expected = const std::string expected =
R"(Type R"(Type
@ -757,19 +687,6 @@ could not be converted into
'{| p: nil |}'; none of the intersection parts are compatible)"; '{| p: nil |}'; none of the intersection parts are compatible)";
CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(expected, toString(result.errors[0]));
} }
else
{
const std::string expected =
(FFlag::LuauSolverV2)
? R"(Type '{ p: number?, q: number?, r: number? } & { p: number?, q: string? }' could not be converted into '{ p: nil }'; type { p: number?, q: number?, r: number? } & { p: number?, q: string? }[0][read "p"][0] (number) is not exactly { p: nil }[read "p"] (nil)
type { p: number?, q: number?, r: number? } & { p: number?, q: string? }[1][read "p"][0] (number) is not exactly { p: nil }[read "p"] (nil))"
:
R"(Type
'{| p: number?, q: number?, r: number? |} & {| p: number?, q: string? |}'
could not be converted into
'{| p: nil |}'; none of the intersection parts are compatible)";
CHECK_EQ(expected, toString(result.errors[0]));
}
} }
TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_top_properties") TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_top_properties")
@ -781,59 +698,35 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_top_properties")
end end
)"); )");
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) if (FFlag::LuauSolverV2)
{ {
const std::string expected = "Type\n\t" const std::string expected =
"'{ p: number?, q: any } & { p: unknown, q: string? }'" "Type\n\t"
"\ncould not be converted into\n\t" "'{ p: number?, q: any } & { p: unknown, q: string? }'"
"'{ p: string?, q: number? }'; \n" "\ncould not be converted into\n\t"
"this is because \n\t" "'{ p: string?, q: number? }'; \n"
" * in the 1st component of the intersection, accessing `p` has the 1st component of the union as `number` and " "this is because \n\t"
"accessing `p` results in `string?`, and `number` is not exactly `string?`\n\t" " * in the 1st component of the intersection, accessing `p` has the 1st component of the union as `number` and "
" * in the 1st component of the intersection, accessing `p` results in `number?` and accessing `p` has the 1st " "accessing `p` results in `string?`, and `number` is not exactly `string?`\n\t"
"component of the union as `string`, and `number?` is not exactly `string`\n\t" " * in the 1st component of the intersection, accessing `p` results in `number?` and accessing `p` has the 1st "
" * in the 1st component of the intersection, accessing `q` results in `any` and accessing `q` results in " "component of the union as `string`, and `number?` is not exactly `string`\n\t"
"`number?`, and `any` is not exactly `number?`\n\t" " * in the 1st component of the intersection, accessing `q` results in `any` and accessing `q` results in "
" * in the 2nd component of the intersection, accessing `p` results in `unknown` and accessing `p` results in " "`number?`, and `any` is not exactly `number?`\n\t"
"`string?`, and `unknown` is not exactly `string?`\n\t" " * in the 2nd component of the intersection, accessing `p` results in `unknown` and accessing `p` results in "
" * in the 2nd component of the intersection, accessing `q` has the 1st component of the union as `string` and " "`string?`, and `unknown` is not exactly `string?`\n\t"
"accessing `q` results in `number?`, and `string` is not exactly `number?`\n\t" " * in the 2nd component of the intersection, accessing `q` has the 1st component of the union as `string` and "
" * in the 2nd component of the intersection, accessing `q` results in `string?` and accessing `q` has the 1st " "accessing `q` results in `number?`, and `string` is not exactly `number?`\n\t"
"component of the union as `number`, and `string?` is not exactly `number`"; " * in the 2nd component of the intersection, accessing `q` results in `string?` and accessing `q` has the 1st "
CHECK_EQ(expected, toString(result.errors[0])); "component of the union as `number`, and `string?` is not exactly `number`";
}
else if (FFlag::LuauSolverV2)
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ(
R"(Type
'{ p: number?, q: any } & { p: unknown, q: string? }'
could not be converted into
'{ p: string?, q: number? }'; type { p: number?, q: any } & { p: unknown, q: string? }[0][read "p"] (number?) is not exactly { p: string?, q: number? }[read "p"][0] (string)
type { p: number?, q: any } & { p: unknown, q: string? }[0][read "p"][0] (number) is not exactly { p: string?, q: number? }[read "p"] (string?)
type { p: number?, q: any } & { p: unknown, q: string? }[0][read "q"] (any) is not exactly { p: string?, q: number? }[read "q"] (number?)
type { p: number?, q: any } & { p: unknown, q: string? }[1][read "p"] (unknown) is not exactly { p: string?, q: number? }[read "p"] (string?)
type { p: number?, q: any } & { p: unknown, q: string? }[1][read "q"] (string?) is not exactly { p: string?, q: number? }[read "q"][0] (number)
type { p: number?, q: any } & { p: unknown, q: string? }[1][read "q"][0] (string) is not exactly { p: string?, q: number? }[read "q"] (number?))",
toString(result.errors[0])
);
}
else if (FFlag::LuauImproveTypePathsInErrors)
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = R"(Type
'{| p: number?, q: any |} & {| p: unknown, q: string? |}'
could not be converted into
'{| p: string?, q: number? |}'; none of the intersection parts are compatible)";
CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(expected, toString(result.errors[0]));
} }
else else
{ {
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = R"(Type const std::string expected = R"(Type
'{| p: number?, q: any |} & {| p: unknown, q: string? |}' '{| p: number?, q: any |} & {| p: unknown, q: string? |}'
could not be converted into could not be converted into
'{| p: string?, q: number? |}'; none of the intersection parts are compatible)"; '{| p: string?, q: number? |}'; none of the intersection parts are compatible)";
CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(expected, toString(result.errors[0]));
} }
} }
@ -859,7 +752,7 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_returning_intersections")
end end
)"); )");
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) if (FFlag::LuauSolverV2)
{ {
const std::string expected1 = const std::string expected1 =
"Type\n\t" "Type\n\t"
@ -904,32 +797,7 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_returning_intersections")
CHECK_EQ(expected1, toString(result.errors[0])); CHECK_EQ(expected1, toString(result.errors[0]));
CHECK_EQ(expected2, toString(result.errors[1])); CHECK_EQ(expected2, toString(result.errors[1]));
} }
else if (FFlag::LuauSolverV2) else
{
LUAU_REQUIRE_ERROR_COUNT(2, result);
CHECK_EQ(
R"(Type
'((number?) -> { p: number } & { q: number }) & ((string?) -> { p: number } & { r: number })'
could not be converted into
'(nil) -> { p: number, q: number, r: number }'; type ((number?) -> { p: number } & { q: number }) & ((string?) -> { p: number } & { r: number })[0].returns()[0][0] ({ p: number }) is not a subtype of (nil) -> { p: number, q: number, r: number }.returns()[0] ({ p: number, q: number, r: number })
type ((number?) -> { p: number } & { q: number }) & ((string?) -> { p: number } & { r: number })[0].returns()[0][1] ({ q: number }) is not a subtype of (nil) -> { p: number, q: number, r: number }.returns()[0] ({ p: number, q: number, r: number })
type ((number?) -> { p: number } & { q: number }) & ((string?) -> { p: number } & { r: number })[1].returns()[0][0] ({ p: number }) is not a subtype of (nil) -> { p: number, q: number, r: number }.returns()[0] ({ p: number, q: number, r: number })
type ((number?) -> { p: number } & { q: number }) & ((string?) -> { p: number } & { r: number })[1].returns()[0][1] ({ r: number }) is not a subtype of (nil) -> { p: number, q: number, r: number }.returns()[0] ({ p: number, q: number, r: number }))",
toString(result.errors[0])
);
CHECK_EQ(
R"(Type
'((number?) -> { p: number } & { q: number }) & ((string?) -> { p: number } & { r: number })'
could not be converted into
'(number?) -> { p: number, q: number, r: number }'; type ((number?) -> { p: number } & { q: number }) & ((string?) -> { p: number } & { r: number })[0].returns()[0][0] ({ p: number }) is not a subtype of (number?) -> { p: number, q: number, r: number }.returns()[0] ({ p: number, q: number, r: number })
type ((number?) -> { p: number } & { q: number }) & ((string?) -> { p: number } & { r: number })[0].returns()[0][1] ({ q: number }) is not a subtype of (number?) -> { p: number, q: number, r: number }.returns()[0] ({ p: number, q: number, r: number })
type ((number?) -> { p: number } & { q: number }) & ((string?) -> { p: number } & { r: number })[1].arguments()[0] (string?) is not a supertype of (number?) -> { p: number, q: number, r: number }.arguments()[0][0] (number)
type ((number?) -> { p: number } & { q: number }) & ((string?) -> { p: number } & { r: number })[1].returns()[0][0] ({ p: number }) is not a subtype of (number?) -> { p: number, q: number, r: number }.returns()[0] ({ p: number, q: number, r: number })
type ((number?) -> { p: number } & { q: number }) & ((string?) -> { p: number } & { r: number })[1].returns()[0][1] ({ r: number }) is not a subtype of (number?) -> { p: number, q: number, r: number }.returns()[0] ({ p: number, q: number, r: number }))",
toString(result.errors[1])
);
}
else if (FFlag::LuauImproveTypePathsInErrors)
{ {
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ( CHECK_EQ(
@ -940,17 +808,6 @@ could not be converted into
toString(result.errors[0]) toString(result.errors[0])
); );
} }
else
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ(
R"(Type
'((number?) -> {| p: number |} & {| q: number |}) & ((string?) -> {| p: number |} & {| r: number |})'
could not be converted into
'(number?) -> {| p: number, q: number, r: number |}'; none of the intersection parts are compatible)",
toString(result.errors[0])
);
}
} }
TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic") TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic")
@ -967,7 +824,7 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic")
{ {
LUAU_REQUIRE_ERROR_COUNT(0, result); LUAU_REQUIRE_ERROR_COUNT(0, result);
} }
else if (FFlag::LuauImproveTypePathsInErrors) else
{ {
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = R"(Type const std::string expected = R"(Type
@ -976,15 +833,6 @@ could not be converted into
'(number?) -> a'; none of the intersection parts are compatible)"; '(number?) -> a'; none of the intersection parts are compatible)";
CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(expected, toString(result.errors[0]));
} }
else
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = R"(Type
'((number?) -> a | number) & ((string?) -> a | string)'
could not be converted into
'(number?) -> a'; none of the intersection parts are compatible)";
CHECK_EQ(expected, toString(result.errors[0]));
}
} }
TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generics") TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generics")
@ -1003,7 +851,7 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generics")
{ {
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
} }
else if (FFlag::LuauImproveTypePathsInErrors) else
{ {
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = R"(Type const std::string expected = R"(Type
@ -1012,15 +860,6 @@ could not be converted into
'(a?) -> (a & c) | b'; none of the intersection parts are compatible)"; '(a?) -> (a & c) | b'; none of the intersection parts are compatible)";
CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(expected, toString(result.errors[0]));
} }
else
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = R"(Type
'((a?) -> a | b) & ((c?) -> b | c)'
could not be converted into
'(a?) -> (a & c) | b'; none of the intersection parts are compatible)";
CHECK_EQ(expected, toString(result.errors[0]));
}
} }
TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic_packs") TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic_packs")
@ -1034,7 +873,7 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic_packs")
end end
)"); )");
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) if (FFlag::LuauSolverV2)
{ {
const std::string expected1 = const std::string expected1 =
"Type\n\t" "Type\n\t"
@ -1061,27 +900,7 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic_packs")
CHECK_EQ(expected1, toString(result.errors[0])); CHECK_EQ(expected1, toString(result.errors[0]));
CHECK_EQ(expected2, toString(result.errors[1])); CHECK_EQ(expected2, toString(result.errors[1]));
} }
else if (FFlag::LuauSolverV2) else
{
LUAU_REQUIRE_ERROR_COUNT(2, result);
CHECK_EQ(
R"(Type
'((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...))'
could not be converted into
'(nil, a...) -> (nil, b...)'; type ((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...))[0].returns()[0][0] (number) is not a subtype of (nil, a...) -> (nil, b...).returns()[0] (nil)
type ((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...))[1].returns()[0][0] (string) is not a subtype of (nil, a...) -> (nil, b...).returns()[0] (nil))",
toString(result.errors[0])
);
CHECK_EQ(
R"(Type
'((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...))'
could not be converted into
'(nil, b...) -> (nil, a...)'; type ((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...))[0].returns()[0][0] (number) is not a subtype of (nil, b...) -> (nil, a...).returns()[0] (nil)
type ((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...))[1].returns()[0][0] (string) is not a subtype of (nil, b...) -> (nil, a...).returns()[0] (nil))",
toString(result.errors[1])
);
}
else if (FFlag::LuauImproveTypePathsInErrors)
{ {
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = R"(Type const std::string expected = R"(Type
@ -1090,15 +909,6 @@ could not be converted into
'(nil, b...) -> (nil, a...)'; none of the intersection parts are compatible)"; '(nil, b...) -> (nil, a...)'; none of the intersection parts are compatible)";
CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(expected, toString(result.errors[0]));
} }
else
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = R"(Type
'((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...))'
could not be converted into
'(nil, b...) -> (nil, a...)'; none of the intersection parts are compatible)";
CHECK_EQ(expected, toString(result.errors[0]));
}
} }
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_result") TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_result")
@ -1117,22 +927,11 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_result")
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauImproveTypePathsInErrors) const std::string expected = "Type\n\t"
{ "'((nil) -> unknown) & ((number) -> number)'"
const std::string expected = "Type\n\t" "\ncould not be converted into\n\t"
"'((nil) -> unknown) & ((number) -> number)'" "'(number?) -> number?'; none of the intersection parts are compatible";
"\ncould not be converted into\n\t" CHECK_EQ(expected, toString(result.errors[0]));
"'(number?) -> number?'; none of the intersection parts are compatible";
CHECK_EQ(expected, toString(result.errors[0]));
}
else
{
const std::string expected = R"(Type
'((nil) -> unknown) & ((number) -> number)'
could not be converted into
'(number?) -> number?'; none of the intersection parts are compatible)";
CHECK_EQ(expected, toString(result.errors[0]));
}
} }
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_arguments") TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_arguments")
@ -1151,22 +950,12 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_arguments")
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauImproveTypePathsInErrors) const std::string expected =
{ "Type\n\t"
const std::string expected = "Type\n\t" "'((number) -> number?) & ((unknown) -> string?)'"
"'((number) -> number?) & ((unknown) -> string?)'" "\ncould not be converted into\n\t"
"\ncould not be converted into\n\t" "'(number?) -> nil'; none of the intersection parts are compatible";
"'(number?) -> nil'; none of the intersection parts are compatible"; CHECK_EQ(expected, toString(result.errors[0]));
CHECK_EQ(expected, toString(result.errors[0]));
}
else
{
const std::string expected = R"(Type
'((number) -> number?) & ((unknown) -> string?)'
could not be converted into
'(number?) -> nil'; none of the intersection parts are compatible)";
CHECK_EQ(expected, toString(result.errors[0]));
}
} }
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_result") TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_result")
@ -1180,7 +969,7 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_result")
end end
)"); )");
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) if (FFlag::LuauSolverV2)
{ {
const std::string expected1 = const std::string expected1 =
"Type\n\t" "Type\n\t"
@ -1209,28 +998,7 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_result")
CHECK_EQ(expected1, toString(result.errors[0])); CHECK_EQ(expected1, toString(result.errors[0]));
CHECK_EQ(expected2, toString(result.errors[1])); CHECK_EQ(expected2, toString(result.errors[1]));
} }
else if (FFlag::LuauSolverV2) else
{
LUAU_REQUIRE_ERROR_COUNT(2, result);
CHECK_EQ(
R"(Type
'((nil) -> never) & ((number) -> number)'
could not be converted into
'(number?) -> number'; type ((nil) -> never) & ((number) -> number)[0].arguments()[0] (number) is not a supertype of (number?) -> number.arguments()[0][1] (nil)
type ((nil) -> never) & ((number) -> number)[1].arguments()[0] (nil) is not a supertype of (number?) -> number.arguments()[0][0] (number))",
toString(result.errors[0])
);
CHECK_EQ(
R"(Type
'((nil) -> never) & ((number) -> number)'
could not be converted into
'(number?) -> never'; type ((nil) -> never) & ((number) -> number)[0].arguments()[0] (number) is not a supertype of (number?) -> never.arguments()[0][1] (nil)
type ((nil) -> never) & ((number) -> number)[0].returns()[0] (number) is not a subtype of (number?) -> never.returns()[0] (never)
type ((nil) -> never) & ((number) -> number)[1].arguments()[0] (nil) is not a supertype of (number?) -> never.arguments()[0][0] (number))",
toString(result.errors[1])
);
}
else if (FFlag::LuauImproveTypePathsInErrors)
{ {
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = R"(Type const std::string expected = R"(Type
@ -1239,15 +1007,6 @@ could not be converted into
'(number?) -> never'; none of the intersection parts are compatible)"; '(number?) -> never'; none of the intersection parts are compatible)";
CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(expected, toString(result.errors[0]));
} }
else
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = R"(Type
'((nil) -> never) & ((number) -> number)'
could not be converted into
'(number?) -> never'; none of the intersection parts are compatible)";
CHECK_EQ(expected, toString(result.errors[0]));
}
} }
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_arguments") TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_arguments")
@ -1261,7 +1020,7 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_arguments")
end end
)"); )");
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) if (FFlag::LuauSolverV2)
{ {
const std::string expected1 = const std::string expected1 =
"Type\n\t" "Type\n\t"
@ -1294,26 +1053,7 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_arguments")
CHECK_EQ(expected1, toString(result.errors[0])); CHECK_EQ(expected1, toString(result.errors[0]));
CHECK_EQ(expected2, toString(result.errors[1])); CHECK_EQ(expected2, toString(result.errors[1]));
} }
else if (FFlag::LuauSolverV2) else
{
LUAU_REQUIRE_ERROR_COUNT(2, result);
const std::string expected1 = R"(Type
'((never) -> string?) & ((number) -> number?)'
could not be converted into
'(never) -> nil'; type ((never) -> string?) & ((number) -> number?)[0].returns()[0][0] (number) is not a subtype of (never) -> nil.returns()[0] (nil)
type ((never) -> string?) & ((number) -> number?)[1].returns()[0][0] (string) is not a subtype of (never) -> nil.returns()[0] (nil))";
const std::string expected2 = R"(Type
'((never) -> string?) & ((number) -> number?)'
could not be converted into
'(number?) -> nil'; type ((never) -> string?) & ((number) -> number?)[0].arguments()[0] (number) is not a supertype of (number?) -> nil.arguments()[0][1] (nil)
type ((never) -> string?) & ((number) -> number?)[0].returns()[0][0] (number) is not a subtype of (number?) -> nil.returns()[0] (nil)
type ((never) -> string?) & ((number) -> number?)[1].arguments()[0] (never) is not a supertype of (number?) -> nil.arguments()[0][0] (number)
type ((never) -> string?) & ((number) -> number?)[1].arguments()[0] (never) is not a supertype of (number?) -> nil.arguments()[0][1] (nil)
type ((never) -> string?) & ((number) -> number?)[1].returns()[0][0] (string) is not a subtype of (number?) -> nil.returns()[0] (nil))";
CHECK_EQ(expected1, toString(result.errors[0]));
CHECK_EQ(expected2, toString(result.errors[1]));
}
else if (FFlag::LuauImproveTypePathsInErrors)
{ {
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = R"(Type const std::string expected = R"(Type
@ -1322,15 +1062,6 @@ could not be converted into
'(number?) -> nil'; none of the intersection parts are compatible)"; '(number?) -> nil'; none of the intersection parts are compatible)";
CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(expected, toString(result.errors[0]));
} }
else
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = R"(Type
'((never) -> string?) & ((number) -> number?)'
could not be converted into
'(number?) -> nil'; none of the intersection parts are compatible)";
CHECK_EQ(expected, toString(result.errors[0]));
}
} }
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_overlapping_results_and_variadics") TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_overlapping_results_and_variadics")
@ -1347,22 +1078,11 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_overlapping_results_and_
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauImproveTypePathsInErrors) const std::string expected = "Type\n\t"
{ "'((number?) -> (...number)) & ((string?) -> number | string)'"
const std::string expected = "Type\n\t" "\ncould not be converted into\n\t"
"'((number?) -> (...number)) & ((string?) -> number | string)'" "'(number | string) -> (number, number?)'; none of the intersection parts are compatible";
"\ncould not be converted into\n\t" CHECK(expected == toString(result.errors[0]));
"'(number | string) -> (number, number?)'; none of the intersection parts are compatible";
CHECK(expected == toString(result.errors[0]));
}
else
{
const std::string expected = R"(Type
'((number?) -> (...number)) & ((string?) -> number | string)'
could not be converted into
'(number | string) -> (number, number?)'; none of the intersection parts are compatible)";
CHECK_EQ(expected, toString(result.errors[0]));
}
} }
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_1") TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_1")
@ -1430,7 +1150,7 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_3")
{ {
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
} }
else if (FFlag::LuauImproveTypePathsInErrors) else
{ {
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = R"(Type const std::string expected = R"(Type
@ -1439,15 +1159,6 @@ could not be converted into
'() -> number'; none of the intersection parts are compatible)"; '() -> number'; none of the intersection parts are compatible)";
CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(expected, toString(result.errors[0]));
} }
else
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = R"(Type
'(() -> (a...)) & (() -> (number?, a...))'
could not be converted into
'() -> number'; none of the intersection parts are compatible)";
CHECK_EQ(expected, toString(result.errors[0]));
}
} }
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_4") TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_4")
@ -1463,31 +1174,21 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_4")
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) if (FFlag::LuauSolverV2)
{ {
const std::string expected = "Type\n\t" const std::string expected =
"'((a...) -> ()) & ((number, a...) -> number)'" "Type\n\t"
"\ncould not be converted into\n\t" "'((a...) -> ()) & ((number, a...) -> number)'"
"'((a...) -> ()) & ((number, a...) -> number)'; \n" "\ncould not be converted into\n\t"
"this is because \n\t" "'((a...) -> ()) & ((number, a...) -> number)'; \n"
" * in the 1st component of the intersection, the function returns is `()` in the former type and `number` in " "this is because \n\t"
"the latter type, and `()` is not a subtype of `number`\n\t" " * in the 1st component of the intersection, the function returns is `()` in the former type and `number` in "
" * in the 2nd component of the intersection, the function takes a tail of `a...` and in the 1st component of " "the latter type, and `()` is not a subtype of `number`\n\t"
"the intersection, the function takes a tail of `a...`, and `a...` is not a supertype of `a...`"; " * in the 2nd component of the intersection, the function takes a tail of `a...` and in the 1st component of "
"the intersection, the function takes a tail of `a...`, and `a...` is not a supertype of `a...`";
CHECK(expected == toString(result.errors[0])); CHECK(expected == toString(result.errors[0]));
} }
else if (FFlag::LuauSolverV2) else
{
CHECK_EQ(
R"(Type
'((a...) -> ()) & ((number, a...) -> number)'
could not be converted into
'((a...) -> ()) & ((number, a...) -> number)'; at [0].returns(), is not a subtype of number
type ((a...) -> ()) & ((number, a...) -> number)[1].arguments().tail() (a...) is not a supertype of ((a...) -> ()) & ((number, a...) -> number)[0].arguments().tail() (a...))",
toString(result.errors[0])
);
}
else if (FFlag::LuauImproveTypePathsInErrors)
{ {
CHECK_EQ( CHECK_EQ(
R"(Type R"(Type
@ -1497,16 +1198,6 @@ could not be converted into
toString(result.errors[0]) toString(result.errors[0])
); );
} }
else
{
CHECK_EQ(
R"(Type
'((a...) -> ()) & ((number, a...) -> number)'
could not be converted into
'(number?) -> ()'; none of the intersection parts are compatible)",
toString(result.errors[0])
);
}
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatables") TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatables")

View file

@ -12,7 +12,6 @@
LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions) LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization) LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect) LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect)
@ -465,20 +464,14 @@ local b: B.T = a
CheckResult result = frontend.check("game/C"); CheckResult result = frontend.check("game/C");
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) if (FFlag::LuauSolverV2)
{ {
const std::string expected = "Type 'T' from 'game/A' could not be converted into 'T' from 'game/B'; \n" const std::string expected =
"this is because accessing `x` results in `number` in the former type and `string` in the latter type, and " "Type 'T' from 'game/A' could not be converted into 'T' from 'game/B'; \n"
"`number` is not exactly `string`"; "this is because accessing `x` results in `number` in the former type and `string` in the latter type, and "
"`number` is not exactly `string`";
CHECK(expected == toString(result.errors[0])); CHECK(expected == toString(result.errors[0]));
} }
else if (FFlag::LuauSolverV2)
{
CHECK(
toString(result.errors.at(0)) ==
"Type 'T' from 'game/A' could not be converted into 'T' from 'game/B'; at [read \"x\"], number is not exactly string"
);
}
else else
{ {
const std::string expected = R"(Type 'T' from 'game/A' could not be converted into 'T' from 'game/B' const std::string expected = R"(Type 'T' from 'game/A' could not be converted into 'T' from 'game/B'
@ -518,20 +511,14 @@ local b: B.T = a
CheckResult result = frontend.check("game/D"); CheckResult result = frontend.check("game/D");
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) if (FFlag::LuauSolverV2)
{ {
const std::string expected = "Type 'T' from 'game/B' could not be converted into 'T' from 'game/C'; \n" const std::string expected =
"this is because accessing `x` results in `number` in the former type and `string` in the latter type, and " "Type 'T' from 'game/B' could not be converted into 'T' from 'game/C'; \n"
"`number` is not exactly `string`"; "this is because accessing `x` results in `number` in the former type and `string` in the latter type, and "
"`number` is not exactly `string`";
CHECK(expected == toString(result.errors[0])); CHECK(expected == toString(result.errors[0]));
} }
else if (FFlag::LuauSolverV2)
{
CHECK(
toString(result.errors.at(0)) ==
"Type 'T' from 'game/B' could not be converted into 'T' from 'game/C'; at [read \"x\"], number is not exactly string"
);
}
else else
{ {
const std::string expected = R"(Type 'T' from 'game/B' could not be converted into 'T' from 'game/C' const std::string expected = R"(Type 'T' from 'game/B' could not be converted into 'T' from 'game/C'

View file

@ -19,7 +19,6 @@ LUAU_FASTINT(LuauTarjanChildLimit)
LUAU_FASTINT(LuauTypeInferIterationLimit) LUAU_FASTINT(LuauTypeInferIterationLimit)
LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTINT(LuauTypeInferTypePackLoopLimit) LUAU_FASTINT(LuauTypeInferTypePackLoopLimit)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
TEST_SUITE_BEGIN("ProvisionalTests"); TEST_SUITE_BEGIN("ProvisionalTests");
@ -874,20 +873,13 @@ TEST_CASE_FIXTURE(Fixture, "assign_table_with_refined_property_with_a_similar_ty
else else
{ {
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = (FFlag::LuauImproveTypePathsInErrors) ? const std::string expected =
R"(Type R"(Type
'{| x: number? |}' '{| x: number? |}'
could not be converted into could not be converted into
'{| x: number |}' '{| x: number |}'
caused by: caused by:
Property 'x' is not compatible. Property 'x' is not compatible.
Type 'number?' could not be converted into 'number' in an invariant context)"
: R"(Type
'{| x: number? |}'
could not be converted into
'{| x: number |}'
caused by:
Property 'x' is not compatible.
Type 'number?' could not be converted into 'number' in an invariant context)"; Type 'number?' could not be converted into 'number' in an invariant context)";
CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(expected, toString(result.errors[0]));
} }

View file

@ -11,13 +11,13 @@
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(DebugLuauEqSatSimplification) LUAU_FASTFLAG(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauIntersectNotNil) LUAU_FASTFLAG(LuauIntersectNotNil)
LUAU_FASTFLAG(LuauSkipNoRefineDuringRefinement)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization) LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAG(LuauFunctionCallsAreNotNilable) LUAU_FASTFLAG(LuauFunctionCallsAreNotNilable)
LUAU_FASTFLAG(LuauSimplyRefineNotNil) LUAU_FASTFLAG(LuauSimplyRefineNotNil)
LUAU_FASTFLAG(LuauWeakNilRefinementType) LUAU_FASTFLAG(LuauWeakNilRefinementType)
LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions) LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions)
LUAU_FASTFLAG(LuauSimplificationTableExternType) LUAU_FASTFLAG(LuauSimplificationTableExternType)
LUAU_FASTFLAG(LuauAvoidDoubleNegation)
using namespace Luau; using namespace Luau;
@ -1243,6 +1243,8 @@ TEST_CASE_FIXTURE(Fixture, "apply_refinements_on_astexprindexexpr_whose_subscrip
TEST_CASE_FIXTURE(Fixture, "discriminate_from_truthiness_of_x") TEST_CASE_FIXTURE(Fixture, "discriminate_from_truthiness_of_x")
{ {
ScopedFastFlag _{FFlag::LuauAvoidDoubleNegation, true};
CheckResult result = check(R"( CheckResult result = check(R"(
type T = {tag: "missing", x: nil} | {tag: "exists", x: string} type T = {tag: "missing", x: nil} | {tag: "exists", x: string}
@ -1259,9 +1261,12 @@ TEST_CASE_FIXTURE(Fixture, "discriminate_from_truthiness_of_x")
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
{ {
// CLI-115281 Types produced by refinements do not consistently get simplified // CLI-115281 Types produced by refinements do not consistently get
// simplified. Sometimes this is due to not refining at the correct
// time, sometimes this is due to hitting the simplifier rather than
// normalization.
CHECK("{ tag: \"exists\", x: string } & { x: ~(false?) }" == toString(requireTypeAtPosition({5, 28}))); CHECK("{ tag: \"exists\", x: string } & { x: ~(false?) }" == toString(requireTypeAtPosition({5, 28})));
CHECK("({ tag: \"exists\", x: string } & { x: ~~(false?) }) | { tag: \"missing\", x: nil }" == toString(requireTypeAtPosition({7, 28}))); CHECK(R"(({ tag: "exists", x: string } & { x: false? }) | ({ tag: "missing", x: nil } & { x: false? }))" == toString(requireTypeAtPosition({7, 28})));
} }
else else
{ {
@ -2552,8 +2557,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "truthy_refinement_on_generic")
TEST_CASE_FIXTURE(Fixture, "truthy_call_of_function_with_table_value_as_argument_should_not_refine_value_as_never") TEST_CASE_FIXTURE(Fixture, "truthy_call_of_function_with_table_value_as_argument_should_not_refine_value_as_never")
{ {
ScopedFastFlag sff{FFlag::LuauSkipNoRefineDuringRefinement, true};
CheckResult result = check(R"( CheckResult result = check(R"(
type Item = {} type Item = {}

View file

@ -8,7 +8,6 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauPropagateExpectedTypesForCalls) LUAU_FASTFLAG(LuauPropagateExpectedTypesForCalls)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
TEST_SUITE_BEGIN("TypeSingletons"); TEST_SUITE_BEGIN("TypeSingletons");
@ -387,7 +386,7 @@ TEST_CASE_FIXTURE(Fixture, "table_properties_type_error_escapes")
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) if (FFlag::LuauSolverV2)
{ {
const std::string expected = "Type\n\t" const std::string expected = "Type\n\t"
"'{ [\"\\n\"]: number }'" "'{ [\"\\n\"]: number }'"
@ -395,13 +394,6 @@ TEST_CASE_FIXTURE(Fixture, "table_properties_type_error_escapes")
"'{ [\"<>\"]: number }'"; "'{ [\"<>\"]: number }'";
CHECK(expected == toString(result.errors[0])); CHECK(expected == toString(result.errors[0]));
} }
else if (FFlag::LuauSolverV2)
CHECK(
"Type\n"
" '{ [\"\\n\"]: number }'\n"
"could not be converted into\n"
" '{ [\"<>\"]: number }'" == toString(result.errors[0])
);
else else
CHECK_EQ( CHECK_EQ(
R"(Table type '{ ["\n"]: number }' not compatible with type '{| ["<>"]: number |}' because the former is missing field '<>')", R"(Table type '{ ["\n"]: number }' not compatible with type '{| ["<>"]: number |}' because the former is missing field '<>')",
@ -471,23 +463,12 @@ TEST_CASE_FIXTURE(Fixture, "parametric_tagged_union_alias")
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauImproveTypePathsInErrors) const std::string expectedError = "Type\n\t"
{ "'{ result: string, success: boolean }'"
const std::string expectedError = "Type\n\t" "\ncould not be converted into\n\t"
"'{ result: string, success: boolean }'" "'Err<number> | Ok<string>'";
"\ncould not be converted into\n\t" CHECK(toString(result.errors[0]) == expectedError);
"'Err<number> | Ok<string>'";
CHECK(toString(result.errors[0]) == expectedError);
}
else
{
const std::string expectedError = R"(Type
'{ result: string, success: boolean }'
could not be converted into
'Err<number> | Ok<string>')";
CHECK(toString(result.errors[0]) == expectedError);
}
} }
TEST_CASE_FIXTURE(Fixture, "if_then_else_expression_singleton_options") TEST_CASE_FIXTURE(Fixture, "if_then_else_expression_singleton_options")

View file

@ -24,12 +24,10 @@ LUAU_FASTFLAG(LuauFixIndexerSubtypingOrdering)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization) LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAG(LuauNonReentrantGeneralization2) LUAU_FASTFLAG(LuauNonReentrantGeneralization2)
LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint) LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
LUAU_FASTFLAG(LuauBidirectionalInferenceCollectIndexerTypes)
LUAU_FASTFLAG(LuauBidirectionalFailsafe)
LUAU_FASTFLAG(LuauBidirectionalInferenceElideAssert) LUAU_FASTFLAG(LuauBidirectionalInferenceElideAssert)
LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect) LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect)
LUAU_FASTFLAG(LuauTypeCheckerStricterIndexCheck) LUAU_FASTFLAG(LuauTypeCheckerStricterIndexCheck)
LUAU_FASTFLAG(LuauReportSubtypingErrors)
TEST_SUITE_BEGIN("TableTests"); TEST_SUITE_BEGIN("TableTests");
@ -924,7 +922,7 @@ TEST_CASE_FIXTURE(Fixture, "sealed_table_indexers_must_unify")
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) if (FFlag::LuauSolverV2)
{ {
CHECK( CHECK(
"Type pack '{number}' could not be converted into '{string}'; \n" "Type pack '{number}' could not be converted into '{string}'; \n"
@ -933,14 +931,6 @@ TEST_CASE_FIXTURE(Fixture, "sealed_table_indexers_must_unify")
"and `number` is not exactly `string`" == toString(result.errors[0]) "and `number` is not exactly `string`" == toString(result.errors[0])
); );
} }
else if (FFlag::LuauSolverV2)
{
// CLI-114879 - Error path reporting is not great
CHECK(
toString(result.errors[0]) ==
"Type pack '{number}' could not be converted into '{string}'; at [0].indexResult(), number is not exactly string"
);
}
else else
CHECK_MESSAGE(nullptr != get<TypeMismatch>(result.errors[0]), "Expected a TypeMismatch but got " << result.errors[0]); CHECK_MESSAGE(nullptr != get<TypeMismatch>(result.errors[0]), "Expected a TypeMismatch but got " << result.errors[0]);
} }
@ -1816,7 +1806,7 @@ TEST_CASE_FIXTURE(Fixture, "table_subtyping_with_missing_props_dont_report_multi
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) if (FFlag::LuauSolverV2)
{ {
CHECK_EQ( CHECK_EQ(
"Type pack '{ x: number }' could not be converted into '{ x: number, y: number, z: number }'; \n" "Type pack '{ x: number }' could not be converted into '{ x: number, y: number, z: number }'; \n"
@ -1825,14 +1815,6 @@ TEST_CASE_FIXTURE(Fixture, "table_subtyping_with_missing_props_dont_report_multi
toString(result.errors[0]) toString(result.errors[0])
); );
} }
else if (FFlag::LuauSolverV2)
{
CHECK_EQ(
"Type pack '{ x: number }' could not be converted into '{ x: number, y: number, z: number }';"
" at [0], { x: number } is not a subtype of { x: number, y: number, z: number }",
toString(result.errors[0])
);
}
else else
{ {
MissingProperties* mp = get<MissingProperties>(result.errors[0]); MissingProperties* mp = get<MissingProperties>(result.errors[0]);
@ -2455,7 +2437,7 @@ local b: B = a
LUAU_REQUIRE_ERRORS(result); LUAU_REQUIRE_ERRORS(result);
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) if (FFlag::LuauSolverV2)
{ {
CHECK( CHECK(
"Type 'A' could not be converted into 'B'; \n" "Type 'A' could not be converted into 'B'; \n"
@ -2463,8 +2445,6 @@ local b: B = a
"`string`" == toString(result.errors.at(0)) "`string`" == toString(result.errors.at(0))
); );
} }
else if (FFlag::LuauSolverV2)
CHECK(toString(result.errors.at(0)) == R"(Type 'A' could not be converted into 'B'; at [read "y"], number is not exactly string)");
else else
{ {
const std::string expected = R"(Type 'A' could not be converted into 'B' const std::string expected = R"(Type 'A' could not be converted into 'B'
@ -2490,7 +2470,7 @@ local b: B = a
LUAU_REQUIRE_ERRORS(result); LUAU_REQUIRE_ERRORS(result);
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) if (FFlag::LuauSolverV2)
{ {
CHECK( CHECK(
"Type 'A' could not be converted into 'B'; \n" "Type 'A' could not be converted into 'B'; \n"
@ -2498,8 +2478,6 @@ local b: B = a
"`string`" == toString(result.errors.at(0)) "`string`" == toString(result.errors.at(0))
); );
} }
else if (FFlag::LuauSolverV2)
CHECK(toString(result.errors.at(0)) == R"(Type 'A' could not be converted into 'B'; at [read "b"][read "y"], number is not exactly string)");
else else
{ {
const std::string expected = R"(Type 'A' could not be converted into 'B' const std::string expected = R"(Type 'A' could not be converted into 'B'
@ -2525,8 +2503,8 @@ local b2 = setmetatable({ x = 2, y = 4 }, { __call = function(s, t) end });
local c2: typeof(a2) = b2 local c2: typeof(a2) = b2
)"); )");
const std::string expected1 = (FFlag::LuauImproveTypePathsInErrors) ? const std::string expected1 =
R"(Type 'b1' could not be converted into 'a1' R"(Type 'b1' could not be converted into 'a1'
caused by: caused by:
Type Type
'{ x: number, y: string }' '{ x: number, y: string }'
@ -2534,18 +2512,9 @@ could not be converted into
'{ x: number, y: number }' '{ x: number, y: number }'
caused by: caused by:
Property 'y' is not compatible. Property 'y' is not compatible.
Type 'string' could not be converted into 'number' in an invariant context)"
: R"(Type 'b1' could not be converted into 'a1'
caused by:
Type
'{ x: number, y: string }'
could not be converted into
'{ x: number, y: number }'
caused by:
Property 'y' is not compatible.
Type 'string' could not be converted into 'number' in an invariant context)"; Type 'string' could not be converted into 'number' in an invariant context)";
const std::string expected2 = (FFlag::LuauImproveTypePathsInErrors) ? const std::string expected2 =
R"(Type 'b2' could not be converted into 'a2' R"(Type 'b2' could not be converted into 'a2'
caused by: caused by:
Type Type
'{ __call: <a, b>(a, b) -> () }' '{ __call: <a, b>(a, b) -> () }'
@ -2556,21 +2525,9 @@ caused by:
Type Type
'<a, b>(a, b) -> ()' '<a, b>(a, b) -> ()'
could not be converted into could not be converted into
'<a>(a) -> ()'; different number of generic type parameters)" '<a>(a) -> ()'; different number of generic type parameters)";
: R"(Type 'b2' could not be converted into 'a2'
caused by:
Type
'{ __call: <a, b>(a, b) -> () }'
could not be converted into
'{ __call: <a>(a) -> () }'
caused by:
Property '__call' is not compatible.
Type
'<a, b>(a, b) -> ()'
could not be converted into
'<a>(a) -> ()'; different number of generic type parameters)";
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) if (FFlag::LuauSolverV2)
{ {
// The assignment of c2 to b2 is, surprisingly, allowed under the new // The assignment of c2 to b2 is, surprisingly, allowed under the new
// solver for two reasons: // solver for two reasons:
@ -2586,18 +2543,6 @@ could not be converted into
"`string` is not exactly `number`" == toString(result.errors[0]) "`string` is not exactly `number`" == toString(result.errors[0])
); );
} }
else if (FFlag::LuauSolverV2)
{
// The assignment of c2 to b2 is, surprisingly, allowed under the new
// solver for two reasons:
//
// First, both of the __call functions have hidden ...any arguments
// because their exact definition is available.
//
// Second, nil <: unknown, so we consider that parameter to be optional.
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK("Type 'b1' could not be converted into 'a1'; at table()[read \"y\"], string is not exactly number" == toString(result.errors[0]));
}
else if (FFlag::LuauInstantiateInSubtyping) else if (FFlag::LuauInstantiateInSubtyping)
{ {
LUAU_REQUIRE_ERROR_COUNT(2, result); LUAU_REQUIRE_ERROR_COUNT(2, result);
@ -2606,15 +2551,15 @@ could not be converted into
const std::string expected3 = R"(Type 'b2' could not be converted into 'a2' const std::string expected3 = R"(Type 'b2' could not be converted into 'a2'
caused by: caused by:
Type Type
'{ __call: <a, b>(a, b) -> () }' '{ __call: <a, b>(a, b) -> () }'
could not be converted into could not be converted into
'{ __call: <a>(a) -> () }' '{ __call: <a>(a) -> () }'
caused by: caused by:
Property '__call' is not compatible. Property '__call' is not compatible.
Type Type
'<a, b>(a, b) -> ()' '<a, b>(a, b) -> ()'
could not be converted into could not be converted into
'<a>(a) -> ()'; different number of generic type parameters)"; '<a>(a) -> ()'; different number of generic type parameters)";
CHECK_EQ(expected2, toString(result.errors[1])); CHECK_EQ(expected2, toString(result.errors[1]));
} }
@ -2626,15 +2571,15 @@ could not be converted into
std::string expected3 = R"(Type 'b2' could not be converted into 'a2' std::string expected3 = R"(Type 'b2' could not be converted into 'a2'
caused by: caused by:
Type Type
'{ __call: (a, b) -> () }' '{ __call: (a, b) -> () }'
could not be converted into could not be converted into
'{ __call: <a>(a) -> () }' '{ __call: <a>(a) -> () }'
caused by: caused by:
Property '__call' is not compatible. Property '__call' is not compatible.
Type Type
'(a, b) -> ()' '(a, b) -> ()'
could not be converted into could not be converted into
'<a>(a) -> ()'; different number of generic type parameters)"; '<a>(a) -> ()'; different number of generic type parameters)";
CHECK_EQ(expected3, toString(result.errors[1])); CHECK_EQ(expected3, toString(result.errors[1]));
} }
} }
@ -2651,7 +2596,7 @@ TEST_CASE_FIXTURE(Fixture, "error_detailed_indexer_key")
LUAU_REQUIRE_ERRORS(result); LUAU_REQUIRE_ERRORS(result);
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) if (FFlag::LuauSolverV2)
{ {
CHECK( CHECK(
"Type 'A' could not be converted into 'B'; \n" "Type 'A' could not be converted into 'B'; \n"
@ -2659,10 +2604,6 @@ TEST_CASE_FIXTURE(Fixture, "error_detailed_indexer_key")
toString(result.errors[0]) toString(result.errors[0])
); );
} }
else if (FFlag::LuauSolverV2)
{
CHECK("Type 'A' could not be converted into 'B'; at indexer(), number is not exactly string" == toString(result.errors[0]));
}
else else
{ {
const std::string expected = R"(Type 'A' could not be converted into 'B' const std::string expected = R"(Type 'A' could not be converted into 'B'
@ -2685,7 +2626,7 @@ TEST_CASE_FIXTURE(Fixture, "error_detailed_indexer_value")
LUAU_REQUIRE_ERRORS(result); LUAU_REQUIRE_ERRORS(result);
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) if (FFlag::LuauSolverV2)
{ {
CHECK( CHECK(
"Type 'A' could not be converted into 'B'; \n" "Type 'A' could not be converted into 'B'; \n"
@ -2693,10 +2634,6 @@ TEST_CASE_FIXTURE(Fixture, "error_detailed_indexer_value")
"`string`" == toString(result.errors[0]) "`string`" == toString(result.errors[0])
); );
} }
else if (FFlag::LuauSolverV2)
{
CHECK("Type 'A' could not be converted into 'B'; at indexResult(), number is not exactly string" == toString(result.errors[0]));
}
else else
{ {
const std::string expected = R"(Type 'A' could not be converted into 'B' const std::string expected = R"(Type 'A' could not be converted into 'B'
@ -2742,17 +2679,12 @@ local y: number = tmp.p.y
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) if (FFlag::LuauSolverV2)
CHECK( CHECK(
"Type 'tmp' could not be converted into 'HasSuper'; \n" "Type 'tmp' could not be converted into 'HasSuper'; \n"
"this is because accessing `p` results in `{ x: number, y: number }` in the former type and `Super` in the latter type, and `{ x: " "this is because accessing `p` results in `{ x: number, y: number }` in the former type and `Super` in the latter type, and `{ x: "
"number, y: number }` is not exactly `Super`" == toString(result.errors[0]) "number, y: number }` is not exactly `Super`" == toString(result.errors[0])
); );
else if (FFlag::LuauSolverV2)
CHECK(
"Type 'tmp' could not be converted into 'HasSuper'; at [read \"p\"], { x: number, y: number } is not exactly Super" ==
toString(result.errors[0])
);
else else
{ {
const std::string expected = R"(Type 'tmp' could not be converted into 'HasSuper' const std::string expected = R"(Type 'tmp' could not be converted into 'HasSuper'
@ -3712,21 +3644,13 @@ TEST_CASE_FIXTURE(Fixture, "mixed_tables_with_implicit_numbered_keys")
local t: { [string]: number } = { 5, 6, 7 } local t: { [string]: number } = { 5, 6, 7 }
)"); )");
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) if (FFlag::LuauSolverV2)
{ {
std::string expected = std::string expected =
"Type '{number}' could not be converted into '{ [string]: number }'; \n" "Type '{number}' could not be converted into '{ [string]: number }'; \n"
"this is because the index type is `number` in the former type and `string` in the latter type, and `number` is not exactly `string`"; "this is because the index type is `number` in the former type and `string` in the latter type, and `number` is not exactly `string`";
CHECK(toString(result.errors[0]) == expected); CHECK(toString(result.errors[0]) == expected);
} }
else if (FFlag::LuauSolverV2)
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK(
"Type '{number}' could not be converted into '{ [string]: number }'; at indexer(), number is not exactly string" ==
toString(result.errors[0])
);
}
else else
{ {
LUAU_REQUIRE_ERROR_COUNT(3, result); LUAU_REQUIRE_ERROR_COUNT(3, result);
@ -3824,6 +3748,8 @@ TEST_CASE_FIXTURE(Fixture, "scalar_is_a_subtype_of_a_compatible_polymorphic_shap
TEST_CASE_FIXTURE(Fixture, "scalar_is_not_a_subtype_of_a_compatible_polymorphic_shape_type") TEST_CASE_FIXTURE(Fixture, "scalar_is_not_a_subtype_of_a_compatible_polymorphic_shape_type")
{ {
ScopedFastFlag sffs[] = {{FFlag::LuauNonReentrantGeneralization2, true}, {FFlag::LuauReportSubtypingErrors, true}};
CheckResult result = check(R"( CheckResult result = check(R"(
local function f(s) local function f(s)
return s:absolutely_no_scalar_has_this_method() return s:absolutely_no_scalar_has_this_method()
@ -3861,7 +3787,7 @@ TEST_CASE_FIXTURE(Fixture, "scalar_is_not_a_subtype_of_a_compatible_polymorphic_
CHECK("typeof(string)" == toString(tm4->givenType)); CHECK("typeof(string)" == toString(tm4->givenType));
CHECK("t1 where t1 = { read absolutely_no_scalar_has_this_method: (t1) -> (a...) }" == toString(tm4->wantedType)); CHECK("t1 where t1 = { read absolutely_no_scalar_has_this_method: (t1) -> (a...) }" == toString(tm4->wantedType));
} }
else if (FFlag::LuauImproveTypePathsInErrors) else
{ {
LUAU_REQUIRE_ERROR_COUNT(3, result); LUAU_REQUIRE_ERROR_COUNT(3, result);
@ -3886,36 +3812,6 @@ could not be converted into
caused by: caused by:
Not all union options are compatible. Not all union options are compatible.
Type '"bar"' could not be converted into 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' Type '"bar"' could not be converted into 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}'
caused by:
The former's metatable does not satisfy the requirements.
Table type 'typeof(string)' not compatible with type 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' because the former is missing field 'absolutely_no_scalar_has_this_method')";
CHECK_EQ(expected3, toString(result.errors[2]));
}
else
{
LUAU_REQUIRE_ERROR_COUNT(3, result);
const std::string expected1 =
R"(Type 'string' could not be converted into 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}'
caused by:
The former's metatable does not satisfy the requirements.
Table type 'typeof(string)' not compatible with type 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' because the former is missing field 'absolutely_no_scalar_has_this_method')";
CHECK_EQ(expected1, toString(result.errors[0]));
const std::string expected2 =
R"(Type '"bar"' could not be converted into 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}'
caused by:
The former's metatable does not satisfy the requirements.
Table type 'typeof(string)' not compatible with type 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' because the former is missing field 'absolutely_no_scalar_has_this_method')";
CHECK_EQ(expected2, toString(result.errors[1]));
const std::string expected3 = R"(Type
'"bar" | "baz"'
could not be converted into
't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}'
caused by:
Not all union options are compatible.
Type '"bar"' could not be converted into 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}'
caused by: caused by:
The former's metatable does not satisfy the requirements. The former's metatable does not satisfy the requirements.
Table type 'typeof(string)' not compatible with type 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' because the former is missing field 'absolutely_no_scalar_has_this_method')"; Table type 'typeof(string)' not compatible with type 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' because the former is missing field 'absolutely_no_scalar_has_this_method')";
@ -4467,25 +4363,13 @@ TEST_CASE_FIXTURE(Fixture, "identify_all_problematic_table_fields")
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
std::string expected =
if (FFlag::LuauImproveTypePathsInErrors) "Type '{ a: string, b: boolean, c: number }' could not be converted into 'T'; \n"
{ "this is because \n\t"
std::string expected = " * accessing `a` results in `string` in the former type and `number` in the latter type, and `string` is not exactly `number`\n\t"
"Type '{ a: string, b: boolean, c: number }' could not be converted into 'T'; \n" " * accessing `b` results in `boolean` in the former type and `string` in the latter type, and `boolean` is not exactly `string`\n\t"
"this is because \n\t" " * accessing `c` results in `number` in the former type and `boolean` in the latter type, and `number` is not exactly `boolean`";
" * accessing `a` results in `string` in the former type and `number` in the latter type, and `string` is not exactly `number`\n\t" CHECK(toString(result.errors[0]) == expected);
" * accessing `b` results in `boolean` in the former type and `string` in the latter type, and `boolean` is not exactly `string`\n\t"
" * accessing `c` results in `number` in the former type and `boolean` in the latter type, and `number` is not exactly `boolean`";
CHECK(toString(result.errors[0]) == expected);
}
else
{
std::string expected =
"Type '{ a: string, b: boolean, c: number }' could not be converted into 'T'; at [read \"a\"], string is not exactly number"
"\n\tat [read \"b\"], boolean is not exactly string"
"\n\tat [read \"c\"], number is not exactly boolean";
CHECK(toString(result.errors[0]) == expected);
}
} }
TEST_CASE_FIXTURE(Fixture, "read_and_write_only_table_properties_are_unsupported") TEST_CASE_FIXTURE(Fixture, "read_and_write_only_table_properties_are_unsupported")
@ -5125,29 +5009,17 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "subtyping_with_a_metatable_table_path")
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauImproveTypePathsInErrors) CHECK_EQ(
{ "Type pack '{ @metatable { }, { } & { } }' could not be converted into 'Class'; \n"
CHECK_EQ( "this is because \n\t"
"Type pack '{ @metatable { }, { } & { } }' could not be converted into 'Class'; \n" " * in the 1st entry in the type pack, the metatable portion is `{ }` in the former type and `nil` in the latter type, and `{ }` "
"this is because \n\t" "is not a subtype of `nil`\n\t"
" * in the 1st entry in the type pack, the metatable portion is `{ }` in the former type and `nil` in the latter type, and `{ }` " " * in the 1st entry in the type pack, the table portion has the 1st component of the intersection as `{ }` and in the 1st entry "
"is not a subtype of `nil`\n\t" "in the type pack, the table portion is `nil`, and `{ }` is not a subtype of `nil`\n\t"
" * in the 1st entry in the type pack, the table portion has the 1st component of the intersection as `{ }` and in the 1st entry " " * in the 1st entry in the type pack, the table portion has the 2nd component of the intersection as `{ }` and in the 1st entry "
"in the type pack, the table portion is `nil`, and `{ }` is not a subtype of `nil`\n\t" "in the type pack, the table portion is `nil`, and `{ }` is not a subtype of `nil`",
" * in the 1st entry in the type pack, the table portion has the 2nd component of the intersection as `{ }` and in the 1st entry " toString(result.errors[0])
"in the type pack, the table portion is `nil`, and `{ }` is not a subtype of `nil`", );
toString(result.errors[0])
);
}
else
{
CHECK_EQ(
"Type pack '{ @metatable { }, { } & { } }' could not be converted into 'Class'; at [0].metatable(), { } is not a subtype of nil\n"
"\ttype { @metatable { }, { } & { } }[0].table()[0] ({ }) is not a subtype of Class[0].table() (nil)\n"
"\ttype { @metatable { }, { } & { } }[0].table()[1] ({ }) is not a subtype of Class[0].table() (nil)",
toString(result.errors[0])
);
}
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "metatable_union_type") TEST_CASE_FIXTURE(BuiltinsFixture, "metatable_union_type")
@ -5178,10 +5050,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "metatable_union_type")
TEST_CASE_FIXTURE(Fixture, "function_check_constraint_too_eager") TEST_CASE_FIXTURE(Fixture, "function_check_constraint_too_eager")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag _{FFlag::LuauSolverV2, true};
{FFlag::LuauSolverV2, true},
{FFlag::LuauBidirectionalInferenceCollectIndexerTypes, true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
local function doTheThing(_: { [string]: unknown }) end local function doTheThing(_: { [string]: unknown }) end
@ -5221,10 +5090,7 @@ TEST_CASE_FIXTURE(Fixture, "function_check_constraint_too_eager")
TEST_CASE_FIXTURE(BuiltinsFixture, "magic_functions_bidirectionally_inferred") TEST_CASE_FIXTURE(BuiltinsFixture, "magic_functions_bidirectionally_inferred")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag _{FFlag::LuauSolverV2, true};
{FFlag::LuauSolverV2, true},
{FFlag::LuauBidirectionalInferenceCollectIndexerTypes, true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
local function getStuff(): (string, number, string) local function getStuff(): (string, number, string)
@ -5484,10 +5350,7 @@ TEST_CASE_FIXTURE(Fixture, "oss_1543_optional_generic_param")
TEST_CASE_FIXTURE(Fixture, "missing_fields_bidirectional_inference") TEST_CASE_FIXTURE(Fixture, "missing_fields_bidirectional_inference")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag _{FFlag::LuauSolverV2, true};
{FFlag::LuauSolverV2, true},
{FFlag::LuauBidirectionalInferenceCollectIndexerTypes, true},
};
auto result = check(R"( auto result = check(R"(
type Book = { title: string, author: string } type Book = { title: string, author: string }
@ -5515,10 +5378,7 @@ TEST_CASE_FIXTURE(Fixture, "missing_fields_bidirectional_inference")
TEST_CASE_FIXTURE(Fixture, "generic_index_syntax_bidirectional_infer_with_tables") TEST_CASE_FIXTURE(Fixture, "generic_index_syntax_bidirectional_infer_with_tables")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag _{FFlag::LuauSolverV2, true};
{FFlag::LuauSolverV2, true},
{FFlag::LuauBidirectionalInferenceCollectIndexerTypes, true},
};
auto result = check((R"( auto result = check((R"(
local function getStatus(): string local function getStatus(): string
@ -5572,7 +5432,6 @@ TEST_CASE_FIXTURE(Fixture, "bigger_nested_table_causes_big_type_error")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true}, {FFlag::LuauSolverV2, true},
{FFlag::LuauImproveTypePathsInErrors, true},
}; };
auto result = check(R"( auto result = check(R"(
@ -5606,22 +5465,22 @@ TEST_CASE_FIXTURE(Fixture, "bigger_nested_table_causes_big_type_error")
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
std::string expected = "Type\n\t" std::string expected =
"'{Dir | File | { children: ({Dir | File | { content: string?, path: string, type: \"file\" }} | {Dir | File})?, name: " "Type\n\t"
"string, type: \"dir\" }}'" "'{Dir | File | { children: ({Dir | File | { content: string?, path: string, type: \"file\" }} | {Dir | File})?, name: "
"\ncould not be converted into\n\t" "string, type: \"dir\" }}'"
"'DirectoryChildren'; \n" "\ncould not be converted into\n\t"
"this is because in the result of indexing has the 3rd component of the union as `{ children: ({Dir | File | { content: " "'DirectoryChildren'; \n"
"string?, path: string, type: \"file\" }} | {Dir | File})?, name: string, type: \"dir\" }` and the result of indexing is " "this is because in the result of indexing has the 3rd component of the union as `{ children: ({Dir | File | { content: "
"`Dir | File`, and `{ children: ({Dir | File | { content: string?, path: string, type: \"file\" }} | {Dir | File})?, " "string?, path: string, type: \"file\" }} | {Dir | File})?, name: string, type: \"dir\" }` and the result of indexing is "
"name: string, type: \"dir\" }` is not exactly `Dir | File`"; "`Dir | File`, and `{ children: ({Dir | File | { content: string?, path: string, type: \"file\" }} | {Dir | File})?, "
"name: string, type: \"dir\" }` is not exactly `Dir | File`";
CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(expected, toString(result.errors[0]));
} }
TEST_CASE_FIXTURE(Fixture, "unsafe_bidirectional_mutation") TEST_CASE_FIXTURE(Fixture, "unsafe_bidirectional_mutation")
{ {
ScopedFastFlag _{FFlag::LuauBidirectionalFailsafe, true};
// It's kind of suspect that we allow multiple definitions of keys in // It's kind of suspect that we allow multiple definitions of keys in
// a single table. // a single table.
LUAU_REQUIRE_NO_ERRORS(check(R"( LUAU_REQUIRE_NO_ERRORS(check(R"(
@ -5641,7 +5500,6 @@ TEST_CASE_FIXTURE(Fixture, "unsafe_bidirectional_mutation")
TEST_CASE_FIXTURE(BuiltinsFixture, "function_call_in_indexer_with_compound_assign") TEST_CASE_FIXTURE(BuiltinsFixture, "function_call_in_indexer_with_compound_assign")
{ {
ScopedFastFlag _{FFlag::LuauBidirectionalFailsafe, true};
// This has a bunch of errors, we really just need it to not crash / assert. // This has a bunch of errors, we really just need it to not crash / assert.
std::ignore = check(R"( std::ignore = check(R"(
--!strict --!strict
@ -5676,10 +5534,8 @@ TEST_CASE_FIXTURE(Fixture, "stop_refining_new_table_indices_for_non_primitive_ta
TEST_CASE_FIXTURE(Fixture, "fuzz_match_literal_type_crash_again") TEST_CASE_FIXTURE(Fixture, "fuzz_match_literal_type_crash_again")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag _{FFlag::LuauBidirectionalInferenceElideAssert, true};
{FFlag::LuauBidirectionalInferenceCollectIndexerTypes, true},
{FFlag::LuauBidirectionalInferenceElideAssert, true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
function f(_: { [string]: {unknown}} ) end function f(_: { [string]: {unknown}} ) end
f( f(
@ -5694,8 +5550,6 @@ TEST_CASE_FIXTURE(Fixture, "fuzz_match_literal_type_crash_again")
TEST_CASE_FIXTURE(Fixture, "type_mismatch_in_dict") TEST_CASE_FIXTURE(Fixture, "type_mismatch_in_dict")
{ {
ScopedFastFlag sff{FFlag::LuauBidirectionalInferenceCollectIndexerTypes, true};
CheckResult result = check(R"( CheckResult result = check(R"(
--!strict --!strict
local dict: {[string]: boolean} = { local dict: {[string]: boolean} = {

View file

@ -24,20 +24,19 @@ LUAU_FASTINT(LuauNormalizeCacheLimit)
LUAU_FASTINT(LuauRecursionLimit) LUAU_FASTINT(LuauRecursionLimit)
LUAU_FASTINT(LuauTypeInferTypePackLoopLimit) LUAU_FASTINT(LuauTypeInferTypePackLoopLimit)
LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauAstTypeGroup3)
LUAU_FASTFLAG(LuauNewNonStrictWarnOnUnknownGlobals) LUAU_FASTFLAG(LuauNewNonStrictWarnOnUnknownGlobals)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
LUAU_FASTFLAG(LuauTypeCheckerAcceptNumberConcats) LUAU_FASTFLAG(LuauTypeCheckerAcceptNumberConcats)
LUAU_FASTFLAG(LuauPreprocessTypestatedArgument) LUAU_FASTFLAG(LuauPreprocessTypestatedArgument)
LUAU_FASTFLAG(LuauCacheInferencePerAstExpr) LUAU_FASTFLAG(LuauCacheInferencePerAstExpr)
LUAU_FASTFLAG(LuauLimitIterationWhenCheckingArgumentCounts) LUAU_FASTFLAG(LuauMagicFreezeCheckBlocked2)
LUAU_FASTFLAG(LuauMagicFreezeCheckBlocked)
LUAU_FASTFLAG(LuauNonReentrantGeneralization2) LUAU_FASTFLAG(LuauNonReentrantGeneralization2)
LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect) LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect)
LUAU_FASTFLAG(LuauHasPropProperBlock) LUAU_FASTFLAG(LuauHasPropProperBlock)
LUAU_FASTFLAG(LuauStringPartLengthLimit) LUAU_FASTFLAG(LuauStringPartLengthLimit)
LUAU_FASTFLAG(LuauSimplificationRecheckAssumption) LUAU_FASTFLAG(LuauSimplificationRecheckAssumption)
LUAU_FASTFLAG(LuauAlwaysResolveAstTypes) LUAU_FASTFLAG(LuauAlwaysResolveAstTypes)
LUAU_FASTFLAG(LuauReportSubtypingErrors)
LUAU_FASTFLAG(LuauAvoidDoubleNegation)
using namespace Luau; using namespace Luau;
@ -1144,8 +1143,12 @@ TEST_CASE_FIXTURE(Fixture, "cli_50041_committing_txnlog_in_apollo_client_error")
end end
)"); )");
if (FFlag::LuauInstantiateInSubtyping && FFlag::LuauImproveTypePathsInErrors) if (FFlag::LuauInstantiateInSubtyping)
{ {
// though this didn't error before the flag, it seems as though it should error since fields of a table are invariant.
// the user's intent would likely be that these "method" fields would be read-only, but without an annotation, accepting this should be
// unsound.
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = const std::string expected =
"Type 'Policies' from 'MainModule' could not be converted into 'Policies' from 'MainModule'" "Type 'Policies' from 'MainModule' could not be converted into 'Policies' from 'MainModule'"
@ -1166,31 +1169,6 @@ TEST_CASE_FIXTURE(Fixture, "cli_50041_committing_txnlog_in_apollo_client_error")
"Table type 'FieldSpecifier' not compatible with type '{| from: number? |}' because the former has extra field 'fieldName'"; "Table type 'FieldSpecifier' not compatible with type '{| from: number? |}' because the former has extra field 'fieldName'";
CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(expected, toString(result.errors[0]));
} }
else if (FFlag::LuauInstantiateInSubtyping)
{
// though this didn't error before the flag, it seems as though it should error since fields of a table are invariant.
// the user's intent would likely be that these "method" fields would be read-only, but without an annotation, accepting this should be
// unsound.
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = R"(Type 'Policies' from 'MainModule' could not be converted into 'Policies' from 'MainModule'
caused by:
Property 'getStoreFieldName' is not compatible.
Type
'(Policies, FieldSpecifier & {| from: number? |}) -> (a, b...)'
could not be converted into
'(Policies, FieldSpecifier) -> string'
caused by:
Argument #2 type is not compatible.
Type
'FieldSpecifier'
could not be converted into
'FieldSpecifier & {| from: number? |}'
caused by:
Not all intersection parts are compatible.
Table type 'FieldSpecifier' not compatible with type '{| from: number? |}' because the former has extra field 'fieldName')";
CHECK_EQ(expected, toString(result.errors[0]));
}
else else
{ {
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
@ -1235,19 +1213,27 @@ TEST_CASE_FIXTURE(Fixture, "type_infer_recursion_limit_normalizer")
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
{ {
CHECK(3 == result.errors.size()); if (FFlag::LuauReportSubtypingErrors)
if (FFlag::LuauAstTypeGroup3) {
CHECK(4 == result.errors.size());
CHECK(Location{{2, 22}, {2, 42}} == result.errors[0].location); CHECK(Location{{2, 22}, {2, 42}} == result.errors[0].location);
CHECK(Location{{3, 22}, {3, 42}} == result.errors[1].location);
CHECK(Location{{3, 45}, {3, 46}} == result.errors[2].location);
CHECK(Location{{3, 22}, {3, 41}} == result.errors[3].location);
for (const TypeError& e : result.errors)
CHECK_EQ("Code is too complex to typecheck! Consider simplifying the code around this area", toString(e));
}
else else
CHECK(Location{{2, 22}, {2, 41}} == result.errors[0].location); {
CHECK(Location{{3, 22}, {3, 42}} == result.errors[1].location); CHECK(3 == result.errors.size());
if (FFlag::LuauAstTypeGroup3) CHECK(Location{{2, 22}, {2, 42}} == result.errors[0].location);
CHECK(Location{{3, 22}, {3, 42}} == result.errors[1].location);
CHECK(Location{{3, 22}, {3, 41}} == result.errors[2].location); CHECK(Location{{3, 22}, {3, 41}} == result.errors[2].location);
else CHECK_EQ("Code is too complex to typecheck! Consider simplifying the code around this area", toString(result.errors[0]));
CHECK(Location{{3, 23}, {3, 40}} == result.errors[2].location); CHECK_EQ("Code is too complex to typecheck! Consider simplifying the code around this area", toString(result.errors[1]));
CHECK_EQ("Code is too complex to typecheck! Consider simplifying the code around this area", toString(result.errors[0])); CHECK_EQ("Code is too complex to typecheck! Consider simplifying the code around this area", toString(result.errors[2]));
CHECK_EQ("Code is too complex to typecheck! Consider simplifying the code around this area", toString(result.errors[1])); }
CHECK_EQ("Code is too complex to typecheck! Consider simplifying the code around this area", toString(result.errors[2]));
} }
else else
{ {
@ -1994,7 +1980,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "assert_table_freeze_constraint_solving")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true}, {FFlag::LuauSolverV2, true},
{FFlag::LuauMagicFreezeCheckBlocked, true} {FFlag::LuauMagicFreezeCheckBlocked2, true}
}; };
LUAU_REQUIRE_NO_ERRORS(check(R"( LUAU_REQUIRE_NO_ERRORS(check(R"(
local f = table.freeze local f = table.freeze
@ -2006,7 +1992,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_assert_table_freeze_constraint_solving"
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true}, {FFlag::LuauSolverV2, true},
{FFlag::LuauMagicFreezeCheckBlocked, true} {FFlag::LuauMagicFreezeCheckBlocked2, true}
}; };
// This is the original fuzzer version of the above issue. // This is the original fuzzer version of the above issue.
CheckResult results = check(R"( CheckResult results = check(R"(
@ -2029,7 +2015,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "cyclic_unification_aborts_eventually" * doct
ScopedFastFlag sffs[] = { ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, false}, {FFlag::LuauSolverV2, false},
{FFlag::LuauInstantiateInSubtyping, true}, {FFlag::LuauInstantiateInSubtyping, true},
{FFlag::LuauLimitIterationWhenCheckingArgumentCounts, true},
}; };
ScopedFastInt sfi{FInt::LuauTypeInferTypePackLoopLimit, 100}; ScopedFastInt sfi{FInt::LuauTypeInferTypePackLoopLimit, 100};
@ -2131,4 +2116,55 @@ local _
)")); )"));
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_missing_follow_table_freeze")
{
ScopedFastFlag _{FFlag::LuauMagicFreezeCheckBlocked2, true};
LUAU_REQUIRE_ERRORS(check(R"(
if _:freeze(_)[_][_] then
else
do end
end
if _:freeze((nil))[_][_] then
else
do end
end
_ = table,true,_(lower)
do end
_:freeze()[_] += {} > _
)"));
}
TEST_CASE_FIXTURE(BuiltinsFixture, "fuzzer_avoid_double_negation" * doctest::timeout(0.5))
{
ScopedFastFlag _{FFlag::LuauAvoidDoubleNegation, true};
// We don't care about errors, only that we don't OOM during typechecking.
LUAU_REQUIRE_ERRORS(check(R"(
local _ = _
repeat
do end
while 0 do
do
_ = _[0]
_._ *= _
end
if _ then
elseif "" then
end
_ = _[0]
_ = ""
end
_ = ""
until _
while false do
do
_ = _[0]
do end
end
_ = ""
return if _ then _,_
end
)"));
}
TEST_SUITE_END(); TEST_SUITE_END();

View file

@ -12,8 +12,9 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization) LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAG(LuauReportSubtypingErrors)
LUAU_FASTFLAG(LuauTrackInferredFunctionTypeFromCall)
TEST_SUITE_BEGIN("TypePackTests"); TEST_SUITE_BEGIN("TypePackTests");
@ -957,43 +958,25 @@ a = b
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) if (FFlag::LuauSolverV2)
{ {
const std::string expected = "Type\n\t" const std::string expected =
"'() -> (number, ...boolean)'" "Type\n\t"
"\ncould not be converted into\n\t" "'() -> (number, ...boolean)'"
"'() -> (number, ...string)'; \n" "\ncould not be converted into\n\t"
"this is because it returns a tail of the variadic `boolean` in the former type and `string` in the latter " "'() -> (number, ...string)'; \n"
"type, and `boolean` is not a subtype of `string`"; "this is because it returns a tail of the variadic `boolean` in the former type and `string` in the latter "
"type, and `boolean` is not a subtype of `string`";
CHECK(expected == toString(result.errors[0])); CHECK(expected == toString(result.errors[0]));
} }
else if (FFlag::LuauSolverV2) else
{
const std::string expected = "Type\n"
" '() -> (number, ...boolean)'\n"
"could not be converted into\n"
" '() -> (number, ...string)'; at returns().tail().variadic(), boolean is not a subtype of string";
CHECK(expected == toString(result.errors[0]));
}
else if (FFlag::LuauImproveTypePathsInErrors)
{ {
const std::string expected = R"(Type const std::string expected = R"(Type
'() -> (number, ...boolean)' '() -> (number, ...boolean)'
could not be converted into could not be converted into
'() -> (number, ...string)' '() -> (number, ...string)'
caused by:
Type 'boolean' could not be converted into 'string')";
CHECK_EQ(expected, toString(result.errors[0]));
}
else
{
const std::string expected = R"(Type
'() -> (number, ...boolean)'
could not be converted into
'() -> (number, ...string)'
caused by: caused by:
Type 'boolean' could not be converted into 'string')"; Type 'boolean' could not be converted into 'string')";
CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(expected, toString(result.errors[0]));
@ -1077,6 +1060,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "detect_cyclic_typepacks")
TEST_CASE_FIXTURE(BuiltinsFixture, "detect_cyclic_typepacks2") TEST_CASE_FIXTURE(BuiltinsFixture, "detect_cyclic_typepacks2")
{ {
ScopedFastFlag sffs[] = {{FFlag::LuauReportSubtypingErrors, true}, {FFlag::LuauTrackInferredFunctionTypeFromCall, true}};
CheckResult result = check(R"( CheckResult result = check(R"(
function _(l0:((typeof((pcall)))|((((t0)->())|(typeof(-67108864)))|(any)))|(any),...):(((typeof(0))|(any))|(any),typeof(-67108864),any) function _(l0:((typeof((pcall)))|((((t0)->())|(typeof(-67108864)))|(any)))|(any),...):(((typeof(0))|(any))|(any),typeof(-67108864),any)
xpcall(_,_,_) xpcall(_,_,_)
@ -1121,16 +1106,11 @@ TEST_CASE_FIXTURE(Fixture, "unify_variadic_tails_in_arguments_free")
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) if (FFlag::LuauSolverV2)
CHECK( CHECK(
toString(result.errors.at(0)) == "Type pack '...number' could not be converted into 'boolean'; \nthis is because it has a tail of " toString(result.errors.at(0)) == "Type pack '...number' could not be converted into 'boolean'; \nthis is because it has a tail of "
"`...number`, which is not a subtype of `boolean`" "`...number`, which is not a subtype of `boolean`"
); );
else if (FFlag::LuauSolverV2)
CHECK(
toString(result.errors.at(0)) ==
"Type pack '...number' could not be converted into 'boolean'; type ...number.tail() (...number) is not a subtype of boolean (boolean)"
);
else else
CHECK_EQ(toString(result.errors[0]), "Type 'number' could not be converted into 'boolean'"); CHECK_EQ(toString(result.errors[0]), "Type 'number' could not be converted into 'boolean'");
} }

View file

@ -7,6 +7,8 @@ LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauRefineWaitForBlockedTypesInTarget) LUAU_FASTFLAG(LuauRefineWaitForBlockedTypesInTarget)
LUAU_FASTFLAG(LuauDoNotAddUpvalueTypesToLocalType) LUAU_FASTFLAG(LuauDoNotAddUpvalueTypesToLocalType)
LUAU_FASTFLAG(LuauDfgIfBlocksShouldRespectControlFlow) LUAU_FASTFLAG(LuauDfgIfBlocksShouldRespectControlFlow)
LUAU_FASTFLAG(LuauReportSubtypingErrors)
LUAU_FASTFLAG(LuauNonReentrantGeneralization2)
using namespace Luau; using namespace Luau;
@ -407,9 +409,7 @@ TEST_CASE_FIXTURE(TypeStateFixture, "prototyped_recursive_functions")
TEST_CASE_FIXTURE(BuiltinsFixture, "prototyped_recursive_functions_but_has_future_assignments") TEST_CASE_FIXTURE(BuiltinsFixture, "prototyped_recursive_functions_but_has_future_assignments")
{ {
// early return if the flag isn't set since this is blocking gated commits ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauReportSubtypingErrors, true}, {FFlag::LuauNonReentrantGeneralization2, true}};
if (!FFlag::LuauSolverV2)
return;
CheckResult result = check(R"( CheckResult result = check(R"(
local f local f

View file

@ -11,7 +11,6 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization) LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
TEST_SUITE_BEGIN("UnionTypes"); TEST_SUITE_BEGIN("UnionTypes");
@ -541,7 +540,7 @@ end
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) if (FFlag::LuauSolverV2)
{ {
CHECK_EQ( CHECK_EQ(
@ -553,16 +552,6 @@ end
" * the 3rd component of the union is `Z`, which is not a subtype of `{ w: number }`" " * the 3rd component of the union is `Z`, which is not a subtype of `{ w: number }`"
); );
} }
else if (FFlag::LuauSolverV2)
{
CHECK_EQ(
toString(result.errors[0]),
"Type 'X | Y | Z' could not be converted into '{ w: number }'; type X | Y | Z[0] (X) is not a subtype "
"of { w: number } ({ w: number })\n\t"
"type X | Y | Z[1] (Y) is not a subtype of { w: number } ({ w: number })\n\t"
"type X | Y | Z[2] (Z) is not a subtype of { w: number } ({ w: number })"
);
}
else else
{ {
CHECK_EQ(toString(result.errors[0]), R"(Type 'X | Y | Z' could not be converted into '{| w: number |}' CHECK_EQ(toString(result.errors[0]), R"(Type 'X | Y | Z' could not be converted into '{| w: number |}'
@ -683,22 +672,12 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_union_write_indirect")
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
// NOTE: union normalization will improve this message // NOTE: union normalization will improve this message
if (FFlag::LuauImproveTypePathsInErrors) const std::string expected =
{ "Type\n\t"
const std::string expected = "Type\n\t" "'(string) -> number'"
"'(string) -> number'" "\ncould not be converted into\n\t"
"\ncould not be converted into\n\t" "'((number) -> string) | ((number) -> string)'; none of the union options are compatible";
"'((number) -> string) | ((number) -> string)'; none of the union options are compatible"; CHECK_EQ(expected, toString(result.errors[0]));
CHECK_EQ(expected, toString(result.errors[0]));
}
else
{
const std::string expected = R"(Type
'(string) -> number'
could not be converted into
'((number) -> string) | ((number) -> string)'; none of the union options are compatible)";
CHECK_EQ(expected, toString(result.errors[0]));
}
} }
TEST_CASE_FIXTURE(Fixture, "union_true_and_false") TEST_CASE_FIXTURE(Fixture, "union_true_and_false")
@ -785,22 +764,12 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_mentioning_generic_typepacks")
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauImproveTypePathsInErrors) const std::string expected =
{ "Type\n\t"
const std::string expected = "Type\n\t" "'(number, a...) -> (number?, a...)'"
"'(number, a...) -> (number?, a...)'" "\ncould not be converted into\n\t"
"\ncould not be converted into\n\t" "'((number) -> number) | ((number?, a...) -> (number?, a...))'; none of the union options are compatible";
"'((number) -> number) | ((number?, a...) -> (number?, a...))'; none of the union options are compatible"; CHECK_EQ(expected, toString(result.errors[0]));
CHECK_EQ(expected, toString(result.errors[0]));
}
else
{
const std::string expected = R"(Type
'(number, a...) -> (number?, a...)'
could not be converted into
'((number) -> number) | ((number?, a...) -> (number?, a...))'; none of the union options are compatible)";
CHECK_EQ(expected, toString(result.errors[0]));
}
} }
TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_arities") TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_arities")
@ -816,22 +785,12 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_arities")
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauImproveTypePathsInErrors) const std::string expected =
{ "Type\n\t"
const std::string expected = "Type\n\t" "'(number) -> number?'"
"'(number) -> number?'" "\ncould not be converted into\n\t"
"\ncould not be converted into\n\t" "'((number) -> nil) | ((number, string?) -> number)'; none of the union options are compatible";
"'((number) -> nil) | ((number, string?) -> number)'; none of the union options are compatible"; CHECK_EQ(expected, toString(result.errors[0]));
CHECK_EQ(expected, toString(result.errors[0]));
}
else
{
const std::string expected = R"(Type
'(number) -> number?'
could not be converted into
'((number) -> nil) | ((number, string?) -> number)'; none of the union options are compatible)";
CHECK_EQ(expected, toString(result.errors[0]));
}
} }
TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_arities") TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_arities")
@ -847,22 +806,12 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_arities")
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauImproveTypePathsInErrors) const std::string expected =
{ "Type\n\t"
const std::string expected = "Type\n\t" "'() -> number | string'"
"'() -> number | string'" "\ncould not be converted into\n\t"
"\ncould not be converted into\n\t" "'(() -> (string, string)) | (() -> number)'; none of the union options are compatible";
"'(() -> (string, string)) | (() -> number)'; none of the union options are compatible"; CHECK_EQ(expected, toString(result.errors[0]));
CHECK_EQ(expected, toString(result.errors[0]));
}
else
{
const std::string expected = R"(Type
'() -> number | string'
could not be converted into
'(() -> (string, string)) | (() -> number)'; none of the union options are compatible)";
CHECK_EQ(expected, toString(result.errors[0]));
}
} }
TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_variadics") TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_variadics")
@ -878,22 +827,12 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_variadics")
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauImproveTypePathsInErrors) const std::string expected =
{ "Type\n\t"
const std::string expected = "Type\n\t" "'(...nil) -> (...number?)'"
"'(...nil) -> (...number?)'" "\ncould not be converted into\n\t"
"\ncould not be converted into\n\t" "'((...string?) -> (...number)) | ((...string?) -> nil)'; none of the union options are compatible";
"'((...string?) -> (...number)) | ((...string?) -> nil)'; none of the union options are compatible"; CHECK_EQ(expected, toString(result.errors[0]));
CHECK_EQ(expected, toString(result.errors[0]));
}
else
{
const std::string expected = R"(Type
'(...nil) -> (...number?)'
could not be converted into
'((...string?) -> (...number)) | ((...string?) -> nil)'; none of the union options are compatible)";
CHECK_EQ(expected, toString(result.errors[0]));
}
} }
TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_variadics") TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_variadics")
@ -906,22 +845,16 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_variadics")
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) if (FFlag::LuauSolverV2)
{ {
const std::string expected = "Type\n\t" const std::string expected =
"'(number) -> ()'" "Type\n\t"
"\ncould not be converted into\n\t" "'(number) -> ()'"
"'((...number?) -> ()) | ((number?) -> ())'"; "\ncould not be converted into\n\t"
"'((...number?) -> ()) | ((number?) -> ())'";
CHECK(expected == toString(result.errors[0])); CHECK(expected == toString(result.errors[0]));
} }
else if (FFlag::LuauSolverV2) else
{
CHECK(R"(Type
'(number) -> ()'
could not be converted into
'((...number?) -> ()) | ((number?) -> ())')" == toString(result.errors[0]));
}
else if (FFlag::LuauImproveTypePathsInErrors)
{ {
const std::string expected = R"(Type const std::string expected = R"(Type
'(number) -> ()' '(number) -> ()'
@ -929,14 +862,6 @@ could not be converted into
'((...number?) -> ()) | ((number?) -> ())'; none of the union options are compatible)"; '((...number?) -> ()) | ((number?) -> ())'; none of the union options are compatible)";
CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(expected, toString(result.errors[0]));
} }
else
{
const std::string expected = R"(Type
'(number) -> ()'
could not be converted into
'((...number?) -> ()) | ((number?) -> ())'; none of the union options are compatible)";
CHECK_EQ(expected, toString(result.errors[0]));
}
} }
TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_variadics") TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_variadics")
@ -952,22 +877,12 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_variadics
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauImproveTypePathsInErrors) const std::string expected =
{ "Type\n\t"
const std::string expected = "Type\n\t" "'() -> (number?, ...number)'"
"'() -> (number?, ...number)'" "\ncould not be converted into\n\t"
"\ncould not be converted into\n\t" "'(() -> (...number)) | (() -> number)'; none of the union options are compatible";
"'(() -> (...number)) | (() -> number)'; none of the union options are compatible"; CHECK_EQ(expected, toString(result.errors[0]));
CHECK_EQ(expected, toString(result.errors[0]));
}
else
{
const std::string expected = R"(Type
'() -> (number?, ...number)'
could not be converted into
'(() -> (...number)) | (() -> number)'; none of the union options are compatible)";
CHECK_EQ(expected, toString(result.errors[0]));
}
} }
TEST_CASE_FIXTURE(Fixture, "less_greedy_unification_with_union_types") TEST_CASE_FIXTURE(Fixture, "less_greedy_unification_with_union_types")

View file

@ -0,0 +1,3 @@
local result = proxyrequire("./dependency", "@"..debug.info(1, "s"))
result[#result+1] = "required into proxy_requirer"
return result