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;
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

View file

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

View file

@ -33,7 +33,7 @@ LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauNonReentrantGeneralization2)
LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3)
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunTypecheck)
LUAU_FASTFLAGVARIABLE(LuauMagicFreezeCheckBlocked)
LUAU_FASTFLAGVARIABLE(LuauMagicFreezeCheckBlocked2)
LUAU_FASTFLAGVARIABLE(LuauFormatUseLastPosition)
namespace Luau
@ -1616,11 +1616,11 @@ bool MagicFreeze::infer(const MagicFunctionCallContext& context)
std::optional<DefId> resultDef = dfg->getDefOptional(targetExpr);
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
// regular inference.
return false;
@ -1629,6 +1629,8 @@ bool MagicFreeze::infer(const MagicFunctionCallContext& 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 (resultTy)

View file

@ -14,7 +14,6 @@ LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTINTVARIABLE(LuauTypeCloneIterationLimit, 100'000)
LUAU_FASTFLAGVARIABLE(LuauClonedTableAndFunctionTypesMustHaveScopes)
LUAU_FASTFLAGVARIABLE(LuauDoNotClonePersistentBindings)
LUAU_FASTFLAG(LuauIncrementalAutocompleteDemandBasedCloning)
namespace Luau
{
@ -549,11 +548,6 @@ public:
void cloneChildren(LazyType* t) override
{
// 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_FASTFLAGVARIABLE(LuauRetainDefinitionAliasLocations)
LUAU_FASTFLAG(LuauDeprecatedAttribute)
LUAU_FASTFLAGVARIABLE(LuauCacheInferencePerAstExpr)
LUAU_FASTFLAGVARIABLE(LuauAlwaysResolveAstTypes)
LUAU_FASTFLAGVARIABLE(LuauWeakNilRefinementType)
@ -52,6 +51,7 @@ LUAU_FASTFLAG(LuauGlobalVariableModuleIsolation)
LUAU_FASTFLAGVARIABLE(LuauNoTypeFunctionsNamedTypeOf)
LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions)
LUAU_FASTFLAG(LuauDoNotAddUpvalueTypesToLocalType)
LUAU_FASTFLAGVARIABLE(LuauAvoidDoubleNegation)
namespace Luau
{
@ -288,7 +288,9 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block)
GeneralizationConstraint{
result,
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 (!sense)
{
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)
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)
{
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
if (GeneralizationConstraint* genConstraint = c.get_if<GeneralizationConstraint>())
{
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)
{
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
FunctionType* fty = getMutable<FunctionType>(signature);
LUAU_ASSERT(fty);
fty->isDeprecatedFunction = func->hasAttribute(AstAttr::Type::Deprecated);
@ -1429,7 +1439,6 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocalFuncti
std::unique_ptr<Constraint> c =
std::make_unique<Constraint>(constraintScope, function->name->location, GeneralizationConstraint{functionType, sig.signature});
if (FFlag::LuauDeprecatedAttribute)
propagateDeprecatedAttributeToConstraint(c->c, function->func);
Constraint* previous = nullptr;
@ -1457,7 +1466,6 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocalFuncti
else
{
module->astTypes[function->func] = sig.signature;
if (FFlag::LuauDeprecatedAttribute)
propagateDeprecatedAttributeToType(sig.signature, function->func);
}
@ -1502,7 +1510,6 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f
if (sigFullyDefined)
{
emplaceType<BoundType>(asMutable(generalizedType), sig.signature);
if (FFlag::LuauDeprecatedAttribute)
propagateDeprecatedAttributeToType(sig.signature, function->func);
}
else
@ -1512,7 +1519,6 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f
NotNull<Constraint> c = addConstraint(constraintScope, function->name->location, GeneralizationConstraint{generalizedType, sig.signature});
getMutable<BlockedType>(generalizedType)->setOwner(c);
if (FFlag::LuauDeprecatedAttribute)
propagateDeprecatedAttributeToConstraint(c->c, function->func);
Constraint* previous = nullptr;
@ -2054,7 +2060,6 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareFunc
FunctionType* ftv = getMutable<FunctionType>(fnType);
ftv->isCheckedFunction = global->isCheckedFunction();
if (FFlag::LuauDeprecatedAttribute)
ftv->isDeprecatedFunction = global->hasAttribute(AstAttr::Type::Deprecated);
ftv->argNames.reserve(global->paramNames.size);
@ -3710,7 +3715,6 @@ TypeId ConstraintGenerator::resolveFunctionType(
// how to quantify/instantiate it.
FunctionType ftv{TypeLevel{}, {}, {}, argTypes, returnTypes};
ftv.isCheckedFunction = fn->isCheckedFunction();
if (FFlag::LuauDeprecatedAttribute)
ftv.isDeprecatedFunction = fn->hasAttribute(AstAttr::Type::Deprecated);
// This replicates the behavior of the appropriate FunctionType

View file

@ -36,10 +36,10 @@ LUAU_FASTFLAGVARIABLE(LuauHasPropProperBlock)
LUAU_FASTFLAGVARIABLE(DebugLuauGreedyGeneralization)
LUAU_FASTFLAG(LuauDeprecatedAttribute)
LUAU_FASTFLAG(LuauNonReentrantGeneralization2)
LUAU_FASTFLAG(LuauBidirectionalInferenceCollectIndexerTypes)
LUAU_FASTFLAG(LuauNewTypeFunReductionChecks2)
LUAU_FASTFLAGVARIABLE(LuauTrackInferredFunctionTypeFromCall)
LUAU_FASTFLAGVARIABLE(LuauAddCallConstraintForIterableFunctions)
LUAU_FASTFLAGVARIABLE(LuauGuardAgainstMalformedTypeAliasExpansion)
namespace Luau
{
@ -870,15 +870,12 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
else
unify(constraint, generalizedType, *generalizedTy);
if (FFlag::LuauDeprecatedAttribute)
{
if (FunctionType* fty = getMutable<FunctionType>(follow(generalizedType)))
{
if (c.hasDeprecatedAttribute)
fty->isDeprecatedFunction = true;
}
}
}
else
{
reportError(CodeTooComplex{}, constraint->location);
@ -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;
}
@ -1212,8 +1226,20 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
// instantiate the alias again, since the instantiation should be
// deterministic.
if (TypeId* cached = instantiatedAliases.find(signature))
{
// 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;
}
@ -1633,47 +1659,15 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<con
for (auto generic : ftv->generics)
{
replacements[generic] = builtinTypes->unknownType;
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes)
containsGenerics.generics.insert(generic);
}
for (auto genericPack : ftv->genericPacks)
{
replacementPacks[genericPack] = builtinTypes->unknownTypePack;
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes)
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> argPackHead = flatten(argsPack).first;
@ -1690,7 +1684,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<con
(*c.astExpectedTypes)[expr] = expectedArgTy;
// Generic types are skipped over entirely, for now.
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes && containsGenerics.hasGeneric(expectedArgTy))
if (containsGenerics.hasGeneric(expectedArgTy))
continue;
const FunctionType* expectedLambdaTy = get<FunctionType>(expectedArgTy);
@ -1857,7 +1851,7 @@ bool ConstraintSolver::tryDispatchHasIndexer(
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)
{
@ -1874,7 +1868,19 @@ bool ConstraintSolver::tryDispatchHasIndexer(
TypeId upperBound =
arena->addType(TableType{/* props */ {}, TableIndexer{indexType, resultType}, TypeLevel{}, ft->scope, TableState::Unsealed});
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;
}

View file

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

View file

@ -33,7 +33,6 @@ LUAU_FASTFLAGVARIABLE(LuauBetterCursorInCommentDetection)
LUAU_FASTFLAGVARIABLE(LuauAllFreeTypesHaveScopes)
LUAU_FASTFLAGVARIABLE(LuauPersistConstraintGenerationScopes)
LUAU_FASTFLAGVARIABLE(LuauCloneTypeAliasBindings)
LUAU_FASTFLAGVARIABLE(LuauIncrementalAutocompleteDemandBasedCloning)
LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
LUAU_FASTFLAGVARIABLE(LuauFragmentNoTypeFunEval)
LUAU_FASTFLAGVARIABLE(LuauBetterScopeSelection)
@ -41,51 +40,11 @@ LUAU_FASTFLAGVARIABLE(LuauBlockDiffFragmentSelection)
LUAU_FASTFLAGVARIABLE(LuauFragmentAcMemoryLeak)
LUAU_FASTFLAGVARIABLE(LuauGlobalVariableModuleIsolation)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
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
LUAU_FASTFLAGVARIABLE(LuauFragmentAutocompleteIfRecommendations)
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);
// when typing a function partially, get the span of the first line
@ -141,6 +100,29 @@ Location getAstStatForExtents(AstStatFor* forStat)
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 empty{cursorPosition, cursorPosition};
@ -199,7 +181,41 @@ Location getFragmentLocation(AstStat* nearestStatement, const Position& cursorPo
else
return empty;
}
if (FFlag::LuauFragmentAutocompleteIfRecommendations)
{
if (auto ifS = getNearestIfToCursor(nearestStatement, cursorPosition))
{
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
// 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 Location{ifS->condition->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};
@ -211,7 +227,6 @@ Location getFragmentLocation(AstStat* nearestStatement, const Position& cursorPo
{
if (auto elseIf = ifS->elsebody->as<AstStatIf>())
{
if (elseIf->thenbody->hasEnd)
return empty;
else
@ -220,6 +235,7 @@ Location getFragmentLocation(AstStat* nearestStatement, const Position& cursorPo
return empty;
}
}
}
return nonEmpty;
}
@ -504,9 +520,31 @@ std::optional<FragmentParseResult> parseFragment(
if (p.root == nullptr)
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);
// 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)
nearestStatement = p.root;
fragmentResult.root = p.root;
@ -718,179 +756,6 @@ void cloneTypesFromFragment(
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)
{
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)
{
endOffset = docOffset;
if (!FFlag::LuauFragmentAutocompleteIfRecommendations)
{
while (endOffset < src.size() && src[endOffset] != '\n')
endOffset++;
}
foundEnd = true;
}
@ -1177,80 +1045,6 @@ std::optional<FragmentParseResult> parseFragment_DEPRECATED(
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)
{
if (!reporter)
@ -1267,156 +1061,6 @@ static void reportFragmentString(IFragmentAutocompleteReporter* reporter, std::s
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_(
Frontend& frontend,
AstStatBlock* root,
@ -1609,12 +1253,7 @@ std::pair<FragmentTypeCheckStatus, FragmentTypeCheckResult> typecheckFragment(
FrontendOptions frontendOptions = opts.value_or(frontend.options);
const ScopePtr& closestScope = FFlag::LuauBetterScopeSelection ? findClosestScope(module, parseResult.scopePos)
: findClosestScope_DEPRECATED(module, parseResult.nearestStatement);
FragmentTypeCheckResult result =
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
);
FragmentTypeCheckResult result = typecheckFragment_(frontend, parseResult.root, module, closestScope, cursorPos, std::move(parseResult.alloc), frontendOptions, reporter);
result.ancestry = std::move(parseResult.ancestry);
reportFragmentString(reporter, tryParse->fragmentToParse);
return {FragmentTypeCheckStatus::Success, result};

View file

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

View file

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

View file

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

View file

@ -22,6 +22,7 @@
LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity)
LUAU_FASTINTVARIABLE(LuauSubtypingReasoningLimit, 100)
LUAU_FASTFLAGVARIABLE(LuauSubtypingEnableReasoningLimit)
LUAU_FASTFLAGVARIABLE(LuauSubtypeGenericsAndNegations)
namespace Luau
{
@ -669,6 +670,18 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
result = {false};
else if (get<ErrorType>(subTy))
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))
result = isCovariantWith(env, p.first->ty, p.second->ty, scope).withBothComponent(TypePath::TypeField::Negated);
else if (auto subNegation = get<NegationType>(subTy))
@ -711,13 +724,13 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
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);
result.isSubtype = ok;
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);
result.isSubtype = ok;

View file

@ -13,8 +13,6 @@
#include "Luau/TypeUtils.h"
#include "Luau/Unifier2.h"
LUAU_FASTFLAGVARIABLE(LuauBidirectionalInferenceCollectIndexerTypes)
LUAU_FASTFLAGVARIABLE(LuauBidirectionalFailsafe)
LUAU_FASTFLAGVARIABLE(LuauBidirectionalInferenceElideAssert)
namespace Luau
@ -148,22 +146,13 @@ TypeId matchLiteralType(
expectedType = follow(expectedType);
exprType = follow(exprType);
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes)
{
// 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
// can unconditionally do so here.
if (is<AnyType, UnknownType>(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>())
@ -250,7 +239,7 @@ TypeId matchLiteralType(
// { x = {}, x = 42 }
//
// The type of this will be `{ x: number }`
if (FFlag::LuauBidirectionalFailsafe && !tableTy)
if (!tableTy)
return exprType;
LUAU_ASSERT(tableTy);
@ -291,7 +280,7 @@ TypeId matchLiteralType(
auto it = tableTy->props.find(keyStr);
// This can occur, potentially, if we are re-entrant.
if (FFlag::LuauBidirectionalFailsafe && it == tableTy->props.end())
if (it == tableTy->props.end())
continue;
LUAU_ASSERT(it != tableTy->props.end());
@ -330,18 +319,8 @@ TypeId matchLiteralType(
toBlock
);
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes)
{
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>());
}
@ -409,7 +388,7 @@ TypeId matchLiteralType(
}
else if (item.kind == AstExprTable::Item::List)
{
if (!FFlag::LuauBidirectionalInferenceCollectIndexerTypes || !FFlag::LuauBidirectionalInferenceElideAssert)
if (!FFlag::LuauBidirectionalInferenceElideAssert)
LUAU_ASSERT(tableTy->indexer);
if (expectedTableTy->indexer)
@ -431,18 +410,9 @@ TypeId matchLiteralType(
toBlock
);
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes)
{
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)
{
@ -464,12 +434,9 @@ TypeId matchLiteralType(
if (!item.key->as<AstExprConstantString>() && expectedTableTy->indexer)
(*astExpectedTypes)[item.key] = expectedTableTy->indexer->indexType;
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes)
{
indexerKeyTypes.insert(tKey);
indexerValueTypes.insert(tProp);
}
}
else
LUAU_ASSERT(!"Unexpected");
}
@ -530,7 +497,7 @@ TypeId matchLiteralType(
// have one too.
// TODO: If the expected table also has an indexer, we might want to
// push the expected indexer's types into it.
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes && expectedTableTy->indexer)
if (expectedTableTy->indexer)
{
if (indexerValueTypes.size() > 0 && indexerKeyTypes.size() > 0)
{

View file

@ -11,10 +11,9 @@
#include <math.h>
LUAU_FASTFLAG(LuauStoreCSTData2)
LUAU_FASTFLAG(LuauAstTypeGroup3)
LUAU_FASTFLAG(LuauParseOptionalAsNode2)
LUAU_FASTFLAG(LuauFixFunctionWithAttributesStartLocation)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAG(LuauStoreLocalAnnotationColonPositions)
namespace
{
@ -368,7 +367,7 @@ struct Printer_DEPRECATED
else if (typeCount == 1)
{
bool shouldParenthesize = unconditionallyParenthesize && (list.types.size == 0 || !list.types.data[0]->is<AstTypeGroup>());
if (FFlag::LuauAstTypeGroup3 ? shouldParenthesize : unconditionallyParenthesize)
if (shouldParenthesize)
writer.symbol("(");
// Only variadic tail
@ -381,7 +380,7 @@ struct Printer_DEPRECATED
visualizeTypeAnnotation(*list.types.data[0]);
}
if (FFlag::LuauAstTypeGroup3 ? shouldParenthesize : unconditionallyParenthesize)
if (shouldParenthesize)
writer.symbol(")");
}
else
@ -1233,18 +1232,9 @@ struct Printer_DEPRECATED
AstType* l = a->types.data[0];
AstType* r = a->types.data[1];
if (FFlag::LuauParseOptionalAsNode2)
{
auto lta = l->as<AstTypeReference>();
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)
auto rta = r->as<AstTypeReference>();
@ -1266,15 +1256,12 @@ struct Printer_DEPRECATED
}
for (size_t i = 0; i < a->types.size; ++i)
{
if (FFlag::LuauParseOptionalAsNode2)
{
if (a->types.data[i]->is<AstTypeOptional>())
{
writer.symbol("?");
continue;
}
}
if (i > 0)
{
@ -1359,14 +1346,15 @@ struct Printer
return nullptr;
}
void visualize(const AstLocal& local)
void visualize(const AstLocal& local, Position colonPosition)
{
advance(local.location.begin);
writer.identifier(local.name.value);
if (writeTypes && local.annotation)
{
// TODO: handle spacing for type annotation
if (FFlag::LuauStoreLocalAnnotationColonPositions)
advance(colonPosition);
writer.symbol(":");
visualizeTypeAnnotation(*local.annotation);
}
@ -1428,7 +1416,7 @@ struct Printer
else if (typeCount == 1)
{
bool shouldParenthesize = unconditionallyParenthesize && (list.types.size == 0 || !list.types.data[0]->is<AstTypeGroup>());
if (FFlag::LuauAstTypeGroup3 ? shouldParenthesize : unconditionallyParenthesize)
if (shouldParenthesize)
{
if (openParenthesesPosition)
advance(*openParenthesesPosition);
@ -1447,7 +1435,7 @@ struct Printer
visualizeTypeAnnotation(*list.types.data[0]);
}
if (FFlag::LuauAstTypeGroup3 ? shouldParenthesize : unconditionallyParenthesize)
if (shouldParenthesize)
{
if (closeParenthesesPosition)
advance(*closeParenthesesPosition);
@ -1973,10 +1961,16 @@ struct Printer
writer.keyword("local");
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();
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)
@ -1999,7 +1993,11 @@ struct Printer
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)
advance(cstNode->equalsPosition);
writer.symbol("=");
@ -2029,10 +2027,16 @@ struct Printer
writer.keyword("for");
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();
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);
@ -2363,6 +2367,11 @@ struct Printer
writer.identifier(local->name.value);
if (writeTypes && local->annotation)
{
if (FFlag::LuauStoreReturnTypesAsPackOnAst && FFlag::LuauStoreLocalAnnotationColonPositions && cstNode)
{
LUAU_ASSERT(cstNode->argsAnnotationColonPositions.size > i);
advance(cstNode->argsAnnotationColonPositions.data[i]);
}
writer.symbol(":");
visualizeTypeAnnotation(*local->annotation);
}
@ -2376,6 +2385,11 @@ struct Printer
if (func.varargAnnotation)
{
if (FFlag::LuauStoreReturnTypesAsPackOnAst && FFlag::LuauStoreLocalAnnotationColonPositions && cstNode)
{
LUAU_ASSERT(cstNode->varargAnnotationColonPosition != Position({0, 0}));
advance(cstNode->varargAnnotationColonPosition);
}
writer.symbol(":");
visualizeTypePackAnnotation(*func.varargAnnotation, true);
}
@ -2755,18 +2769,9 @@ struct Printer
AstType* l = a->types.data[0];
AstType* r = a->types.data[1];
if (FFlag::LuauParseOptionalAsNode2)
{
auto lta = l->as<AstTypeReference>();
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)
auto rta = r->as<AstTypeReference>();
@ -2795,8 +2800,6 @@ struct Printer
size_t separatorIndex = 0;
for (size_t i = 0; i < a->types.size; ++i)
{
if (FFlag::LuauParseOptionalAsNode2)
{
if (const auto optional = a->types.data[i]->as<AstTypeOptional>())
{
@ -2804,13 +2807,11 @@ struct Printer
writer.symbol("?");
continue;
}
}
if (i > 0)
{
if (cstNode && FFlag::LuauParseOptionalAsNode2)
if (cstNode)
{
// separatorIndex is only valid if `?` is handled as an AstTypeOptional
advance(cstNode->separatorPositions.data[separatorIndex]);
separatorIndex++;
}

View file

@ -30,11 +30,11 @@
LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAGVARIABLE(LuauImproveTypePathsInErrors)
LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
LUAU_FASTFLAGVARIABLE(LuauTypeCheckerAcceptNumberConcats)
LUAU_FASTFLAGVARIABLE(LuauTypeCheckerStricterIndexCheck)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAGVARIABLE(LuauReportSubtypingErrors)
namespace Luau
{
@ -1323,9 +1323,22 @@ void TypeChecker2::visit(AstExprConstantBool* expr)
NotNull<Scope> scope{findInnermostScope(expr->location)};
const SubtypingResult r = subtyping->isSubtype(bestType, inferredType, scope);
if (FFlag::LuauReportSubtypingErrors)
{
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)
{
@ -1348,9 +1361,22 @@ void TypeChecker2::visit(AstExprConstantString* expr)
NotNull<Scope> scope{findInnermostScope(expr->location)};
const SubtypingResult r = subtyping->isSubtype(bestType, inferredType, scope);
if (FFlag::LuauReportSubtypingErrors)
{
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)
{
@ -1417,6 +1443,12 @@ void TypeChecker2::visitCall(AstExprCall* call)
if (result.isSubtype)
fnTy = follow(*selectedOverloadTy);
if (FFlag::LuauReportSubtypingErrors)
{
if (!isErrorSuppressing(call->location, *selectedOverloadTy))
reportErrors(std::move(result.errors));
}
if (result.normalizationTooComplex)
{
reportError(NormalizationTooComplex{}, call->func->location);
@ -2728,8 +2760,6 @@ Reasonings TypeChecker2::explainReasonings_(TID subTy, TID superTy, Location loc
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);
if (FFlag::LuauImproveTypePathsInErrors)
{
std::string relation = "a subtype of";
if (reasoning.variance == SubtypingVariance::Invariant)
relation = "exactly";
@ -2765,24 +2795,6 @@ Reasonings TypeChecker2::explainReasonings_(TID subTy, TID superTy, Location loc
reason << toStringHuman(reasoning.superPath) << "`" << superLeafAsString << "`, and " << baseReason;
reasons.push_back(reason.str());
}
else
{
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;
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 (suppressed)
@ -2850,6 +2862,12 @@ bool TypeChecker2::testIsSubtype(TypeId subTy, TypeId superTy, Location location
NotNull<Scope> scope{findInnermostScope(location)};
SubtypingResult r = subtyping->isSubtype(subTy, superTy, scope);
if (FFlag::LuauReportSubtypingErrors)
{
if (!isErrorSuppressing(location, subTy))
reportErrors(std::move(r.errors));
}
if (r.normalizationTooComplex)
reportError(NormalizationTooComplex{}, location);
@ -2864,6 +2882,12 @@ bool TypeChecker2::testIsSubtype(TypePackId subTy, TypePackId superTy, Location
NotNull<Scope> scope{findInnermostScope(location)};
SubtypingResult r = subtyping->isSubtype(subTy, superTy, scope);
if (FFlag::LuauReportSubtypingErrors)
{
if (!isErrorSuppressing(location, subTy))
reportErrors(std::move(r.errors));
}
if (r.normalizationTooComplex)
reportError(NormalizationTooComplex{}, location);

View file

@ -56,17 +56,16 @@ LUAU_FASTFLAGVARIABLE(LuauMetatableTypeFunctions)
LUAU_FASTFLAGVARIABLE(LuauIndexTypeFunctionImprovements)
LUAU_FASTFLAGVARIABLE(LuauIndexTypeFunctionFunctionMetamethods)
LUAU_FASTFLAGVARIABLE(LuauIntersectNotNil)
LUAU_FASTFLAGVARIABLE(LuauSkipNoRefineDuringRefinement)
LUAU_FASTFLAGVARIABLE(LuauMetatablesHaveLength)
LUAU_FASTFLAGVARIABLE(LuauIndexAnyIsAny)
LUAU_FASTFLAGVARIABLE(LuauFixCyclicIndexInIndexer)
LUAU_FASTFLAGVARIABLE(LuauSimplyRefineNotNil)
LUAU_FASTFLAGVARIABLE(LuauIndexDeferPendingIndexee)
LUAU_FASTFLAGVARIABLE(LuauNewTypeFunReductionChecks2)
LUAU_FASTFLAGVARIABLE(LuauReduceUnionFollowUnionType)
LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect)
LUAU_FASTFLAGVARIABLE(LuauNarrowIntersectionNevers)
LUAU_FASTFLAGVARIABLE(LuauRefineWaitForBlockedTypesInTarget)
LUAU_FASTFLAGVARIABLE(LuauNotAllBinaryTypeFunsHaveDefaults)
namespace Luau
{
@ -2491,8 +2490,6 @@ struct CollectUnionTypeOptions : TypeOnceVisitor
}
bool visit(TypeId ty, const UnionType& ut) override
{
if (FFlag::LuauReduceUnionFollowUnionType)
{
// If we have something like:
//
@ -2503,15 +2500,6 @@ struct CollectUnionTypeOptions : TypeOnceVisitor
// 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
{
@ -3600,8 +3588,8 @@ void BuiltinTypeFunctions::addToScope(NotNull<TypeArena> arena, NotNull<Scope> s
return TypeFun{{genericT}, arena->addType(TypeFunctionInstanceType{NotNull{tf}, {t}, {}})};
};
// make a type function for a two-argument type function
auto mkBinaryTypeFunction = [&](const TypeFunction* tf)
// make a type function for a two-argument type function with a default argument for the second type being the first
auto mkBinaryTypeFunctionWithDefault = [&](const TypeFunction* tf)
{
TypeId t = arena->addType(GenericType{"T", 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}, {}})};
};
// 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[unmFunc.name] = mkUnaryTypeFunction(&unmFunc);
scope->exportedTypeBindings[addFunc.name] = mkBinaryTypeFunction(&addFunc);
scope->exportedTypeBindings[subFunc.name] = mkBinaryTypeFunction(&subFunc);
scope->exportedTypeBindings[mulFunc.name] = mkBinaryTypeFunction(&mulFunc);
scope->exportedTypeBindings[divFunc.name] = mkBinaryTypeFunction(&divFunc);
scope->exportedTypeBindings[idivFunc.name] = mkBinaryTypeFunction(&idivFunc);
scope->exportedTypeBindings[powFunc.name] = mkBinaryTypeFunction(&powFunc);
scope->exportedTypeBindings[modFunc.name] = mkBinaryTypeFunction(&modFunc);
scope->exportedTypeBindings[concatFunc.name] = mkBinaryTypeFunction(&concatFunc);
scope->exportedTypeBindings[addFunc.name] = mkBinaryTypeFunctionWithDefault(&addFunc);
scope->exportedTypeBindings[subFunc.name] = mkBinaryTypeFunctionWithDefault(&subFunc);
scope->exportedTypeBindings[mulFunc.name] = mkBinaryTypeFunctionWithDefault(&mulFunc);
scope->exportedTypeBindings[divFunc.name] = mkBinaryTypeFunctionWithDefault(&divFunc);
scope->exportedTypeBindings[idivFunc.name] = mkBinaryTypeFunctionWithDefault(&idivFunc);
scope->exportedTypeBindings[powFunc.name] = mkBinaryTypeFunctionWithDefault(&powFunc);
scope->exportedTypeBindings[modFunc.name] = mkBinaryTypeFunctionWithDefault(&modFunc);
scope->exportedTypeBindings[concatFunc.name] = mkBinaryTypeFunctionWithDefault(&concatFunc);
scope->exportedTypeBindings[ltFunc.name] = mkBinaryTypeFunction(&ltFunc);
scope->exportedTypeBindings[leFunc.name] = mkBinaryTypeFunction(&leFunc);
scope->exportedTypeBindings[eqFunc.name] = mkBinaryTypeFunction(&eqFunc);
scope->exportedTypeBindings[ltFunc.name] = mkBinaryTypeFunctionWithDefault(&ltFunc);
scope->exportedTypeBindings[leFunc.name] = mkBinaryTypeFunctionWithDefault(&leFunc);
scope->exportedTypeBindings[eqFunc.name] = mkBinaryTypeFunctionWithDefault(&eqFunc);
scope->exportedTypeBindings[keyofFunc.name] = mkUnaryTypeFunction(&keyofFunc);
scope->exportedTypeBindings[rawkeyofFunc.name] = mkUnaryTypeFunction(&rawkeyofFunc);
if (FFlag::LuauNotAllBinaryTypeFunsHaveDefaults)
{
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::LuauNotAllBinaryTypeFunsHaveDefaults)
scope->exportedTypeBindings[setmetatableFunc.name] = mkBinaryTypeFunction(&setmetatableFunc);
else
scope->exportedTypeBindings[setmetatableFunc.name] = mkBinaryTypeFunctionWithDefault(&setmetatableFunc);
scope->exportedTypeBindings[getmetatableFunc.name] = mkUnaryTypeFunction(&getmetatableFunc);
}
}

View file

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

View file

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

View file

@ -116,7 +116,9 @@ public:
Position openGenericsPosition{0, 0};
AstArray<Position> genericsCommaPositions;
Position closeGenericsPosition{0, 0};
AstArray<Position> argsAnnotationColonPositions;
AstArray<Position> argsCommaPositions;
Position varargAnnotationColonPosition{0, 0};
Position returnSpecifierPosition{0, 0};
};
@ -224,8 +226,9 @@ class CstStatLocal : public CstNode
public:
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> valuesCommaPositions;
};
@ -235,8 +238,9 @@ class CstStatFor : public CstNode
public:
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 endCommaPosition;
std::optional<Position> stepCommaPosition;
@ -247,8 +251,9 @@ class CstStatForIn : public CstNode
public:
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> valuesCommaPositions;
};

View file

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

View file

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

View file

@ -19,14 +19,12 @@ LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
// See docs/SyntaxChanges.md for an explanation.
LUAU_FASTFLAGVARIABLE(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauStoreCSTData2)
LUAU_FASTFLAGVARIABLE(LuauAstTypeGroup3)
LUAU_FASTFLAGVARIABLE(LuauParseOptionalAsNode2)
LUAU_FASTFLAGVARIABLE(LuauDeclareExternType)
LUAU_FASTFLAGVARIABLE(LuauParseStringIndexer)
LUAU_FASTFLAGVARIABLE(LuauTypeFunResultInAutocomplete)
LUAU_FASTFLAGVARIABLE(LuauDeprecatedAttribute)
LUAU_FASTFLAGVARIABLE(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAGVARIABLE(LuauFixFunctionWithAttributesStartLocation)
LUAU_FASTFLAGVARIABLE(LuauStoreLocalAnnotationColonPositions)
LUAU_DYNAMIC_FASTFLAGVARIABLE(DebugLuauReportReturnTypeVariadicWithTypeSuffix, false)
// 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);
if (options.storeCstData)
cstNodeMap[node] = allocator.alloc<CstStatFor>(equalsPosition, endCommaPosition, stepCommaPosition);
cstNodeMap[node] = allocator.alloc<CstStatFor>(varname.colonPosition, equalsPosition, endCommaPosition, stepCommaPosition);
return node;
}
else
@ -664,7 +663,7 @@ AstStat* Parser::parseFor()
{
Position initialCommaPosition = lexer.current().location.begin;
nextLexeme();
parseBindingList(names, false, &varsCommaPosition, initialCommaPosition);
parseBindingList(names, false, &varsCommaPosition, &initialCommaPosition);
}
else
{
@ -709,7 +708,12 @@ AstStat* Parser::parseFor()
AstStatForIn* node =
allocator.alloc<AstStatForIn>(Location(start, end), copy(vars), copy(values), body, hasIn, inLocation, hasDo, matchDo.location);
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;
}
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)
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);
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;
}
else
@ -1140,8 +1150,6 @@ AstStat* Parser::parseTypeFunction(const Location& start, bool exported, Positio
AstDeclaredExternTypeProperty Parser::parseDeclaredExternTypeMethod(const AstArray<AstAttr*>& attributes)
{
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
Location start = lexer.current().location;
nextLexeme();
@ -1225,85 +1233,6 @@ AstDeclaredExternTypeProperty Parser::parseDeclaredExternTypeMethod(const AstArr
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)
{
// `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};
if (FFlag::LuauDeprecatedAttribute && (lexer.current().type == Lexeme::Attribute))
if (lexer.current().type == Lexeme::Attribute)
{
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.
if (lexer.current().type == Lexeme::ReservedFunction)
{
if (FFlag::LuauDeprecatedAttribute)
props.push_back(parseDeclaredExternTypeMethod(attributes));
else
props.push_back(parseDeclaredExternTypeMethod_DEPRECATED());
}
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.
if (lexer.current().type == Lexeme::ReservedFunction)
{
if (FFlag::LuauDeprecatedAttribute)
props.push_back(parseDeclaredExternTypeMethod(attributes));
else
props.push_back(parseDeclaredExternTypeMethod_DEPRECATED());
}
else if (lexer.current().type == '[' &&
(lexer.lookahead().type == Lexeme::RawString || lexer.lookahead().type == Lexeme::QuotedString))
@ -1786,8 +1709,19 @@ std::pair<AstExprFunction*, AstLocal*> Parser::parseFunctionBody(
AstTypePack* varargAnnotation = nullptr;
if (lexer.current().type != ')')
{
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;
@ -1846,6 +1780,8 @@ std::pair<AstExprFunction*, AstLocal*> Parser::parseFunctionBody(
if (options.storeCstData)
{
cstNode->functionKeywordPosition = matchFunction.location.begin;
if (FFlag::LuauStoreLocalAnnotationColonPositions)
cstNode->argsAnnotationColonPositions = extractAnnotationColonPositions(args);
cstNodeMap[node] = cstNode;
}
@ -2042,17 +1978,31 @@ Parser::Binding Parser::parseBinding()
if (!name)
name = Name(nameError, lexer.current().location);
Position colonPosition = lexer.current().location.begin;
AstType* annotation = parseOptionalType();
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]
std::tuple<bool, Location, AstTypePack*> Parser::parseBindingList(
TempVector<Binding>& result,
bool allowDot3,
AstArray<Position>* commaPositions,
std::optional<Position> initialCommaPosition
Position* initialCommaPosition,
Position* varargAnnotationColonPosition
)
{
TempVector<Position> localCommaPositions(scratchPosition);
@ -2070,6 +2020,9 @@ std::tuple<bool, Location, AstTypePack*> Parser::parseBindingList(
AstTypePack* tailAnnotation = nullptr;
if (lexer.current().type == ':')
{
if (FFlag::LuauStoreLocalAnnotationColonPositions && varargAnnotationColonPosition)
*varargAnnotationColonPosition = lexer.current().location.begin;
nextLexeme();
tailAnnotation = parseVariadicArgumentTypePack();
}
@ -2256,8 +2209,6 @@ AstTypePack* Parser::parseReturnType()
nextLexeme();
Location innerBegin = lexer.current().location;
matchRecoveryStopOnToken[Lexeme::SkinnyArrow]++;
TempVector<AstType*> result(scratchType);
@ -2283,8 +2234,6 @@ AstTypePack* Parser::parseReturnType()
if (lexer.current().type != Lexeme::SkinnyArrow && resultNames.empty())
{
// If it turns out that it's just '(A)', it's possible that there are unions/intersections to follow, so fold over it.
if (FFlag::LuauAstTypeGroup3)
{
if (result.size() == 1)
{
// TODO(CLI-140667): stop parsing type suffix when varargAnnotation != nullptr - this should be a parse error
@ -2299,32 +2248,12 @@ AstTypePack* Parser::parseReturnType()
// 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});
AstTypePackExplicit* node =
allocator.alloc<AstTypePackExplicit>(Location{location.begin, endPos}, AstTypeList{copy(&returnType, 1), varargAnnotation});
if (options.storeCstData)
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});
if (options.storeCstData)
@ -2366,8 +2295,6 @@ std::pair<Location, AstTypeList> Parser::parseReturnType_DEPRECATED()
nextLexeme();
Location innerBegin = lexer.current().location;
matchRecoveryStopOnToken[Lexeme::SkinnyArrow]++;
TempVector<AstType*> result(scratchType);
@ -2387,8 +2314,6 @@ std::pair<Location, AstTypeList> Parser::parseReturnType_DEPRECATED()
if (lexer.current().type != Lexeme::SkinnyArrow && resultNames.empty())
{
// If it turns out that it's just '(A)', it's possible that there are unions/intersections to follow, so fold over it.
if (FFlag::LuauAstTypeGroup3)
{
if (result.size() == 1)
{
// TODO(CLI-140667): stop parsing type suffix when varargAnnotation != nullptr - this should be a parse error
@ -2405,24 +2330,7 @@ std::pair<Location, AstTypeList> Parser::parseReturnType_DEPRECATED()
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}};
}
@ -2888,10 +2796,7 @@ AstTypeOrPack Parser::parseFunctionType(bool allowPack, const AstArray<AstAttr*>
}
else
{
if (FFlag::LuauAstTypeGroup3)
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 isIntersection = false;
// Clip with FFlag::LuauParseOptionalAsNode2
bool hasOptional_DEPRECATED = false;
unsigned int optionalCount = 0;
Location location = begin;
@ -3047,19 +2950,10 @@ AstType* Parser::parseTypeSuffix(AstType* type, const Location& begin)
Location loc = lexer.current().location;
nextLexeme();
if (FFlag::LuauParseOptionalAsNode2)
{
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;
hasOptional_DEPRECATED = true;
}
else if (c == '&')
{
@ -3087,17 +2981,9 @@ AstType* Parser::parseTypeSuffix(AstType* type, const Location& begin)
else
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");
}
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)
return parts[0];
@ -4520,16 +4406,11 @@ AstArray<AstTypeOrPack> Parser::parseTypeParams(Position* openingPosition, TempV
// the next lexeme is one that follows a type
// (&, |, ?), then assume that this was actually a
// parenthesized type.
if (FFlag::LuauAstTypeGroup3)
{
auto parenthesizedType = explicitTypePack->typeList.types.data[0];
parameters.push_back(
{parseTypeSuffix(allocator.alloc<AstTypeGroup>(parenthesizedType->location, parenthesizedType), begin), {}}
);
}
else
parameters.push_back({parseTypeSuffix(explicitTypePack->typeList.types.data[0], begin), {}});
}
else
{
// Otherwise, it's a type pack.

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
// so they can be included by unit tests.
void* createCliRequireContext(lua_State* L);
void setupState(lua_State* L);
std::string runCode(lua_State* L, const std::string& source);
void getCompletions(lua_State* L, const std::string& editBuffer, const AddCompletionCallback& addCompletionCallback);

View file

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

View file

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

View file

@ -3,6 +3,7 @@
#include "Luau/CodeGen.h"
#include "Luau/CodeGenOptions.h"
#include "Luau/FileUtils.h"
#include "Luau/Require.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);
}
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)
{
ReplRequirer* req = static_cast<ReplRequirer*>(ctx);
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)
{
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);
}
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);
@ -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
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
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 (req->codegenEnabled())
@ -199,23 +204,18 @@ void requireConfigInit(luarequire_Configuration* config)
config->to_parent = to_parent;
config->to_child = to_child;
config->is_module_present = is_module_present;
config->get_contents = get_contents;
config->is_config_present = is_config_present;
config->get_chunkname = get_chunkname;
config->get_loadname = get_loadname;
config->get_cache_key = get_cache_key;
config->get_config = get_config;
config->load = load;
}
ReplRequirer::ReplRequirer(
std::function<Luau::CompileOptions()> copts,
std::function<bool()> coverageActive,
std::function<bool()> codegenEnabled,
std::function<void(lua_State*, int)> coverageTrack
)
: copts(std::move(copts))
, coverageActive(std::move(coverageActive))
, codegenEnabled(std::move(codegenEnabled))
, coverageTrack(std::move(coverageTrack))
ReplRequirer::ReplRequirer(CompileOptions copts, BoolCheck coverageActive, BoolCheck codegenEnabled, Coverage coverageTrack)
: copts(copts)
, coverageActive(coverageActive)
, codegenEnabled(codegenEnabled)
, coverageTrack(coverageTrack)
{
}

View file

@ -83,15 +83,15 @@ struct luarequire_Configuration
// Returns whether the context is currently pointing at a module.
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
// through the debug library. This function is only called if
// is_module_present returns true.
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
// 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);
@ -109,7 +109,7 @@ struct luarequire_Configuration
// 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
// 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.

View file

@ -62,16 +62,16 @@ bool RuntimeNavigationContext::isModulePresent() const
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
{
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
{
return getStringFromCWriter(config->get_cache_key, initalIdentifierBufferSize);

View file

@ -31,8 +31,8 @@ public:
// Custom capabilities
bool isModulePresent() const;
std::optional<std::string> getContents() const;
std::optional<std::string> getChunkname() const;
std::optional<std::string> getLoadname() const;
std::optional<std::string> getCacheKey() const;
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");
if (!config.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)
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)
luaL_error(L, "require configuration is missing required function pointer: get_cache_key");
if (!config.is_config_present)

View file

@ -29,8 +29,8 @@ struct ResolvedRequire
};
Status status;
std::string contents;
std::string chunkname;
std::string loadname;
std::string cacheKey;
};
@ -89,17 +89,17 @@ static ResolvedRequire resolveRequire(luarequire_Configuration* lrc, lua_State*
return ResolvedRequire{ResolvedRequire::Status::ErrorReported};
}
std::optional<std::string> contents = navigationContext.getContents();
if (!contents)
std::optional<std::string> loadname = navigationContext.getLoadname();
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::ModuleRead,
std::move(*contents),
std::move(*chunkname),
std::move(*loadname),
std::move(*cacheKey),
};
}
@ -181,7 +181,7 @@ int lua_requireinternal(lua_State* L, const char* requirerChunkname)
lua_pushinteger(L, numArgsBeforeLoad);
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 (lua_gettop(L) != numArgsBeforeLoad)

View file

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

View file

@ -11,7 +11,6 @@
using namespace Luau;
LUAU_FASTFLAG(LuauAstTypeGroup3)
LUAU_FASTFLAG(LuauFixFunctionWithAttributesStartLocation)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
@ -505,28 +504,16 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_annotation")
{
AstStat* statement = expectParseStatement("type T = ((number) -> (string | nil)) & ((string) -> ())");
if (FFlag::LuauStoreReturnTypesAsPackOnAst && FFlag::LuauAstTypeGroup3)
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
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})";
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
{
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);
}
}

View file

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

View file

@ -33,12 +33,12 @@ LUAU_FASTFLAG(LuauClonedTableAndFunctionTypesMustHaveScopes)
LUAU_FASTFLAG(LuauDisableNewSolverAssertsInMixedMode)
LUAU_FASTFLAG(LuauCloneTypeAliasBindings)
LUAU_FASTFLAG(LuauDoNotClonePersistentBindings)
LUAU_FASTFLAG(LuauIncrementalAutocompleteDemandBasedCloning)
LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
LUAU_FASTFLAG(LuauBetterScopeSelection)
LUAU_FASTFLAG(LuauBlockDiffFragmentSelection)
LUAU_FASTFLAG(LuauFragmentAcMemoryLeak)
LUAU_FASTFLAG(LuauGlobalVariableModuleIsolation)
LUAU_FASTFLAG(LuauFragmentAutocompleteIfRecommendations)
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 luauCloneTypeAliasBindings{FFlag::LuauCloneTypeAliasBindings, true};
ScopedFastFlag luauDoNotClonePersistentBindings{FFlag::LuauDoNotClonePersistentBindings, true};
ScopedFastFlag luauIncrementalAutocompleteDemandBasedCloning{FFlag::LuauIncrementalAutocompleteDemandBasedCloning, true};
ScopedFastFlag luauBetterScopeSelection{FFlag::LuauBetterScopeSelection, true};
ScopedFastFlag luauBlockDiffFragmentSelection{FFlag::LuauBlockDiffFragmentSelection, true};
ScopedFastFlag luauAutocompleteUsesModuleForTypeCompatibility{FFlag::LuauAutocompleteUsesModuleForTypeCompatibility, true};
ScopedFastFlag luauFragmentAcMemoryLeak{FFlag::LuauFragmentAcMemoryLeak, true};
ScopedFastFlag luauGlobalVariableModuleIsolation{FFlag::LuauGlobalVariableModuleIsolation, true};
ScopedFastFlag luauFragmentAutocompleteIfRecommendations{FFlag::LuauFragmentAutocompleteIfRecommendations, true};
FragmentAutocompleteFixtureImpl()
: BaseType(true)
@ -746,11 +746,10 @@ TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "if_partial")
{
auto region = getAutocompleteRegion(
R"(
if
)",
Position{1, 3}
if)",
Position{1, 2}
);
CHECK_EQ(Location{{1, 0}, {1, 3}}, region.fragmentLocation);
CHECK_EQ(Location{{1, 2}, {1, 2}}, region.fragmentLocation);
REQUIRE(region.parentBlock);
CHECK(region.nearestStatement->as<AstStatIf>());
}
@ -763,7 +762,7 @@ if true
)",
Position{1, 7}
);
CHECK_EQ(Location{{1, 0}, {1, 7}}, region.fragmentLocation);
CHECK_EQ(Location{{1, 3}, {1, 7}}, region.fragmentLocation);
REQUIRE(region.parentBlock);
CHECK(region.nearestStatement->as<AstStatIf>());
}
@ -776,7 +775,7 @@ if true
)",
Position{1, 8}
);
CHECK_EQ(Location{{1, 8}, {1, 8}}, region.fragmentLocation);
CHECK_EQ(Location{{1, 3}, {1, 8}}, region.fragmentLocation);
REQUIRE(region.parentBlock);
CHECK(region.nearestStatement->as<AstStatIf>());
}
@ -849,7 +848,7 @@ elseif
)",
Position{2, 8}
);
CHECK_EQ(Location{{2, 0}, {2, 8}}, region.fragmentLocation);
CHECK_EQ(Location{{2, 8}, {2, 8}}, region.fragmentLocation);
REQUIRE(region.parentBlock);
CHECK(region.nearestStatement->as<AstStatIf>());
}
@ -1071,7 +1070,7 @@ TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "statement_in_empty_fragment_is_n
);
REQUIRE(fragment.has_value());
CHECK_EQ("", fragment->fragmentToParse);
CHECK_EQ(2, fragment->ancestry.size());
CHECK_EQ(1, fragment->ancestry.size());
REQUIRE(fragment->root);
CHECK_EQ(0, fragment->root->body.size);
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("local z = x + y", fragment->fragmentToParse);
CHECK_EQ(5, fragment->ancestry.size());
CHECK_EQ(4, fragment->ancestry.size());
REQUIRE(fragment->root);
CHECK_EQ(1, fragment->root->body.size);
auto stat = fragment->root->body.data[0]->as<AstStatLocal>();
@ -1149,7 +1148,7 @@ local y = 5
REQUIRE(fragment.has_value());
CHECK_EQ("local z = x + y", fragment->fragmentToParse);
CHECK_EQ(5, fragment->ancestry.size());
CHECK_EQ(4, fragment->ancestry.size());
REQUIRE(fragment->root);
CHECK_EQ(Location{Position{2, 0}, Position{2, 15}}, fragment->root->location);
CHECK_EQ(1, fragment->root->body.size);
@ -1244,7 +1243,7 @@ abc("bar")
REQUIRE(stringFragment.has_value());
CHECK_EQ("abc(\"foo\")", stringFragment->fragmentToParse);
CHECK_EQ("abc(\"foo\"", stringFragment->fragmentToParse);
CHECK(stringFragment->nearestStatement);
CHECK(stringFragment->nearestStatement->is<AstStatExpr>());
@ -3605,6 +3604,126 @@ end)
)";
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)
TEST_SUITE_END();

View file

@ -16,40 +16,10 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAG(DebugLuauFreezeArena)
LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
LUAU_FASTFLAG(LuauNewNonStrictVisitTypes2)
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
{
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.
// 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>
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
if (FFlag::LuauSolverV2)
{
REQUIRE_EQ(
"Type\n\t"
@ -930,14 +900,6 @@ TEST_CASE_FIXTURE(FrontendFixture, "it_should_be_safe_to_stringify_errors_when_f
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
REQUIRE_EQ(
"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")
{
ScopedFastFlag sff[] = {{FFlag::LuauDeprecatedAttribute, true}, {FFlag::LuauSolverV2, true}};
ScopedFastFlag _{FFlag::LuauSolverV2, true};
// @deprecated works on local functions
{
@ -1877,7 +1877,7 @@ end
TEST_CASE_FIXTURE(Fixture, "DeprecatedAttributeFunctionDeclaration")
{
ScopedFastFlag sff[] = {{FFlag::LuauDeprecatedAttribute, true}, {FFlag::LuauSolverV2, true}};
ScopedFastFlag _{FFlag::LuauSolverV2, true};
// @deprecated works on function type declarations
@ -1895,7 +1895,7 @@ bar(2)
TEST_CASE_FIXTURE(Fixture, "DeprecatedAttributeTableDeclaration")
{
ScopedFastFlag sff[] = {{FFlag::LuauDeprecatedAttribute, true}, {FFlag::LuauSolverV2, true}};
ScopedFastFlag _{FFlag::LuauSolverV2, true};
// @deprecated works on table type declarations
@ -1915,7 +1915,7 @@ print(Hooty:tooty(2.0))
TEST_CASE_FIXTURE(Fixture, "DeprecatedAttributeMethodDeclaration")
{
ScopedFastFlag sff[] = {{FFlag::LuauDeprecatedAttribute, true}, {FFlag::LuauSolverV2, true}};
ScopedFastFlag _{FFlag::LuauSolverV2, true};
// @deprecated works on table type declarations

View file

@ -17,8 +17,6 @@ LUAU_FASTINT(LuauRecursionLimit)
LUAU_FASTINT(LuauTypeLengthLimit)
LUAU_FASTINT(LuauParseErrorLimit)
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauAstTypeGroup3)
LUAU_FASTFLAG(LuauParseOptionalAsNode2)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAG(LuauParseStringIndexer)
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>();
}
REQUIRE(returnAnnotation != nullptr);
if (FFlag::LuauAstTypeGroup3)
CHECK(returnAnnotation->types.data[0]->as<AstTypeGroup>());
else
CHECK(returnAnnotation->types.data[0]->as<AstTypeReference>());
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")
{
ScopedFastFlag _{FFlag::LuauAstTypeGroup3, true};
AstStatBlock* stat = parse(R"(
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")
{
ScopedFastFlag _{FFlag::LuauAstTypeGroup3, true};
AstStatBlock* stat = parse(R"(
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")
{
ScopedFastFlag _{FFlag::LuauAstTypeGroup3, true};
AstStatBlock* stat = parse(R"(
type Foo = () -> (string)
)");
@ -4206,18 +4195,10 @@ TEST_CASE_FIXTURE(Fixture, "grouped_function_type")
auto unionTy = paramTy.type->as<AstTypeUnion>();
LUAU_ASSERT(unionTy);
CHECK_EQ(unionTy->types.size, 2);
if (FFlag::LuauAstTypeGroup3)
{
auto groupTy = unionTy->types.data[0]->as<AstTypeGroup>(); // (() -> ())
REQUIRE(groupTy);
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")

View file

@ -7,6 +7,7 @@
#include "lualib.h"
#include "Luau/Repl.h"
#include "Luau/ReplRequirer.h"
#include "Luau/Require.h"
#include "Luau/FileUtils.h"
@ -546,6 +547,16 @@ TEST_CASE_FIXTURE(ReplWithPathFixture, "RegisterRuntimeModule")
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")
{
runCode(L, "return pcall(function() return loadstring(\"require('a/relative/path')\")() end)");

View file

@ -16,6 +16,7 @@
#include <initializer_list>
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauSubtypeGenericsAndNegations)
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();

View file

@ -14,7 +14,6 @@ using namespace Luau;
LUAU_FASTFLAG(LuauRecursiveTypeParameterRestriction)
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauAttributeSyntax)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
TEST_SUITE_BEGIN("ToString");
@ -873,28 +872,11 @@ TEST_CASE_FIXTURE(Fixture, "tostring_error_mismatch")
)");
std::string expected;
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
if (FFlag::LuauSolverV2)
expected =
"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 "
"type, and `string` is not exactly `number`";
else if (FFlag::LuauSolverV2)
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
'{ 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)";
else
expected = R"(Type
'{ a: number, b: string, c: { d: string } }'

View file

@ -13,9 +13,8 @@
using namespace Luau;
LUAU_FASTFLAG(LuauStoreCSTData2)
LUAU_FASTFLAG(LuauAstTypeGroup3);
LUAU_FASTFLAG(LuauParseOptionalAsNode2)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAG(LuauStoreLocalAnnotationColonPositions)
TEST_SUITE_BEGIN("TranspilerTests");
@ -343,7 +342,8 @@ TEST_CASE("function_with_types_spaces_around_tokens")
{
ScopedFastFlag sffs[] = {
{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 )";
CHECK_EQ(code, transpile(code, {}, true).code);
@ -369,10 +369,6 @@ TEST_CASE("function_with_types_spaces_around_tokens")
code = R"( function p<X, Y, Z...> (o: string, m: number, ...: any): string end )";
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 )";
// CHECK_EQ(code, transpile(code, {}, true).code);
code = R"( function p<X, Y, Z...>(o : string, m: number, ...: any): string end )";
CHECK_EQ(code, transpile(code, {}, true).code);
@ -385,9 +381,11 @@ TEST_CASE("function_with_types_spaces_around_tokens")
code = R"( function p<X, Y, Z...>(o: string, m: number, ...: any): string end )";
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 )";
// CHECK_EQ(code, transpile(code, {}, true).code);
code = R"( function p<X, Y, Z...>(o: string, m: number, ...: any): string end )";
CHECK_EQ(code, transpile(code, {}, true).code);
code = R"( function p<X, Y, Z...>(o: string, m: number, ... : any): string end )";
CHECK_EQ(code, transpile(code, {}, true).code);
code = R"( function p<X, Y, Z...>(o: string, m: number, ...: any): string end )";
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);
}
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")
{
std::string code = R"(
@ -1323,8 +1374,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_union_type_nested_3")
if (FFlag::LuauStoreCSTData2)
CHECK_EQ(code, transpile(code, {}, true).code);
else if (FFlag::LuauAstTypeGroup3)
CHECK_EQ("local a: (string & number)?", transpile(code, {}, true).code);
else
CHECK_EQ("local a: (string & number)?", transpile(code, {}, true).code);
}
@ -1358,7 +1407,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_leading_union_pipe")
{
ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true},
{FFlag::LuauParseOptionalAsNode2, true},
};
std::string code = "local a: | string | number";
CHECK_EQ(code, transpile(code, {}, true).code);
@ -1371,7 +1419,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_union_spaces_around_tokens")
{
ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true},
{FFlag::LuauParseOptionalAsNode2, true},
};
std::string code = "local a: string | number";
CHECK_EQ(code, transpile(code, {}, true).code);
@ -1408,8 +1455,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_mixed_union_intersection")
{
ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true},
{FFlag::LuauAstTypeGroup3, true},
{FFlag::LuauParseOptionalAsNode2, true},
};
std::string code = "local a: string | (Foo & Bar)";
CHECK_EQ(code, transpile(code, {}, true).code);
@ -1437,7 +1482,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_preserve_union_optional_style")
{
ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true},
{FFlag::LuauParseOptionalAsNode2, true},
};
std::string code = "local a: string | nil";
CHECK_EQ(code, transpile(code, {}, true).code);
@ -2029,7 +2073,6 @@ TEST_CASE("transpile_types_preserve_parentheses_style")
{
ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true},
{FFlag::LuauAstTypeGroup3, true},
};
std::string code = R"( type Foo = number )";
@ -2176,7 +2219,6 @@ TEST_CASE("transpile_type_function_return_types")
{
ScopedFastFlag fflags[] = {
{FFlag::LuauStoreCSTData2, true},
{FFlag::LuauAstTypeGroup3, true},
{FFlag::LuauStoreReturnTypesAsPackOnAst, true},
};
std::string code = R"( type Foo = () -> () )";
@ -2224,8 +2266,6 @@ TEST_CASE("transpile_type_function_return_types")
TEST_CASE("fuzzer_nil_optional")
{
ScopedFastFlag _{FFlag::LuauParseOptionalAsNode2, true};
const std::string code = R"( local x: nil? )";
CHECK_EQ(code, transpile(code, {}, true).code);
}

View file

@ -10,7 +10,6 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauTypeFunReadWriteParents)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
LUAU_FASTFLAG(LuauNewTypeFunReductionChecks2)
LUAU_FASTFLAG(LuauNoTypeFunctionsNamedTypeOf)
@ -1337,9 +1336,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tag_field")
LUAU_REQUIRE_ERROR_COUNT(3, result);
if (FFlag::LuauImproveTypePathsInErrors)
{
CHECK(
toString(result.errors[0]) ==
"Type pack '\"number\"' could not be converted into 'never'; \n"
@ -1356,17 +1352,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tag_field")
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")
{

View file

@ -11,10 +11,9 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauFixInfiniteRecursionInNormalization)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
LUAU_FASTFLAG(LuauBidirectionalInferenceCollectIndexerTypes)
LUAU_FASTFLAG(LuauRetainDefinitionAliasLocations)
LUAU_FASTFLAG(LuauNewNonStrictVisitTypes2)
LUAU_FASTFLAG(LuauGuardAgainstMalformedTypeAliasExpansion)
TEST_SUITE_BEGIN("TypeAliases");
@ -220,11 +219,10 @@ TEST_CASE_FIXTURE(Fixture, "generic_aliases")
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = (FFlag::LuauImproveTypePathsInErrors)
? "Type '{ v: string }' could not be converted into 'T<number>'; \n"
const std::string expected =
"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 "
"`string` is not exactly `number`"
: R"(Type '{ v: string }' could not be converted into 'T<number>'; at [read "v"], string is not exactly number)";
"`string` is not exactly `number`";
CHECK(result.errors[0].location == Location{{4, 31}, {4, 44}});
CHECK_EQ(expected, toString(result.errors[0]));
}
@ -244,11 +242,9 @@ TEST_CASE_FIXTURE(Fixture, "dependent_generic_aliases")
LUAU_REQUIRE_ERROR_COUNT(1, result);
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 "
"`number`"
: R"(Type '{ t: { v: string } }' could not be converted into 'U<number>'; at [read "t"][read "v"], string is not exactly number)";
"`number`";
CHECK(result.errors[0].location == Location{{4, 31}, {4, 52}});
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")
{
ScopedFastFlag _{FFlag::LuauBidirectionalInferenceCollectIndexerTypes, true};
CheckResult result = check(R"(
--!strict
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}});
}
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();

View file

@ -11,7 +11,6 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauTableCloneClonesType3)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAG(LuauArityMismatchOnUndersaturatedUnknownArguments)
@ -147,7 +146,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "sort_with_bad_predicate")
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = (FFlag::LuauImproveTypePathsInErrors) ? "Type\n\t"
const std::string expected =
"Type\n\t"
"'(number, number) -> boolean'"
"\ncould not be converted into\n\t"
"'((string, string) -> boolean)?'"
@ -159,20 +159,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "sort_with_bad_predicate")
"'(string, string) -> boolean'"
"\ncaused by:\n"
" Argument #1 type is not compatible.\n"
"Type 'string' could not be converted into 'number'"
: R"(Type
'(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')";
"Type 'string' could not be converted into 'number'";
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);
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
if (FFlag::LuauSolverV2)
CHECK_EQ(
"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`",
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
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;
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
TEST_SUITE_BEGIN("TypeInferExternTypes");
@ -547,14 +546,12 @@ local b: B = a
LUAU_REQUIRE_ERRORS(result);
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
if (FFlag::LuauSolverV2)
CHECK(
"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 "
"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
{
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_FASTFLAG(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauUngeneralizedTypesForRecursiveFunctions)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAG(LuauReduceUnionFollowUnionType)
LUAU_FASTFLAG(LuauArityMismatchOnUndersaturatedUnknownArguments)
LUAU_FASTFLAG(LuauHasPropProperBlock)
LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect)
@ -1313,7 +1311,7 @@ f(function(a, b, c, ...) return a + b end)
LUAU_REQUIRE_ERRORS(result);
std::string expected;
if (FFlag::LuauInstantiateInSubtyping && FFlag::LuauImproveTypePathsInErrors)
if (FFlag::LuauInstantiateInSubtyping)
{
expected = "Type\n\t"
"'<a>(number, number, a) -> number'"
@ -1322,16 +1320,7 @@ f(function(a, b, c, ...) return a + b end)
"\ncaused by:\n"
" Argument count mismatch. Function expects 3 arguments, but only 2 are specified";
}
else if (FFlag::LuauInstantiateInSubtyping)
{
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)
else
{
expected = "Type\n\t"
"'(number, number, *error-type*) -> number'"
@ -1340,15 +1329,6 @@ caused by:
"\ncaused by:\n"
" 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]));
@ -1544,19 +1524,13 @@ local b: B = a
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = (FFlag::LuauImproveTypePathsInErrors)
? "Type\n\t"
const std::string expected =
"Type\n\t"
"'(number, number) -> string'"
"\ncould not be converted into\n\t"
"'(number) -> string'"
"\ncaused by:\n"
" 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)";
" Argument count mismatch. Function expects 2 arguments, but only 1 is specified";
CHECK_EQ(expected, toString(result.errors[0]));
}
@ -1574,20 +1548,14 @@ local b: B = a
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = (FFlag::LuauImproveTypePathsInErrors) ? "Type\n\t"
const std::string expected =
"Type\n\t"
"'(number, number) -> string'"
"\ncould not be converted into\n\t"
"'(number, string) -> string'"
"\ncaused by:\n"
" Argument #2 type is not compatible.\n"
"Type 'string' could not be converted into 'number'"
: R"(Type
'(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')";
"Type 'string' could not be converted into 'number'";
CHECK_EQ(expected, toString(result.errors[0]));
}
@ -1605,18 +1573,13 @@ local b: B = a
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = (FFlag::LuauImproveTypePathsInErrors) ? "Type\n\t"
const std::string expected =
"Type\n\t"
"'(number, number) -> number'"
"\ncould not be converted into\n\t"
"'(number, number) -> (number, boolean)'"
"\ncaused by:\n"
" Function only returns 1 value, but 2 are required here"
: R"(Type
'(number, number) -> number'
could not be converted into
'(number, number) -> (number, boolean)'
caused by:
Function only returns 1 value, but 2 are required here)";
" Function only returns 1 value, but 2 are required here";
CHECK_EQ(expected, toString(result.errors[0]));
}
@ -1634,20 +1597,14 @@ local b: B = a
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = (FFlag::LuauImproveTypePathsInErrors) ? "Type\n\t"
const std::string expected =
"Type\n\t"
"'(number, number) -> string'"
"\ncould not be converted into\n\t"
"'(number, number) -> number'"
"\ncaused by:\n"
" Return type is not compatible.\n"
"Type 'string' could not be converted into 'number'"
: R"(Type
'(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')";
"Type 'string' could not be converted into 'number'";
CHECK_EQ(expected, toString(result.errors[0]));
}
@ -1665,20 +1622,14 @@ local b: B = a
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = (FFlag::LuauImproveTypePathsInErrors) ? "Type\n\t"
const std::string expected =
"Type\n\t"
"'(number, number) -> (number, string)'"
"\ncould not be converted into\n\t"
"'(number, number) -> (number, boolean)'"
"\ncaused by:\n"
" Return #2 type is not compatible.\n"
"Type 'string' could not be converted into 'boolean'"
: R"(Type
'(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')";
"Type 'string' could not be converted into 'boolean'";
CHECK_EQ(expected, toString(result.errors[0]));
}
@ -1828,24 +1779,6 @@ 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)"
);
}
else if (FFlag::LuauImproveTypePathsInErrors)
{
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')");
CHECK_EQ(toString(result.errors[1]), R"(Type 'string' could not be converted into 'number')");
}
else
{
LUAU_REQUIRE_ERROR_COUNT(2, result);
@ -1890,8 +1823,6 @@ function t:b() return 2 end -- not OK
LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauImproveTypePathsInErrors)
{
CHECK_EQ(
"Type\n\t"
"'(*error-type*) -> number'"
@ -1902,19 +1833,6 @@ function t:b() return 2 end -- not OK
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")
{
@ -2187,15 +2105,10 @@ z = y -- Not OK, so the line is colorable
LUAU_REQUIRE_ERROR_COUNT(1, result);
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)')"
"\ncould not be converted into\n\t"
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)";
R"('("blue" | "red") -> ("blue" | "red") -> ("blue" | "red") -> false'; none of the intersection parts are compatible)";
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);
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
if (FFlag::LuauSolverV2)
CHECK(
"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 "
"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
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);
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
if (FFlag::LuauSolverV2)
CHECK(
"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 "
"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
CHECK_EQ("Type 'string' could not be converted into 'number'", toString(result.errors[0]));
CHECK_EQ("() -> number", toString(requireType("num_or_str")));

View file

@ -12,7 +12,6 @@
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions)
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->wantedType), "T<string>");
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 "
"`string`\n\t"
" * 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";
" * accessing `b` results in `number` in the former type and `string` in the latter type, and `number` is not exactly `string`";
CHECK_EQ(mismatch->reason, reason);
}
else

View file

@ -10,7 +10,6 @@
using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
LUAU_FASTFLAG(LuauNarrowIntersectionNevers)
TEST_SUITE_BEGIN("IntersectionTypes");
@ -360,19 +359,13 @@ TEST_CASE_FIXTURE(Fixture, "table_intersection_write_sealed_indirect")
{
LUAU_REQUIRE_ERROR_COUNT(4, result);
const std::string expected = (FFlag::LuauImproveTypePathsInErrors)
? "Type\n\t"
const std::string expected =
"Type\n\t"
"'(string, number) -> string'"
"\ncould not be converted into\n\t"
"'(string) -> string'\n"
"caused by:\n"
" 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)";
" Argument count mismatch. Function expects 2 arguments, but only 1 is specified";
CHECK_EQ(expected, toString(result.errors[0]));
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);
const std::string expected = (FFlag::LuauImproveTypePathsInErrors)
? "Type\n\t"
const std::string expected =
"Type\n\t"
"'(string, number) -> string'"
"\ncould not be converted into\n\t"
"'(string) -> string'\n"
"caused by:\n"
" 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)";
" Argument count mismatch. Function expects 2 arguments, but only 1 is specified";
CHECK_EQ(expected, toString(result.errors[0]));
CHECK_EQ(toString(result.errors[1]), "Cannot add property 'z' to table 'XY'");
@ -440,18 +427,11 @@ local a: XYZ = 3
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = R"(Type 'number' could not be converted into 'X & Y & Z'
caused by:
Not all intersection parts are compatible.
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)
if (FFlag::LuauSolverV2)
{
const std::string expected = "Type "
const std::string expected =
"Type "
"'number'"
" could not be converted into "
"'X & Y & Z'; \n"
@ -459,13 +439,19 @@ Type 'number' could not be converted into 'X')";
" * 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 if (FFlag::LuauSolverV2)
CHECK_EQ(dcrExprected, toString(result.errors[0]));
else
{
const std::string expected = R"(Type 'number' could not be converted into 'X & Y & Z'
caused by:
Not all intersection parts are compatible.
Type 'number' could not be converted into 'X')";
CHECK_EQ(expected, toString(result.errors[0]));
}
}
TEST_CASE_FIXTURE(Fixture, "error_detailed_intersection_all")
{
@ -482,9 +468,10 @@ end
LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
if (FFlag::LuauSolverV2)
{
const std::string expected = "Type pack "
const std::string expected =
"Type pack "
"'X & Y & Z'"
" could not be converted into "
"'number'; \n"
@ -497,15 +484,6 @@ end
"type pack is `number`, and `Z` is not a subtype of `number`";
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
CHECK_EQ(
toString(result.errors[0]), R"(Type 'X & Y & Z' could not be converted into 'number'; none of the intersection parts are compatible)"
@ -551,9 +529,10 @@ TEST_CASE_FIXTURE(Fixture, "intersect_bool_and_false")
LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
if (FFlag::LuauSolverV2)
{
const std::string expected = "Type "
const std::string expected =
"Type "
"'boolean & false'"
" could not be converted into "
"'true'; \n"
@ -562,14 +541,6 @@ TEST_CASE_FIXTURE(Fixture, "intersect_bool_and_false")
" * the 2nd component of the intersection is `false`, which is not a subtype of `true`";
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
CHECK_EQ(
toString(result.errors[0]), "Type 'boolean & false' could not be converted into 'true'; none of the intersection parts are compatible"
@ -588,9 +559,10 @@ TEST_CASE_FIXTURE(Fixture, "intersect_false_and_bool_and_false")
LUAU_REQUIRE_ERROR_COUNT(1, result);
// TODO: odd stringification of `false & (boolean & false)`.)
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
if (FFlag::LuauSolverV2)
{
const std::string expected = "Type "
const std::string expected =
"Type "
"'boolean & false & false'"
" could not be converted into "
"'true'; \n"
@ -600,13 +572,6 @@ TEST_CASE_FIXTURE(Fixture, "intersect_false_and_bool_and_false")
" * the 3rd component of the intersection is `false`, which is not a subtype of `true`";
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
CHECK_EQ(
toString(result.errors[0]),
@ -623,7 +588,7 @@ TEST_CASE_FIXTURE(Fixture, "intersect_saturate_overloaded_functions")
end
)");
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
if (FFlag::LuauSolverV2)
{
const std::string expected1 =
"Type\n\t"
@ -654,33 +619,6 @@ TEST_CASE_FIXTURE(Fixture, "intersect_saturate_overloaded_functions")
CHECK_EQ(expected1, toString(result.errors[0]));
CHECK_EQ(expected2, toString(result.errors[1]));
}
else if (FFlag::LuauSolverV2)
{
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);
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]));
}
else
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
@ -706,22 +644,13 @@ TEST_CASE_FIXTURE(Fixture, "union_saturate_overloaded_functions")
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)'"
"\ncould not be converted into\n\t"
"'(boolean | number) -> boolean | number'; none of the intersection parts are compatible";
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")
@ -735,9 +664,10 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables")
LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
if (FFlag::LuauSolverV2)
{
const std::string expected = "Type "
const std::string expected =
"Type "
"'{ p: number?, q: number?, r: number? } & { p: number?, q: string? }'"
" could not be converted into "
"'{ p: nil }'; \n"
@ -748,22 +678,9 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables")
"accessing `p` results in `nil`, and `number` is not exactly `nil`";
CHECK_EQ(expected, toString(result.errors[0]));
}
else if (FFlag::LuauImproveTypePathsInErrors)
{
const std::string expected =
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]));
}
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
@ -781,9 +698,10 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_top_properties")
end
)");
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
if (FFlag::LuauSolverV2)
{
const std::string expected = "Type\n\t"
const std::string expected =
"Type\n\t"
"'{ p: number?, q: any } & { p: unknown, q: string? }'"
"\ncould not be converted into\n\t"
"'{ p: string?, q: number? }'; \n"
@ -802,31 +720,6 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_top_properties")
"component of the union as `number`, and `string?` is not exactly `number`";
CHECK_EQ(expected, toString(result.errors[0]));
}
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]));
}
else
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
@ -859,7 +752,7 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_returning_intersections")
end
)");
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
if (FFlag::LuauSolverV2)
{
const std::string expected1 =
"Type\n\t"
@ -904,42 +797,6 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_returning_intersections")
CHECK_EQ(expected1, toString(result.errors[0]));
CHECK_EQ(expected2, toString(result.errors[1]));
}
else if (FFlag::LuauSolverV2)
{
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);
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])
);
}
else
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
@ -967,15 +824,6 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic")
{
LUAU_REQUIRE_ERROR_COUNT(0, result);
}
else if (FFlag::LuauImproveTypePathsInErrors)
{
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]));
}
else
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
@ -1003,15 +851,6 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generics")
{
LUAU_REQUIRE_NO_ERRORS(result);
}
else if (FFlag::LuauImproveTypePathsInErrors)
{
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]));
}
else
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
@ -1034,7 +873,7 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic_packs")
end
)");
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
if (FFlag::LuauSolverV2)
{
const std::string expected1 =
"Type\n\t"
@ -1061,35 +900,6 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic_packs")
CHECK_EQ(expected1, toString(result.errors[0]));
CHECK_EQ(expected2, toString(result.errors[1]));
}
else if (FFlag::LuauSolverV2)
{
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);
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]));
}
else
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
@ -1117,23 +927,12 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_result")
LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauImproveTypePathsInErrors)
{
const std::string expected = "Type\n\t"
"'((nil) -> unknown) & ((number) -> number)'"
"\ncould not be converted into\n\t"
"'(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")
{
@ -1151,23 +950,13 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_arguments")
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?)'"
"\ncould not be converted into\n\t"
"'(number?) -> nil'; none of the intersection parts are compatible";
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")
{
@ -1180,7 +969,7 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_result")
end
)");
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
if (FFlag::LuauSolverV2)
{
const std::string expected1 =
"Type\n\t"
@ -1209,36 +998,6 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_result")
CHECK_EQ(expected1, toString(result.errors[0]));
CHECK_EQ(expected2, toString(result.errors[1]));
}
else if (FFlag::LuauSolverV2)
{
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);
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]));
}
else
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
@ -1261,7 +1020,7 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_arguments")
end
)");
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
if (FFlag::LuauSolverV2)
{
const std::string expected1 =
"Type\n\t"
@ -1294,34 +1053,6 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_arguments")
CHECK_EQ(expected1, toString(result.errors[0]));
CHECK_EQ(expected2, toString(result.errors[1]));
}
else if (FFlag::LuauSolverV2)
{
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);
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]));
}
else
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
@ -1347,23 +1078,12 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_overlapping_results_and_
LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauImproveTypePathsInErrors)
{
const std::string expected = "Type\n\t"
"'((number?) -> (...number)) & ((string?) -> number | string)'"
"\ncould not be converted into\n\t"
"'(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")
{
@ -1430,15 +1150,6 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_3")
{
LUAU_REQUIRE_NO_ERRORS(result);
}
else if (FFlag::LuauImproveTypePathsInErrors)
{
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]));
}
else
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
@ -1463,9 +1174,10 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_4")
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"
"'((a...) -> ()) & ((number, a...) -> number)'"
"\ncould not be converted into\n\t"
"'((a...) -> ()) & ((number, a...) -> number)'; \n"
@ -1476,27 +1188,6 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_4")
"the intersection, the function takes a tail of `a...`, and `a...` is not a supertype of `a...`";
CHECK(expected == toString(result.errors[0]));
}
else if (FFlag::LuauSolverV2)
{
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(
R"(Type
'((a...) -> ()) & ((number, a...) -> number)'
could not be converted into
'(number?) -> ()'; none of the intersection parts are compatible)",
toString(result.errors[0])
);
}
else
{
CHECK_EQ(

View file

@ -12,7 +12,6 @@
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect)
@ -465,20 +464,14 @@ local b: B.T = a
CheckResult result = frontend.check("game/C");
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 =
"Type 'T' from 'game/A' could not be converted into 'T' from 'game/B'; \n"
"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]));
}
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
{
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");
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 =
"Type 'T' from 'game/B' could not be converted into 'T' from 'game/C'; \n"
"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]));
}
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
{
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(LuauTypeInferRecursionLimit)
LUAU_FASTINT(LuauTypeInferTypePackLoopLimit)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
TEST_SUITE_BEGIN("ProvisionalTests");
@ -874,20 +873,13 @@ TEST_CASE_FIXTURE(Fixture, "assign_table_with_refined_property_with_a_similar_ty
else
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = (FFlag::LuauImproveTypePathsInErrors) ?
const std::string expected =
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)"
: 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)";
CHECK_EQ(expected, toString(result.errors[0]));
}

View file

@ -11,13 +11,13 @@
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauIntersectNotNil)
LUAU_FASTFLAG(LuauSkipNoRefineDuringRefinement)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAG(LuauFunctionCallsAreNotNilable)
LUAU_FASTFLAG(LuauSimplyRefineNotNil)
LUAU_FASTFLAG(LuauWeakNilRefinementType)
LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions)
LUAU_FASTFLAG(LuauSimplificationTableExternType)
LUAU_FASTFLAG(LuauAvoidDoubleNegation)
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")
{
ScopedFastFlag _{FFlag::LuauAvoidDoubleNegation, true};
CheckResult result = check(R"(
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)
{
// 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?) }) | { 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
{
@ -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")
{
ScopedFastFlag sff{FFlag::LuauSkipNoRefineDuringRefinement, true};
CheckResult result = check(R"(
type Item = {}

View file

@ -8,7 +8,6 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauPropagateExpectedTypesForCalls)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
TEST_SUITE_BEGIN("TypeSingletons");
@ -387,7 +386,7 @@ TEST_CASE_FIXTURE(Fixture, "table_properties_type_error_escapes")
LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
if (FFlag::LuauSolverV2)
{
const std::string expected = "Type\n\t"
"'{ [\"\\n\"]: number }'"
@ -395,13 +394,6 @@ TEST_CASE_FIXTURE(Fixture, "table_properties_type_error_escapes")
"'{ [\"<>\"]: number }'";
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
CHECK_EQ(
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);
if (FFlag::LuauImproveTypePathsInErrors)
{
const std::string expectedError = "Type\n\t"
"'{ result: string, success: boolean }'"
"\ncould not be converted into\n\t"
"'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")

View file

@ -24,12 +24,10 @@ LUAU_FASTFLAG(LuauFixIndexerSubtypingOrdering)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAG(LuauNonReentrantGeneralization2)
LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
LUAU_FASTFLAG(LuauBidirectionalInferenceCollectIndexerTypes)
LUAU_FASTFLAG(LuauBidirectionalFailsafe)
LUAU_FASTFLAG(LuauBidirectionalInferenceElideAssert)
LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect)
LUAU_FASTFLAG(LuauTypeCheckerStricterIndexCheck)
LUAU_FASTFLAG(LuauReportSubtypingErrors)
TEST_SUITE_BEGIN("TableTests");
@ -924,7 +922,7 @@ TEST_CASE_FIXTURE(Fixture, "sealed_table_indexers_must_unify")
LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
if (FFlag::LuauSolverV2)
{
CHECK(
"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])
);
}
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
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);
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
if (FFlag::LuauSolverV2)
{
CHECK_EQ(
"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])
);
}
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
{
MissingProperties* mp = get<MissingProperties>(result.errors[0]);
@ -2455,7 +2437,7 @@ local b: B = a
LUAU_REQUIRE_ERRORS(result);
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
if (FFlag::LuauSolverV2)
{
CHECK(
"Type 'A' could not be converted into 'B'; \n"
@ -2463,8 +2445,6 @@ local b: B = a
"`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
{
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);
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
if (FFlag::LuauSolverV2)
{
CHECK(
"Type 'A' could not be converted into 'B'; \n"
@ -2498,8 +2478,6 @@ local b: B = a
"`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
{
const std::string expected = R"(Type 'A' could not be converted into 'B'
@ -2525,7 +2503,7 @@ local b2 = setmetatable({ x = 2, y = 4 }, { __call = function(s, t) end });
local c2: typeof(a2) = b2
)");
const std::string expected1 = (FFlag::LuauImproveTypePathsInErrors) ?
const std::string expected1 =
R"(Type 'b1' could not be converted into 'a1'
caused by:
Type
@ -2534,17 +2512,8 @@ 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)"
: 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)";
const std::string expected2 = (FFlag::LuauImproveTypePathsInErrors) ?
const std::string expected2 =
R"(Type 'b2' could not be converted into 'a2'
caused by:
Type
@ -2555,22 +2524,10 @@ 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)"
: 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
// solver for two reasons:
@ -2586,18 +2543,6 @@ could not be converted into
"`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)
{
LUAU_REQUIRE_ERROR_COUNT(2, result);
@ -2651,7 +2596,7 @@ TEST_CASE_FIXTURE(Fixture, "error_detailed_indexer_key")
LUAU_REQUIRE_ERRORS(result);
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
if (FFlag::LuauSolverV2)
{
CHECK(
"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])
);
}
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
{
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);
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
if (FFlag::LuauSolverV2)
{
CHECK(
"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])
);
}
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
{
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);
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
if (FFlag::LuauSolverV2)
CHECK(
"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: "
"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
{
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 }
)");
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
if (FFlag::LuauSolverV2)
{
std::string expected =
"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`";
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
{
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")
{
ScopedFastFlag sffs[] = {{FFlag::LuauNonReentrantGeneralization2, true}, {FFlag::LuauReportSubtypingErrors, true}};
CheckResult result = check(R"(
local function f(s)
return s:absolutely_no_scalar_has_this_method()
@ -3861,36 +3787,6 @@ TEST_CASE_FIXTURE(Fixture, "scalar_is_not_a_subtype_of_a_compatible_polymorphic_
CHECK("typeof(string)" == toString(tm4->givenType));
CHECK("t1 where t1 = { read absolutely_no_scalar_has_this_method: (t1) -> (a...) }" == toString(tm4->wantedType));
}
else if (FFlag::LuauImproveTypePathsInErrors)
{
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:
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);
@ -4467,9 +4363,6 @@ TEST_CASE_FIXTURE(Fixture, "identify_all_problematic_table_fields")
LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauImproveTypePathsInErrors)
{
std::string expected =
"Type '{ a: string, b: boolean, c: number }' could not be converted into 'T'; \n"
"this is because \n\t"
@ -4478,15 +4371,6 @@ TEST_CASE_FIXTURE(Fixture, "identify_all_problematic_table_fields")
" * 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")
{
@ -5125,8 +5009,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "subtyping_with_a_metatable_table_path")
LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauImproveTypePathsInErrors)
{
CHECK_EQ(
"Type pack '{ @metatable { }, { } & { } }' could not be converted into 'Class'; \n"
"this is because \n\t"
@ -5139,16 +5021,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "subtyping_with_a_metatable_table_path")
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")
{
@ -5178,10 +5050,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "metatable_union_type")
TEST_CASE_FIXTURE(Fixture, "function_check_constraint_too_eager")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauBidirectionalInferenceCollectIndexerTypes, true},
};
ScopedFastFlag _{FFlag::LuauSolverV2, true};
CheckResult result = check(R"(
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")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauBidirectionalInferenceCollectIndexerTypes, true},
};
ScopedFastFlag _{FFlag::LuauSolverV2, true};
CheckResult result = check(R"(
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")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauBidirectionalInferenceCollectIndexerTypes, true},
};
ScopedFastFlag _{FFlag::LuauSolverV2, true};
auto result = check(R"(
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")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauBidirectionalInferenceCollectIndexerTypes, true},
};
ScopedFastFlag _{FFlag::LuauSolverV2, true};
auto result = check((R"(
local function getStatus(): string
@ -5572,7 +5432,6 @@ TEST_CASE_FIXTURE(Fixture, "bigger_nested_table_causes_big_type_error")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauImproveTypePathsInErrors, true},
};
auto result = check(R"(
@ -5606,7 +5465,8 @@ TEST_CASE_FIXTURE(Fixture, "bigger_nested_table_causes_big_type_error")
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
std::string expected = "Type\n\t"
std::string expected =
"Type\n\t"
"'{Dir | File | { children: ({Dir | File | { content: string?, path: string, type: \"file\" }} | {Dir | File})?, name: "
"string, type: \"dir\" }}'"
"\ncould not be converted into\n\t"
@ -5621,7 +5481,6 @@ TEST_CASE_FIXTURE(Fixture, "bigger_nested_table_causes_big_type_error")
TEST_CASE_FIXTURE(Fixture, "unsafe_bidirectional_mutation")
{
ScopedFastFlag _{FFlag::LuauBidirectionalFailsafe, true};
// It's kind of suspect that we allow multiple definitions of keys in
// a single table.
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")
{
ScopedFastFlag _{FFlag::LuauBidirectionalFailsafe, true};
// This has a bunch of errors, we really just need it to not crash / assert.
std::ignore = check(R"(
--!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")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauBidirectionalInferenceCollectIndexerTypes, true},
{FFlag::LuauBidirectionalInferenceElideAssert, true},
};
ScopedFastFlag _{FFlag::LuauBidirectionalInferenceElideAssert, true};
CheckResult result = check(R"(
function f(_: { [string]: {unknown}} ) end
f(
@ -5694,8 +5550,6 @@ TEST_CASE_FIXTURE(Fixture, "fuzz_match_literal_type_crash_again")
TEST_CASE_FIXTURE(Fixture, "type_mismatch_in_dict")
{
ScopedFastFlag sff{FFlag::LuauBidirectionalInferenceCollectIndexerTypes, true};
CheckResult result = check(R"(
--!strict
local dict: {[string]: boolean} = {

View file

@ -24,20 +24,19 @@ LUAU_FASTINT(LuauNormalizeCacheLimit)
LUAU_FASTINT(LuauRecursionLimit)
LUAU_FASTINT(LuauTypeInferTypePackLoopLimit)
LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauAstTypeGroup3)
LUAU_FASTFLAG(LuauNewNonStrictWarnOnUnknownGlobals)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
LUAU_FASTFLAG(LuauTypeCheckerAcceptNumberConcats)
LUAU_FASTFLAG(LuauPreprocessTypestatedArgument)
LUAU_FASTFLAG(LuauCacheInferencePerAstExpr)
LUAU_FASTFLAG(LuauLimitIterationWhenCheckingArgumentCounts)
LUAU_FASTFLAG(LuauMagicFreezeCheckBlocked)
LUAU_FASTFLAG(LuauMagicFreezeCheckBlocked2)
LUAU_FASTFLAG(LuauNonReentrantGeneralization2)
LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect)
LUAU_FASTFLAG(LuauHasPropProperBlock)
LUAU_FASTFLAG(LuauStringPartLengthLimit)
LUAU_FASTFLAG(LuauSimplificationRecheckAssumption)
LUAU_FASTFLAG(LuauAlwaysResolveAstTypes)
LUAU_FASTFLAG(LuauReportSubtypingErrors)
LUAU_FASTFLAG(LuauAvoidDoubleNegation)
using namespace Luau;
@ -1144,8 +1143,12 @@ TEST_CASE_FIXTURE(Fixture, "cli_50041_committing_txnlog_in_apollo_client_error")
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);
const std::string expected =
"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'";
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
{
LUAU_REQUIRE_NO_ERRORS(result);
@ -1235,20 +1213,28 @@ TEST_CASE_FIXTURE(Fixture, "type_infer_recursion_limit_normalizer")
if (FFlag::LuauSolverV2)
{
CHECK(3 == result.errors.size());
if (FFlag::LuauAstTypeGroup3)
if (FFlag::LuauReportSubtypingErrors)
{
CHECK(4 == result.errors.size());
CHECK(Location{{2, 22}, {2, 42}} == result.errors[0].location);
else
CHECK(Location{{2, 22}, {2, 41}} == result.errors[0].location);
CHECK(Location{{3, 22}, {3, 42}} == result.errors[1].location);
if (FFlag::LuauAstTypeGroup3)
CHECK(Location{{3, 22}, {3, 41}} == result.errors[2].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
CHECK(Location{{3, 23}, {3, 40}} == result.errors[2].location);
{
CHECK(3 == result.errors.size());
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_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[1]));
CHECK_EQ("Code is too complex to typecheck! Consider simplifying the code around this area", toString(result.errors[2]));
}
}
else
{
CHECK(1 == result.errors.size());
@ -1994,7 +1980,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "assert_table_freeze_constraint_solving")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauMagicFreezeCheckBlocked, true}
{FFlag::LuauMagicFreezeCheckBlocked2, true}
};
LUAU_REQUIRE_NO_ERRORS(check(R"(
local f = table.freeze
@ -2006,7 +1992,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_assert_table_freeze_constraint_solving"
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauMagicFreezeCheckBlocked, true}
{FFlag::LuauMagicFreezeCheckBlocked2, true}
};
// This is the original fuzzer version of the above issue.
CheckResult results = check(R"(
@ -2029,7 +2015,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "cyclic_unification_aborts_eventually" * doct
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, false},
{FFlag::LuauInstantiateInSubtyping, true},
{FFlag::LuauLimitIterationWhenCheckingArgumentCounts, true},
};
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();

View file

@ -12,8 +12,9 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAG(LuauReportSubtypingErrors)
LUAU_FASTFLAG(LuauTrackInferredFunctionTypeFromCall)
TEST_SUITE_BEGIN("TypePackTests");
@ -957,10 +958,11 @@ a = b
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"
"'() -> (number, ...boolean)'"
"\ncould not be converted into\n\t"
"'() -> (number, ...string)'; \n"
@ -969,25 +971,6 @@ a = b
CHECK(expected == toString(result.errors[0]));
}
else if (FFlag::LuauSolverV2)
{
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
'() -> (number, ...boolean)'
could not be converted into
'() -> (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
@ -1077,6 +1060,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "detect_cyclic_typepacks")
TEST_CASE_FIXTURE(BuiltinsFixture, "detect_cyclic_typepacks2")
{
ScopedFastFlag sffs[] = {{FFlag::LuauReportSubtypingErrors, true}, {FFlag::LuauTrackInferredFunctionTypeFromCall, true}};
CheckResult result = check(R"(
function _(l0:((typeof((pcall)))|((((t0)->())|(typeof(-67108864)))|(any)))|(any),...):(((typeof(0))|(any))|(any),typeof(-67108864),any)
xpcall(_,_,_)
@ -1121,16 +1106,11 @@ TEST_CASE_FIXTURE(Fixture, "unify_variadic_tails_in_arguments_free")
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
if (FFlag::LuauSolverV2)
CHECK(
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`"
);
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
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(LuauDoNotAddUpvalueTypesToLocalType)
LUAU_FASTFLAG(LuauDfgIfBlocksShouldRespectControlFlow)
LUAU_FASTFLAG(LuauReportSubtypingErrors)
LUAU_FASTFLAG(LuauNonReentrantGeneralization2)
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")
{
// early return if the flag isn't set since this is blocking gated commits
if (!FFlag::LuauSolverV2)
return;
ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauReportSubtypingErrors, true}, {FFlag::LuauNonReentrantGeneralization2, true}};
CheckResult result = check(R"(
local f

View file

@ -11,7 +11,6 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
TEST_SUITE_BEGIN("UnionTypes");
@ -541,7 +540,7 @@ end
LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
if (FFlag::LuauSolverV2)
{
CHECK_EQ(
@ -553,16 +552,6 @@ end
" * 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
{
CHECK_EQ(toString(result.errors[0]), R"(Type 'X | Y | Z' could not be converted into '{| w: number |}'
@ -683,23 +672,13 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_union_write_indirect")
LUAU_REQUIRE_ERROR_COUNT(1, result);
// 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'"
"\ncould not be converted into\n\t"
"'((number) -> string) | ((number) -> string)'; none of the union options are compatible";
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")
{
@ -785,23 +764,13 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_mentioning_generic_typepacks")
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...)'"
"\ncould not be converted into\n\t"
"'((number) -> number) | ((number?, a...) -> (number?, a...))'; none of the union options are compatible";
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")
{
@ -816,23 +785,13 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_arities")
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?'"
"\ncould not be converted into\n\t"
"'((number) -> nil) | ((number, string?) -> number)'; none of the union options are compatible";
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")
{
@ -847,23 +806,13 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_arities")
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'"
"\ncould not be converted into\n\t"
"'(() -> (string, string)) | (() -> number)'; none of the union options are compatible";
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")
{
@ -878,23 +827,13 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_variadics")
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?)'"
"\ncould not be converted into\n\t"
"'((...string?) -> (...number)) | ((...string?) -> nil)'; none of the union options are compatible";
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")
{
@ -906,29 +845,15 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_variadics")
)");
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"
"'(number) -> ()'"
"\ncould not be converted into\n\t"
"'((...number?) -> ()) | ((number?) -> ())'";
CHECK(expected == toString(result.errors[0]));
}
else if (FFlag::LuauSolverV2)
{
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
'(number) -> ()'
could not be converted into
'((...number?) -> ()) | ((number?) -> ())'; none of the union options are compatible)";
CHECK_EQ(expected, toString(result.errors[0]));
}
else
{
const std::string expected = R"(Type
@ -952,23 +877,13 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_variadics
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)'"
"\ncould not be converted into\n\t"
"'(() -> (...number)) | (() -> number)'; none of the union options are compatible";
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")
{

View file

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