mirror of
https://github.com/luau-lang/luau.git
synced 2025-08-26 11:27:08 +01:00
Sync to upstream/release/681 (#1902)
# What's Changed? Short week, so a slightly shorter release! This one has been focused on improving polish in fragment autocomplete and the new solver. ## New Type Solver * Fix a bug where we didn't infer self types correctly under bidirectional type inference. * Improve the memory consumption of the new solver by reducing the number of expensive allocations performed by `Substitution`. * The New non strict Mode shouldn't issue errors when we call checked functions with `never` values. * Extend the number of cases in which the new non strict mode can report unknown symbols. * Fix a bug where `and` and `or` expressions didn't correctly forward information computed by their operands. This should allow more programs using these expressions to complete typechecking correctly. * Small performance improvements for `Generalization` ## Fragment Autocomplete * Fragment autocomplete provides richer autofill information when typing `self.|` * Fragment autocomplete now uses refinement information computed in the new solver to provide more accurate incremental completion. ## Code Generation * Fix a bug where Codegen could sometimes try to execute a non-executable page --- Co-authored-by: Ariel Weiss <aaronweiss@roblox.com> Co-authored-by: Hunter Goldstein <hgoldstein@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: Vyacheslav Egorov <vegorov@roblox.com> Co-authored-by: Ariel Weiss <aaronweiss@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com>
This commit is contained in:
parent
e190754565
commit
60cd88af32
24 changed files with 1058 additions and 80 deletions
|
@ -283,6 +283,25 @@ struct SimplifyConstraint
|
|||
TypeId ty;
|
||||
};
|
||||
|
||||
// push_function_type_constraint expectedFunctionType => functionType
|
||||
//
|
||||
// Attempt to "push" the types of `expectedFunctionType` into `functionType`,
|
||||
// assuming that `expr` is a lambda who's ungeneralized type is `functionType`.
|
||||
// Similar to `FunctionCheckConstraint`. For example:
|
||||
//
|
||||
// local Foo = {} :: { bar : (number) -> () }
|
||||
//
|
||||
// function Foo.bar(x) end
|
||||
//
|
||||
// This will force `x` to be inferred as `number`.
|
||||
struct PushFunctionTypeConstraint
|
||||
{
|
||||
TypeId expectedFunctionType;
|
||||
TypeId functionType;
|
||||
NotNull<AstExprFunction> expr;
|
||||
bool isSelf;
|
||||
};
|
||||
|
||||
using ConstraintV = Variant<
|
||||
SubtypeConstraint,
|
||||
PackSubtypeConstraint,
|
||||
|
@ -302,7 +321,8 @@ using ConstraintV = Variant<
|
|||
ReducePackConstraint,
|
||||
EqualityConstraint,
|
||||
TableCheckConstraint,
|
||||
SimplifyConstraint>;
|
||||
SimplifyConstraint,
|
||||
PushFunctionTypeConstraint>;
|
||||
|
||||
struct Constraint
|
||||
{
|
||||
|
|
|
@ -494,6 +494,9 @@ private:
|
|||
);
|
||||
|
||||
TypeId simplifyUnion(const ScopePtr& scope, Location location, TypeId left, TypeId right);
|
||||
|
||||
void updateRValueRefinements(const ScopePtr& scope, DefId def, TypeId ty) const;
|
||||
void updateRValueRefinements(Scope* scope, DefId def, TypeId ty) const;
|
||||
};
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -256,6 +256,8 @@ public:
|
|||
|
||||
bool tryDispatch(const SimplifyConstraint& c, NotNull<const Constraint> constraint);
|
||||
|
||||
bool tryDispatch(const PushFunctionTypeConstraint& c, NotNull<const Constraint> constraint);
|
||||
|
||||
// for a, ... in some_table do
|
||||
// also handles __iter metamethod
|
||||
bool tryDispatchIterableTable(TypeId iteratorTy, const IterableConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||
|
|
|
@ -46,6 +46,8 @@ struct DataFlowGraph
|
|||
|
||||
const RefinementKey* getRefinementKey(const AstExpr* expr) const;
|
||||
|
||||
std::optional<Symbol> getSymbolFromDef(const Def* def) const;
|
||||
|
||||
private:
|
||||
DataFlowGraph(NotNull<DefArena> defArena, NotNull<RefinementKeyArena> keyArena);
|
||||
|
||||
|
@ -63,6 +65,7 @@ private:
|
|||
// There's no AstStatDeclaration, and it feels useless to introduce it just to enforce an invariant in one place.
|
||||
// All keys in this maps are really only statements that ambiently declares a symbol.
|
||||
DenseHashMap<const AstStat*, const Def*> declaredDefs{nullptr};
|
||||
DenseHashMap<const Def*, Symbol> defToSymbol{nullptr};
|
||||
|
||||
DenseHashMap<const AstExpr*, const RefinementKey*> astRefinementKeys{nullptr};
|
||||
friend struct DataFlowGraphBuilder;
|
||||
|
|
|
@ -152,7 +152,9 @@ struct Module
|
|||
// Once a module has been typechecked, we clone its public interface into a
|
||||
// separate arena. This helps us to force Type ownership into a DAG rather
|
||||
// than a DCG.
|
||||
void clonePublicInterface(NotNull<BuiltinTypes> builtinTypes, InternalErrorReporter& ice);
|
||||
void clonePublicInterface_DEPRECATED(NotNull<BuiltinTypes> builtinTypes, InternalErrorReporter& ice);
|
||||
|
||||
void clonePublicInterface(NotNull<BuiltinTypes> builtinTypes, InternalErrorReporter& ice, SolverMode mode);
|
||||
};
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include "Luau/VisitType.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization4)
|
||||
LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -222,6 +223,14 @@ DenseHashSet<TypeId> Constraint::getMaybeMutatedFreeTypes_DEPRECATED() const
|
|||
rci.traverse(tcc->exprType);
|
||||
}
|
||||
|
||||
if (FFlag::LuauPushFunctionTypesInFunctionStatement)
|
||||
{
|
||||
if (auto pftc = get<PushFunctionTypeConstraint>(*this))
|
||||
{
|
||||
rci.traverse(pftc->functionType);
|
||||
}
|
||||
}
|
||||
|
||||
return types;
|
||||
}
|
||||
|
||||
|
@ -318,6 +327,14 @@ TypeIds Constraint::getMaybeMutatedFreeTypes() const
|
|||
rci.traverse(tcc->exprType);
|
||||
}
|
||||
|
||||
if (FFlag::LuauPushFunctionTypesInFunctionStatement)
|
||||
{
|
||||
if (auto pftc = get<PushFunctionTypeConstraint>(*this))
|
||||
{
|
||||
rci.traverse(pftc->functionType);
|
||||
}
|
||||
}
|
||||
|
||||
return types;
|
||||
}
|
||||
|
||||
|
|
|
@ -52,6 +52,8 @@ LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
|
|||
LUAU_FASTFLAGVARIABLE(LuauFollowTypeAlias)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFollowExistingTypeFunction)
|
||||
LUAU_FASTFLAGVARIABLE(LuauRefineTablesWithReadType)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFragmentAutocompleteTracksRValueRefinements)
|
||||
LUAU_FASTFLAGVARIABLE(LuauPushFunctionTypesInFunctionStatement)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -776,8 +778,10 @@ void ConstraintGenerator::applyRefinements(const ScopePtr& scope, Location locat
|
|||
|
||||
if (partition.shouldAppendNilType)
|
||||
ty = createTypeFunctionInstance(builtinTypeFunctions().weakoptionalFunc, {ty}, {}, scope, location);
|
||||
|
||||
scope->rvalueRefinements[def] = ty;
|
||||
if (FFlag::LuauFragmentAutocompleteTracksRValueRefinements)
|
||||
updateRValueRefinements(scope, def, ty);
|
||||
else
|
||||
scope->rvalueRefinements[def] = ty;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1283,7 +1287,10 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFor* for_)
|
|||
|
||||
DefId def = dfg->getDef(for_->var);
|
||||
forScope->lvalueTypes[def] = annotationTy;
|
||||
forScope->rvalueRefinements[def] = annotationTy;
|
||||
if (FFlag::LuauFragmentAutocompleteTracksRValueRefinements)
|
||||
updateRValueRefinements(forScope, def, annotationTy);
|
||||
else
|
||||
forScope->rvalueRefinements[def] = annotationTy;
|
||||
|
||||
visit(forScope, for_->body);
|
||||
|
||||
|
@ -1435,9 +1442,15 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocalFuncti
|
|||
|
||||
DefId def = dfg->getDef(function->name);
|
||||
scope->lvalueTypes[def] = functionType;
|
||||
scope->rvalueRefinements[def] = functionType;
|
||||
if (FFlag::LuauFragmentAutocompleteTracksRValueRefinements)
|
||||
updateRValueRefinements(scope, def, functionType);
|
||||
else
|
||||
scope->rvalueRefinements[def] = functionType;
|
||||
sig.bodyScope->lvalueTypes[def] = sig.signature;
|
||||
sig.bodyScope->rvalueRefinements[def] = sig.signature;
|
||||
if (FFlag::LuauFragmentAutocompleteTracksRValueRefinements)
|
||||
updateRValueRefinements(sig.bodyScope, def, sig.signature);
|
||||
else
|
||||
sig.bodyScope->rvalueRefinements[def] = sig.signature;
|
||||
|
||||
Checkpoint start = checkpoint(this);
|
||||
checkFunctionBody(sig.bodyScope, function->func);
|
||||
|
@ -1497,20 +1510,77 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f
|
|||
{
|
||||
sig.bodyScope->bindings[localName->local] = Binding{sig.signature, localName->location};
|
||||
sig.bodyScope->lvalueTypes[def] = sig.signature;
|
||||
sig.bodyScope->rvalueRefinements[def] = sig.signature;
|
||||
if (FFlag::LuauFragmentAutocompleteTracksRValueRefinements)
|
||||
updateRValueRefinements(sig.bodyScope, def, sig.signature);
|
||||
else
|
||||
sig.bodyScope->rvalueRefinements[def] = sig.signature;
|
||||
}
|
||||
else if (AstExprGlobal* globalName = function->name->as<AstExprGlobal>())
|
||||
{
|
||||
sig.bodyScope->bindings[globalName->name] = Binding{sig.signature, globalName->location};
|
||||
sig.bodyScope->lvalueTypes[def] = sig.signature;
|
||||
sig.bodyScope->rvalueRefinements[def] = sig.signature;
|
||||
if (FFlag::LuauFragmentAutocompleteTracksRValueRefinements)
|
||||
updateRValueRefinements(sig.bodyScope, def, sig.signature);
|
||||
else
|
||||
sig.bodyScope->rvalueRefinements[def] = sig.signature;
|
||||
}
|
||||
else if (AstExprIndexName* indexName = function->name->as<AstExprIndexName>())
|
||||
{
|
||||
sig.bodyScope->rvalueRefinements[def] = sig.signature;
|
||||
if (FFlag::LuauFragmentAutocompleteTracksRValueRefinements)
|
||||
updateRValueRefinements(sig.bodyScope, def, sig.signature);
|
||||
else
|
||||
sig.bodyScope->rvalueRefinements[def] = sig.signature;
|
||||
}
|
||||
|
||||
if (FFlag::LuauPushFunctionTypesInFunctionStatement)
|
||||
{
|
||||
if (auto indexName = function->name->as<AstExprIndexName>())
|
||||
{
|
||||
auto beginProp = checkpoint(this);
|
||||
auto [fn, _] = check(scope, indexName);
|
||||
auto endProp = checkpoint(this);
|
||||
auto pftc = addConstraint(
|
||||
sig.signatureScope,
|
||||
function->func->location,
|
||||
PushFunctionTypeConstraint{
|
||||
fn,
|
||||
sig.signature,
|
||||
NotNull{function->func},
|
||||
/* isSelf */ indexName->op == ':',
|
||||
}
|
||||
);
|
||||
forEachConstraint(
|
||||
beginProp,
|
||||
endProp,
|
||||
this,
|
||||
[pftc](const ConstraintPtr& c)
|
||||
{
|
||||
pftc->dependencies.emplace_back(c.get());
|
||||
}
|
||||
);
|
||||
auto beginBody = checkpoint(this);
|
||||
checkFunctionBody(sig.bodyScope, function->func);
|
||||
auto endBody = checkpoint(this);
|
||||
forEachConstraint(
|
||||
beginBody,
|
||||
endBody,
|
||||
this,
|
||||
[pftc](const ConstraintPtr& c)
|
||||
{
|
||||
c->dependencies.push_back(pftc);
|
||||
}
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
checkFunctionBody(sig.bodyScope, function->func);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
checkFunctionBody(sig.bodyScope, function->func);
|
||||
}
|
||||
|
||||
checkFunctionBody(sig.bodyScope, function->func);
|
||||
Checkpoint end = checkpoint(this);
|
||||
|
||||
TypeId generalizedType = arena->addType(BlockedType{});
|
||||
|
@ -1582,7 +1652,10 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f
|
|||
if (generalizedType == nullptr)
|
||||
ice->ice("generalizedType == nullptr", function->location);
|
||||
|
||||
scope->rvalueRefinements[def] = generalizedType;
|
||||
if (FFlag::LuauFragmentAutocompleteTracksRValueRefinements)
|
||||
updateRValueRefinements(scope, def, generalizedType);
|
||||
else
|
||||
scope->rvalueRefinements[def] = generalizedType;
|
||||
|
||||
return ControlFlow::None;
|
||||
}
|
||||
|
@ -1890,7 +1963,10 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareGlob
|
|||
|
||||
DefId def = dfg->getDef(global);
|
||||
rootScope->lvalueTypes[def] = globalTy;
|
||||
rootScope->rvalueRefinements[def] = globalTy;
|
||||
if (FFlag::LuauFragmentAutocompleteTracksRValueRefinements)
|
||||
updateRValueRefinements(rootScope, def, globalTy);
|
||||
else
|
||||
rootScope->rvalueRefinements[def] = globalTy;
|
||||
|
||||
return ControlFlow::None;
|
||||
}
|
||||
|
@ -2149,7 +2225,10 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareFunc
|
|||
|
||||
DefId def = dfg->getDef(global);
|
||||
rootScope->lvalueTypes[def] = fnType;
|
||||
rootScope->rvalueRefinements[def] = fnType;
|
||||
if (FFlag::LuauFragmentAutocompleteTracksRValueRefinements)
|
||||
updateRValueRefinements(rootScope, def, fnType);
|
||||
else
|
||||
rootScope->rvalueRefinements[def] = fnType;
|
||||
|
||||
return ControlFlow::None;
|
||||
}
|
||||
|
@ -2397,7 +2476,10 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall*
|
|||
|
||||
DefId def = dfg->getDef(targetLocal);
|
||||
scope->lvalueTypes[def] = resultTy; // TODO: typestates: track this as an assignment
|
||||
scope->rvalueRefinements[def] = resultTy; // TODO: typestates: track this as an assignment
|
||||
if (FFlag::LuauFragmentAutocompleteTracksRValueRefinements)
|
||||
updateRValueRefinements(scope, def, resultTy); // TODO: typestates: track this as an assignment
|
||||
else
|
||||
scope->rvalueRefinements[def] = resultTy; // TODO: typestates: track this as an assignment
|
||||
|
||||
// HACK: If we have a targetLocal, it has already been added to the
|
||||
// inferredBindings table. We want to replace it so that we don't
|
||||
|
@ -2419,7 +2501,10 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall*
|
|||
if (auto def = dfg->getDefOptional(targetExpr))
|
||||
{
|
||||
scope->lvalueTypes[*def] = resultTy;
|
||||
scope->rvalueRefinements[*def] = resultTy;
|
||||
if (FFlag::LuauFragmentAutocompleteTracksRValueRefinements)
|
||||
updateRValueRefinements(scope, *def, resultTy);
|
||||
else
|
||||
scope->rvalueRefinements[*def] = resultTy;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2729,7 +2814,10 @@ Inference ConstraintGenerator::checkIndexName(
|
|||
if (auto ty = lookup(scope, indexLocation, key->def))
|
||||
return Inference{*ty, refinementArena.proposition(key, builtinTypes->truthyType)};
|
||||
|
||||
scope->rvalueRefinements[key->def] = result;
|
||||
if (FFlag::LuauFragmentAutocompleteTracksRValueRefinements)
|
||||
updateRValueRefinements(scope, key->def, result);
|
||||
else
|
||||
scope->rvalueRefinements[key->def] = result;
|
||||
}
|
||||
|
||||
if (key)
|
||||
|
@ -2763,8 +2851,10 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprIndexExpr* in
|
|||
{
|
||||
if (auto ty = lookup(scope, indexExpr->location, key->def))
|
||||
return Inference{*ty, refinementArena.proposition(key, builtinTypes->truthyType)};
|
||||
|
||||
scope->rvalueRefinements[key->def] = result;
|
||||
if (FFlag::LuauFragmentAutocompleteTracksRValueRefinements)
|
||||
updateRValueRefinements(scope, key->def, result);
|
||||
else
|
||||
scope->rvalueRefinements[key->def] = result;
|
||||
}
|
||||
|
||||
auto c = addConstraint(scope, indexExpr->expr->location, HasIndexerConstraint{result, obj, indexType});
|
||||
|
@ -3533,7 +3623,10 @@ ConstraintGenerator::FunctionSignature ConstraintGenerator::checkFunctionSignatu
|
|||
|
||||
DefId def = dfg->getDef(fn->self);
|
||||
signatureScope->lvalueTypes[def] = selfType;
|
||||
signatureScope->rvalueRefinements[def] = selfType;
|
||||
if (FFlag::LuauFragmentAutocompleteTracksRValueRefinements)
|
||||
updateRValueRefinements(signatureScope, def, selfType);
|
||||
else
|
||||
signatureScope->rvalueRefinements[def] = selfType;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < fn->args.size; ++i)
|
||||
|
@ -3558,7 +3651,10 @@ ConstraintGenerator::FunctionSignature ConstraintGenerator::checkFunctionSignatu
|
|||
|
||||
DefId def = dfg->getDef(local);
|
||||
signatureScope->lvalueTypes[def] = argTy;
|
||||
signatureScope->rvalueRefinements[def] = argTy;
|
||||
if (FFlag::LuauFragmentAutocompleteTracksRValueRefinements)
|
||||
updateRValueRefinements(signatureScope, def, argTy);
|
||||
else
|
||||
signatureScope->rvalueRefinements[def] = argTy;
|
||||
}
|
||||
|
||||
TypePackId varargPack = nullptr;
|
||||
|
@ -4499,4 +4595,17 @@ TypeId ConstraintGenerator::simplifyUnion(const ScopePtr& scope, Location locati
|
|||
return ::Luau::simplifyUnion(builtinTypes, arena, left, right).result;
|
||||
}
|
||||
|
||||
void ConstraintGenerator::updateRValueRefinements(const ScopePtr& scope, DefId def, TypeId ty) const
|
||||
{
|
||||
updateRValueRefinements(scope.get(), def, ty);
|
||||
}
|
||||
|
||||
void ConstraintGenerator::updateRValueRefinements(Scope* scope, DefId def, TypeId ty) const
|
||||
{
|
||||
scope->rvalueRefinements[def] = ty;
|
||||
if (auto sym = dfg->getSymbolFromDef(def))
|
||||
scope->refinements[*sym] = ty;
|
||||
}
|
||||
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -42,6 +42,8 @@ LUAU_FASTFLAGVARIABLE(LuauMissingFollowInAssignIndexConstraint)
|
|||
LUAU_FASTFLAGVARIABLE(LuauRemoveTypeCallsForReadWriteProps)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTableLiteralSubtypeCheckFunctionCalls)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUseOrderedTypeSetsInConstraints)
|
||||
LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement)
|
||||
LUAU_FASTFLAG(LuauAvoidExcessiveTypeCopying)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -776,7 +778,7 @@ void ConstraintSolver::generalizeOneType(TypeId ty)
|
|||
ty = follow(ty);
|
||||
const FreeType* freeTy = get<FreeType>(ty);
|
||||
|
||||
std::string saveme = toString(ty, opts);
|
||||
std::string saveme = FFlag::DebugLuauLogSolver ? toString(ty, opts) : "[FFlag::DebugLuauLogSolver Off]";
|
||||
|
||||
// Some constraints (like prim) will also replace a free type with something
|
||||
// concrete. If so, our work is already done.
|
||||
|
@ -904,6 +906,8 @@ bool ConstraintSolver::tryDispatch(NotNull<const Constraint> constraint, bool fo
|
|||
success = tryDispatch(*eqc, constraint);
|
||||
else if (auto sc = get<SimplifyConstraint>(*constraint))
|
||||
success = tryDispatch(*sc, constraint);
|
||||
else if (auto pftc = get<PushFunctionTypeConstraint>(*constraint))
|
||||
success = tryDispatch(*pftc, constraint);
|
||||
else
|
||||
LUAU_ASSERT(false);
|
||||
|
||||
|
@ -1845,7 +1849,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<con
|
|||
|
||||
for (size_t i = 0; i < c.callSite->args.size && i + typeOffset < expectedArgs.size() && i + typeOffset < argPackHead.size(); ++i)
|
||||
{
|
||||
const TypeId expectedArgTy = follow(expectedArgs[i + typeOffset]);
|
||||
TypeId expectedArgTy = follow(expectedArgs[i + typeOffset]);
|
||||
const TypeId actualArgTy = follow(argPackHead[i + typeOffset]);
|
||||
AstExpr* expr = unwrapGroup(c.callSite->args.data[i]);
|
||||
|
||||
|
@ -1875,21 +1879,38 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<con
|
|||
else if (expr->is<AstExprConstantBool>() || expr->is<AstExprConstantString>() || expr->is<AstExprConstantNumber>() ||
|
||||
expr->is<AstExprConstantNil>() || (FFlag::LuauTableLiteralSubtypeCheckFunctionCalls && expr->is<AstExprTable>()))
|
||||
{
|
||||
ReferentialReplacer replacer{arena, NotNull{&replacements}, NotNull{&replacementPacks}};
|
||||
if (auto res = replacer.substitute(expectedArgTy))
|
||||
if (FFlag::LuauAvoidExcessiveTypeCopying)
|
||||
{
|
||||
if (FFlag::LuauTableLiteralSubtypeCheckFunctionCalls)
|
||||
if (ContainsGenerics::hasGeneric(expectedArgTy, NotNull{&genericTypesAndPacks}))
|
||||
{
|
||||
// If we do this replacement and there are type
|
||||
// functions in the final type, then we need to
|
||||
// ensure those get reduced.
|
||||
InstantiationQueuer queuer{constraint->scope, constraint->location, this};
|
||||
queuer.traverse(*res);
|
||||
ReferentialReplacer replacer{arena, NotNull{&replacements}, NotNull{&replacementPacks}};
|
||||
if (auto res = replacer.substitute(expectedArgTy))
|
||||
{
|
||||
InstantiationQueuer queuer{constraint->scope, constraint->location, this};
|
||||
queuer.traverse(*res);
|
||||
expectedArgTy = *res;
|
||||
}
|
||||
}
|
||||
u2.unify(actualArgTy, *res);
|
||||
u2.unify(actualArgTy, expectedArgTy);
|
||||
}
|
||||
else
|
||||
u2.unify(actualArgTy, expectedArgTy);
|
||||
{
|
||||
ReferentialReplacer replacer{arena, NotNull{&replacements}, NotNull{&replacementPacks}};
|
||||
if (auto res = replacer.substitute(expectedArgTy))
|
||||
{
|
||||
if (FFlag::LuauTableLiteralSubtypeCheckFunctionCalls)
|
||||
{
|
||||
// If we do this replacement and there are type
|
||||
// functions in the final type, then we need to
|
||||
// ensure those get reduced.
|
||||
InstantiationQueuer queuer{constraint->scope, constraint->location, this};
|
||||
queuer.traverse(*res);
|
||||
}
|
||||
u2.unify(actualArgTy, *res);
|
||||
}
|
||||
else
|
||||
u2.unify(actualArgTy, expectedArgTy);
|
||||
}
|
||||
}
|
||||
else if (!FFlag::LuauTableLiteralSubtypeCheckFunctionCalls && expr->is<AstExprTable>() &&
|
||||
!ContainsGenerics::hasGeneric(expectedArgTy, NotNull{&genericTypesAndPacks}))
|
||||
|
@ -2507,6 +2528,23 @@ bool ConstraintSolver::tryDispatch(const AssignPropConstraint& c, NotNull<const
|
|||
{
|
||||
LUAU_ASSERT(lhsTable->remainingProps > 0);
|
||||
lhsTable->remainingProps -= 1;
|
||||
|
||||
// For some code like:
|
||||
//
|
||||
// local T = {}
|
||||
// function T:foo()
|
||||
// return T:bar(5)
|
||||
// end
|
||||
// function T:bar(i)
|
||||
// return i
|
||||
// end
|
||||
//
|
||||
// We need to wake up an unsealed table if it previously
|
||||
// was blocked on missing a member. In the above, we may
|
||||
// try to solve for `hasProp T "bar"`, block, then never
|
||||
// wake up without forcing a constraint.
|
||||
if (FFlag::LuauPushFunctionTypesInFunctionStatement)
|
||||
unblock(lhsType, constraint->location);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -2905,6 +2943,104 @@ bool ConstraintSolver::tryDispatch(const SimplifyConstraint& c, NotNull<const Co
|
|||
return true;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
struct ContainsAnyGeneric final : public TypeOnceVisitor
|
||||
{
|
||||
bool found = false;
|
||||
|
||||
explicit ContainsAnyGeneric()
|
||||
: TypeOnceVisitor(true)
|
||||
{
|
||||
}
|
||||
|
||||
bool visit(TypeId ty) override
|
||||
{
|
||||
found = found || is<GenericType>(ty);
|
||||
return !found;
|
||||
}
|
||||
|
||||
bool visit(TypePackId ty) override
|
||||
{
|
||||
found = found || is<GenericTypePack>(ty);
|
||||
return !found;
|
||||
}
|
||||
|
||||
static bool hasAnyGeneric(TypeId ty)
|
||||
{
|
||||
ContainsAnyGeneric cg;
|
||||
cg.traverse(ty);
|
||||
return cg.found;
|
||||
}
|
||||
|
||||
static bool hasAnyGeneric(TypePackId tp)
|
||||
{
|
||||
ContainsAnyGeneric cg;
|
||||
cg.traverse(tp);
|
||||
return cg.found;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
bool ConstraintSolver::tryDispatch(const PushFunctionTypeConstraint& c, NotNull<const Constraint> constraint)
|
||||
{
|
||||
// NOTE: This logic could probably be combined with that of
|
||||
// `FunctionCheckConstraint`, but that constraint currently does a few
|
||||
// different things.
|
||||
|
||||
auto expectedFn = get<FunctionType>(follow(c.expectedFunctionType));
|
||||
auto fn = get<FunctionType>(follow(c.functionType));
|
||||
|
||||
// If either the expected type or given type aren't functions, then bail.
|
||||
if (!expectedFn || !fn)
|
||||
return true;
|
||||
|
||||
auto expectedParams = begin(expectedFn->argTypes);
|
||||
auto params = begin(fn->argTypes);
|
||||
|
||||
if (expectedParams == end(expectedFn->argTypes) || params == end(fn->argTypes))
|
||||
return true;
|
||||
|
||||
if (c.isSelf)
|
||||
{
|
||||
if (is<FreeType>(follow(*params)))
|
||||
{
|
||||
shiftReferences(*params, *expectedParams);
|
||||
bind(constraint, *params, *expectedParams);
|
||||
}
|
||||
expectedParams++;
|
||||
params++;
|
||||
}
|
||||
|
||||
// `idx` is an index into the arguments of the attached `AstExprFunction`,
|
||||
// we don't need to increment it with respect to arguments in case of a
|
||||
// `self` type.
|
||||
size_t idx = 0;
|
||||
while (idx < c.expr->args.size && expectedParams != end(expectedFn->argTypes) && params != end(fn->argTypes))
|
||||
{
|
||||
// If we have an explicitly annotated parameter, a non-free type for
|
||||
// the parameter, or the expected type contains a generic, bail.
|
||||
// - Annotations should be respected above all else;
|
||||
// - a non-free-type is unexpected, so just bail;
|
||||
// - a generic in the expected type might cause us to leak a generic, so bail.
|
||||
if (!c.expr->args.data[idx]->annotation && get<FreeType>(*params) && !ContainsAnyGeneric::hasAnyGeneric(*expectedParams))
|
||||
{
|
||||
shiftReferences(*params, *expectedParams);
|
||||
bind(constraint, *params, *expectedParams);
|
||||
}
|
||||
expectedParams++;
|
||||
params++;
|
||||
idx++;
|
||||
}
|
||||
|
||||
if (!c.expr->returnAnnotation && get<FreeTypePack>(fn->retTypes) && !ContainsAnyGeneric::hasAnyGeneric(expectedFn->retTypes))
|
||||
bind(constraint, fn->retTypes, expectedFn->retTypes);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const IterableConstraint& c, NotNull<const Constraint> constraint, bool force)
|
||||
{
|
||||
iteratorTy = follow(iteratorTy);
|
||||
|
|
|
@ -17,6 +17,8 @@ LUAU_FASTFLAGVARIABLE(LuauDfgScopeStackNotNull)
|
|||
LUAU_FASTFLAGVARIABLE(LuauDoNotAddUpvalueTypesToLocalType)
|
||||
LUAU_FASTFLAGVARIABLE(LuauDfgIfBlocksShouldRespectControlFlow)
|
||||
LUAU_FASTFLAGVARIABLE(LuauDfgAllowUpdatesInLoops)
|
||||
LUAU_FASTFLAG(LuauFragmentAutocompleteTracksRValueRefinements)
|
||||
LUAU_FASTFLAGVARIABLE(LuauDfgForwardNilFromAndOr)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -107,6 +109,14 @@ const RefinementKey* DataFlowGraph::getRefinementKey(const AstExpr* expr) const
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
std::optional<Symbol> DataFlowGraph::getSymbolFromDef(const Def* def) const
|
||||
{
|
||||
if (auto ref = defToSymbol.find(def))
|
||||
return *ref;
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<DefId> DfgScope::lookup(Symbol symbol) const
|
||||
{
|
||||
for (const DfgScope* current = this; current; current = current->parent)
|
||||
|
@ -1051,12 +1061,16 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprLocal* l)
|
|||
{
|
||||
DefId def = lookup(l->local, l->local->location);
|
||||
const RefinementKey* key = keyArena->leaf(def);
|
||||
if (FFlag::LuauFragmentAutocompleteTracksRValueRefinements)
|
||||
graph.defToSymbol[def] = l->local;
|
||||
return {def, key};
|
||||
}
|
||||
|
||||
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprGlobal* g)
|
||||
{
|
||||
DefId def = lookup(g->name, g->location);
|
||||
if (FFlag::LuauFragmentAutocompleteTracksRValueRefinements)
|
||||
graph.defToSymbol[def] = g->name;
|
||||
return {def, keyArena->leaf(def)};
|
||||
}
|
||||
|
||||
|
@ -1216,10 +1230,23 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprUnary* u)
|
|||
|
||||
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprBinary* b)
|
||||
{
|
||||
visitExpr(b->left);
|
||||
visitExpr(b->right);
|
||||
if (FFlag::LuauDfgForwardNilFromAndOr)
|
||||
{
|
||||
auto left = visitExpr(b->left);
|
||||
auto right = visitExpr(b->right);
|
||||
// I think there's some subtlety here. There are probably cases where
|
||||
// X or Y / X and Y can _never_ "be subscripted."
|
||||
auto subscripted = (b->op == AstExprBinary::And || b->op == AstExprBinary::Or) &&
|
||||
(containsSubscriptedDefinition(left.def) || containsSubscriptedDefinition(right.def));
|
||||
return {defArena->freshCell(Symbol{}, b->location, subscripted), nullptr};
|
||||
}
|
||||
else
|
||||
{
|
||||
visitExpr(b->left);
|
||||
visitExpr(b->right);
|
||||
|
||||
return {defArena->freshCell(Symbol{}, b->location), nullptr};
|
||||
return {defArena->freshCell(Symbol{}, b->location), nullptr};
|
||||
}
|
||||
}
|
||||
|
||||
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprTypeAssertion* t)
|
||||
|
|
|
@ -40,6 +40,8 @@ LUAU_FASTFLAG(LuauExpectedTypeVisitor)
|
|||
LUAU_FASTFLAGVARIABLE(LuauPopulateRefinedTypesInFragmentFromOldSolver)
|
||||
LUAU_FASTFLAG(LuauUseWorkspacePropToChooseSolver)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFragmentRequiresCanBeResolvedToAModule)
|
||||
LUAU_FASTFLAG(LuauFragmentAutocompleteTracksRValueRefinements)
|
||||
LUAU_FASTFLAGVARIABLE(LuauPopulateSelfTypesInFragment)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -421,6 +423,15 @@ FragmentAutocompleteAncestryResult findAncestryForFragmentParse(AstStatBlock* st
|
|||
{
|
||||
if (globFun->location.contains(cursorPos))
|
||||
{
|
||||
if (FFlag::LuauPopulateSelfTypesInFragment)
|
||||
{
|
||||
if (auto local = globFun->func->self)
|
||||
{
|
||||
localStack.push_back(local);
|
||||
localMap[local->name] = local;
|
||||
}
|
||||
}
|
||||
|
||||
for (AstLocal* loc : globFun->func->args)
|
||||
{
|
||||
localStack.push_back(loc);
|
||||
|
@ -603,7 +614,17 @@ struct UsageFinder : public AstVisitor
|
|||
if (auto ref = dfg->getRefinementKey(expr))
|
||||
mentionedDefs.insert(ref->def);
|
||||
if (auto local = expr->as<AstExprLocal>())
|
||||
localBindingsReferenced.emplace_back(dfg->getDef(local), local->local);
|
||||
{
|
||||
if (FFlag::LuauFragmentAutocompleteTracksRValueRefinements)
|
||||
{
|
||||
auto def = dfg->getDef(local);
|
||||
localBindingsReferenced.emplace_back(def, local->local);
|
||||
symbolsToRefine.emplace_back(def, Symbol(local->local));
|
||||
}
|
||||
else
|
||||
localBindingsReferenced.emplace_back(dfg->getDef(local), local->local);
|
||||
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -611,6 +632,11 @@ struct UsageFinder : public AstVisitor
|
|||
{
|
||||
if (FFlag::LuauGlobalVariableModuleIsolation)
|
||||
globalDefsToPrePopulate.emplace_back(global->name, dfg->getDef(global));
|
||||
if (FFlag::LuauFragmentAutocompleteTracksRValueRefinements)
|
||||
{
|
||||
auto def = dfg->getDef(global);
|
||||
symbolsToRefine.emplace_back(def, Symbol(global->name));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -633,6 +659,7 @@ struct UsageFinder : public AstVisitor
|
|||
std::vector<std::pair<Name, Name>> referencedImportedBindings{{"", ""}};
|
||||
std::vector<std::pair<AstName, const Def*>> globalDefsToPrePopulate;
|
||||
std::vector<AstName> globalFunctionsReferenced;
|
||||
std::vector<std::pair<const Def*, Symbol>> symbolsToRefine;
|
||||
};
|
||||
|
||||
// Runs the `UsageFinder` traversal on the fragment and grabs all of the types that are
|
||||
|
@ -685,7 +712,20 @@ void cloneTypesFromFragment(
|
|||
}
|
||||
}
|
||||
|
||||
if (FFlag::LuauPopulateRefinedTypesInFragmentFromOldSolver && !staleModule->checkedInNewSolver)
|
||||
if (FFlag::LuauFragmentAutocompleteTracksRValueRefinements)
|
||||
{
|
||||
for (const auto& [d, syms] : f.symbolsToRefine)
|
||||
{
|
||||
for (const Scope* stale = staleScope; stale; stale = stale->parent.get())
|
||||
{
|
||||
if (auto res = stale->refinements.find(syms); res != stale->refinements.end())
|
||||
{
|
||||
destScope->rvalueRefinements[d] = Luau::cloneIncremental(res->second, *destArena, cloneState, destScope);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (FFlag::LuauPopulateRefinedTypesInFragmentFromOldSolver && !staleModule->checkedInNewSolver)
|
||||
{
|
||||
for (const auto& [d, loc] : f.localBindingsReferenced)
|
||||
{
|
||||
|
|
|
@ -1672,7 +1672,10 @@ ModulePtr check(
|
|||
}
|
||||
|
||||
unfreeze(result->interfaceTypes);
|
||||
result->clonePublicInterface(builtinTypes, *iceHandler);
|
||||
if (FFlag::LuauUseWorkspacePropToChooseSolver)
|
||||
result->clonePublicInterface(builtinTypes, *iceHandler, SolverMode::New);
|
||||
else
|
||||
result->clonePublicInterface_DEPRECATED(builtinTypes, *iceHandler);
|
||||
|
||||
if (FFlag::DebugLuauForbidInternalTypes)
|
||||
{
|
||||
|
|
|
@ -275,7 +275,7 @@ Module::~Module()
|
|||
unfreeze(internalTypes);
|
||||
}
|
||||
|
||||
void Module::clonePublicInterface(NotNull<BuiltinTypes> builtinTypes, InternalErrorReporter& ice)
|
||||
void Module::clonePublicInterface_DEPRECATED(NotNull<BuiltinTypes> builtinTypes, InternalErrorReporter& ice)
|
||||
{
|
||||
CloneState cloneState{builtinTypes};
|
||||
|
||||
|
@ -319,6 +319,50 @@ void Module::clonePublicInterface(NotNull<BuiltinTypes> builtinTypes, InternalEr
|
|||
this->exportedTypeBindings = moduleScope->exportedTypeBindings;
|
||||
}
|
||||
|
||||
void Module::clonePublicInterface(NotNull<BuiltinTypes> builtinTypes, InternalErrorReporter& ice, SolverMode mode)
|
||||
{
|
||||
CloneState cloneState{builtinTypes};
|
||||
|
||||
ScopePtr moduleScope = getModuleScope();
|
||||
|
||||
TypePackId returnType = moduleScope->returnType;
|
||||
std::optional<TypePackId> varargPack = mode == SolverMode::New ? std::nullopt : moduleScope->varargPack;
|
||||
|
||||
TxnLog log;
|
||||
ClonePublicInterface clonePublicInterface{&log, builtinTypes, this};
|
||||
|
||||
returnType = clonePublicInterface.cloneTypePack(returnType);
|
||||
|
||||
moduleScope->returnType = returnType;
|
||||
if (varargPack)
|
||||
{
|
||||
varargPack = clonePublicInterface.cloneTypePack(*varargPack);
|
||||
moduleScope->varargPack = varargPack;
|
||||
}
|
||||
|
||||
for (auto& [name, tf] : moduleScope->exportedTypeBindings)
|
||||
{
|
||||
tf = clonePublicInterface.cloneTypeFun(tf);
|
||||
}
|
||||
|
||||
for (auto& [name, ty] : declaredGlobals)
|
||||
{
|
||||
ty = clonePublicInterface.cloneType(ty);
|
||||
}
|
||||
|
||||
if (FFlag::LuauUserTypeFunctionAliases)
|
||||
{
|
||||
for (auto& tf : typeFunctionAliases)
|
||||
{
|
||||
*tf = clonePublicInterface.cloneTypeFun(*tf);
|
||||
}
|
||||
}
|
||||
|
||||
// Copy external stuff over to Module itself
|
||||
this->returnType = moduleScope->returnType;
|
||||
this->exportedTypeBindings = moduleScope->exportedTypeBindings;
|
||||
}
|
||||
|
||||
bool Module::hasModuleScope() const
|
||||
{
|
||||
return !scopes.empty();
|
||||
|
|
|
@ -24,6 +24,8 @@ LUAU_FASTFLAG(DebugLuauMagicTypes)
|
|||
|
||||
LUAU_FASTFLAGVARIABLE(LuauNewNonStrictVisitTypes2)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNewNonStrictFixGenericTypePacks)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNewNonStrictMoreUnknownSymbols)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNewNonStrictNoErrorsPassingNever)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -353,12 +355,24 @@ struct NonStrictTypeChecker
|
|||
NonStrictContext condB = visit(ifStatement->condition, ValueContext::RValue);
|
||||
NonStrictContext branchContext;
|
||||
|
||||
// If there is no else branch, don't bother generating warnings for the then branch - we can't prove there is an error
|
||||
if (ifStatement->elsebody)
|
||||
if (FFlag::LuauNewNonStrictMoreUnknownSymbols)
|
||||
{
|
||||
NonStrictContext thenBody = visit(ifStatement->thenbody);
|
||||
NonStrictContext elseBody = visit(ifStatement->elsebody);
|
||||
branchContext = NonStrictContext::conjunction(builtinTypes, arena, thenBody, elseBody);
|
||||
if (ifStatement->elsebody)
|
||||
{
|
||||
NonStrictContext elseBody = visit(ifStatement->elsebody);
|
||||
branchContext = NonStrictContext::conjunction(builtinTypes, arena, thenBody, elseBody);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If there is no else branch, don't bother generating warnings for the then branch - we can't prove there is an error
|
||||
if (ifStatement->elsebody)
|
||||
{
|
||||
NonStrictContext thenBody = visit(ifStatement->thenbody);
|
||||
NonStrictContext elseBody = visit(ifStatement->elsebody);
|
||||
branchContext = NonStrictContext::conjunction(builtinTypes, arena, thenBody, elseBody);
|
||||
}
|
||||
}
|
||||
|
||||
return NonStrictContext::disjunction(builtinTypes, arena, condB, branchContext);
|
||||
|
@ -629,6 +643,13 @@ struct NonStrictTypeChecker
|
|||
|
||||
NonStrictContext visit(AstExprCall* call)
|
||||
{
|
||||
if (FFlag::LuauNewNonStrictMoreUnknownSymbols)
|
||||
{
|
||||
visit(call->func, ValueContext::RValue);
|
||||
for (auto arg : call->args)
|
||||
visit(arg, ValueContext::RValue);
|
||||
}
|
||||
|
||||
NonStrictContext fresh{};
|
||||
TypeId* originalCallTy = module->astOriginalCallTypes.find(call->func);
|
||||
if (!originalCallTy)
|
||||
|
@ -715,7 +736,15 @@ struct NonStrictTypeChecker
|
|||
{
|
||||
AstExpr* arg = arguments[i];
|
||||
if (auto runTimeFailureType = willRunTimeError(arg, fresh))
|
||||
reportError(CheckedFunctionCallError{argTypes[i], *runTimeFailureType, functionName, i}, arg->location);
|
||||
{
|
||||
if (FFlag::LuauNewNonStrictNoErrorsPassingNever)
|
||||
{
|
||||
if (!get<NeverType>(follow(*runTimeFailureType)))
|
||||
reportError(CheckedFunctionCallError{argTypes[i], *runTimeFailureType, functionName, i}, arg->location);
|
||||
}
|
||||
else
|
||||
reportError(CheckedFunctionCallError{argTypes[i], *runTimeFailureType, functionName, i}, arg->location);
|
||||
}
|
||||
}
|
||||
|
||||
if (arguments.size() < argTypes.size())
|
||||
|
|
|
@ -2023,6 +2023,8 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts)
|
|||
return "table_check " + tos(c.expectedType) + " :> " + tos(c.exprType);
|
||||
else if constexpr (std::is_same_v<T, SimplifyConstraint>)
|
||||
return "simplify " + tos(c.ty);
|
||||
else if constexpr (std::is_same_v<T, PushFunctionTypeConstraint>)
|
||||
return "push_function_type " + tos(c.expectedFunctionType) + " => " + tos(c.functionType);
|
||||
else
|
||||
static_assert(always_false_v<T>, "Non-exhaustive constraint switch");
|
||||
};
|
||||
|
|
|
@ -60,6 +60,7 @@ LUAU_FASTFLAGVARIABLE(LuauOccursCheckForRefinement)
|
|||
LUAU_FASTFLAGVARIABLE(LuauStuckTypeFunctionsStillDispatch)
|
||||
LUAU_FASTFLAG(LuauRefineTablesWithReadType)
|
||||
LUAU_FASTFLAGVARIABLE(LuauEmptyStringInKeyOf)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAvoidExcessiveTypeCopying)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -2455,6 +2456,39 @@ struct RefineTypeScrubber : public Substitution
|
|||
|
||||
};
|
||||
|
||||
bool occurs(TypeId haystack, TypeId needle, DenseHashSet<TypeId>& seen)
|
||||
{
|
||||
if (needle == haystack)
|
||||
return true;
|
||||
|
||||
if (seen.contains(haystack))
|
||||
return false;
|
||||
|
||||
seen.insert(haystack);
|
||||
|
||||
if (auto ut = get<UnionType>(haystack))
|
||||
{
|
||||
for (auto option : ut)
|
||||
if (occurs(option, needle, seen))
|
||||
return true;
|
||||
}
|
||||
|
||||
if (auto it = get<UnionType>(haystack))
|
||||
{
|
||||
for (auto part : it)
|
||||
if (occurs(part, needle, seen))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool occurs(TypeId haystack, TypeId needle)
|
||||
{
|
||||
DenseHashSet<TypeId> seen{nullptr};
|
||||
return occurs(haystack, needle, seen);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TypeFunctionReductionResult<TypeId> refineTypeFunction(
|
||||
|
@ -2485,9 +2519,12 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
|
|||
// Instead, we can clip the recursive part:
|
||||
//
|
||||
// t1 where t1 = refine<T | t1, Y> => refine<T, Y>
|
||||
RefineTypeScrubber rts{ctx, instance};
|
||||
if (auto result = rts.substitute(targetTy))
|
||||
targetTy = *result;
|
||||
if (!FFlag::LuauAvoidExcessiveTypeCopying || occurs(targetTy, instance))
|
||||
{
|
||||
RefineTypeScrubber rts{ctx, instance};
|
||||
if (auto result = rts.substitute(targetTy))
|
||||
targetTy = *result;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<TypeId> discriminantTypes;
|
||||
|
|
|
@ -32,6 +32,7 @@ LUAU_FASTINTVARIABLE(LuauVisitRecursionLimit, 500)
|
|||
LUAU_FASTFLAG(LuauKnowsTheDataModel3)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification)
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
LUAU_FASTFLAG(LuauUseWorkspacePropToChooseSolver)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauReduceCheckBinaryExprStackPressure)
|
||||
|
||||
|
@ -303,7 +304,11 @@ ModulePtr TypeChecker::checkWithoutRecursionCheck(const SourceModule& module, Mo
|
|||
normalizer.clearCaches();
|
||||
normalizer.arena = nullptr;
|
||||
|
||||
currentModule->clonePublicInterface(builtinTypes, *iceHandler);
|
||||
if (FFlag::LuauUseWorkspacePropToChooseSolver)
|
||||
currentModule->clonePublicInterface(builtinTypes, *iceHandler, SolverMode::Old);
|
||||
else
|
||||
currentModule->clonePublicInterface_DEPRECATED(builtinTypes, *iceHandler);
|
||||
|
||||
freeze(currentModule->internalTypes);
|
||||
freeze(currentModule->interfaceTypes);
|
||||
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
|
||||
#include <string.h>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauCodeGenAllocationCheck)
|
||||
|
||||
#if defined(_WIN32)
|
||||
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
|
@ -52,7 +54,7 @@ static void freePagesImpl(uint8_t* mem, size_t size)
|
|||
CODEGEN_ASSERT(!"failed to deallocate block memory");
|
||||
}
|
||||
|
||||
static void makePagesExecutable(uint8_t* mem, size_t size)
|
||||
static void makePagesExecutable_DEPRECATED(uint8_t* mem, size_t size)
|
||||
{
|
||||
CODEGEN_ASSERT((uintptr_t(mem) & (kPageSize - 1)) == 0);
|
||||
CODEGEN_ASSERT(size == alignToPageSize(size));
|
||||
|
@ -62,6 +64,15 @@ static void makePagesExecutable(uint8_t* mem, size_t size)
|
|||
CODEGEN_ASSERT(!"Failed to change page protection");
|
||||
}
|
||||
|
||||
[[nodiscard]] static bool makePagesExecutable(uint8_t* mem, size_t size)
|
||||
{
|
||||
CODEGEN_ASSERT((uintptr_t(mem) & (kPageSize - 1)) == 0);
|
||||
CODEGEN_ASSERT(size == alignToPageSize(size));
|
||||
|
||||
DWORD oldProtect;
|
||||
return VirtualProtect(mem, size, PAGE_EXECUTE_READ, &oldProtect) != 0;
|
||||
}
|
||||
|
||||
static void flushInstructionCache(uint8_t* mem, size_t size)
|
||||
{
|
||||
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM)
|
||||
|
@ -91,7 +102,7 @@ static void freePagesImpl(uint8_t* mem, size_t size)
|
|||
CODEGEN_ASSERT(!"Failed to deallocate block memory");
|
||||
}
|
||||
|
||||
static void makePagesExecutable(uint8_t* mem, size_t size)
|
||||
static void makePagesExecutable_DEPRECATED(uint8_t* mem, size_t size)
|
||||
{
|
||||
CODEGEN_ASSERT((uintptr_t(mem) & (kPageSize - 1)) == 0);
|
||||
CODEGEN_ASSERT(size == alignToPageSize(size));
|
||||
|
@ -100,6 +111,14 @@ static void makePagesExecutable(uint8_t* mem, size_t size)
|
|||
CODEGEN_ASSERT(!"Failed to change page protection");
|
||||
}
|
||||
|
||||
[[nodiscard]] static bool makePagesExecutable(uint8_t* mem, size_t size)
|
||||
{
|
||||
CODEGEN_ASSERT((uintptr_t(mem) & (kPageSize - 1)) == 0);
|
||||
CODEGEN_ASSERT(size == alignToPageSize(size));
|
||||
|
||||
return mprotect(mem, size, PROT_READ | PROT_EXEC) == 0;
|
||||
}
|
||||
|
||||
static void flushInstructionCache(uint8_t* mem, size_t size)
|
||||
{
|
||||
#ifdef __APPLE__
|
||||
|
@ -184,7 +203,16 @@ bool CodeAllocator::allocate(
|
|||
|
||||
size_t pageAlignedSize = alignToPageSize(startOffset + totalSize);
|
||||
|
||||
makePagesExecutable(blockPos, pageAlignedSize);
|
||||
if (FFlag::LuauCodeGenAllocationCheck)
|
||||
{
|
||||
if (!makePagesExecutable(blockPos, pageAlignedSize))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
makePagesExecutable_DEPRECATED(blockPos, pageAlignedSize);
|
||||
}
|
||||
|
||||
flushInstructionCache(blockPos + codeOffset, codeSize);
|
||||
|
||||
result = blockPos + startOffset;
|
||||
|
|
|
@ -22,6 +22,7 @@ LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
|||
LUAU_FASTFLAG(LuauEagerGeneralization4)
|
||||
LUAU_FASTFLAG(LuauExpectedTypeVisitor)
|
||||
LUAU_FASTFLAG(LuauImplicitTableIndexerKeys2)
|
||||
LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
|
@ -4694,4 +4695,43 @@ TEST_CASE_FIXTURE(ACFixture, "bidirectional_autocomplete_in_function_call")
|
|||
CHECK_EQ(ac.entryMap.count("right"), 1);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ACBuiltinsFixture, "autocomplete_via_bidirectional_self")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauPushFunctionTypesInFunctionStatement, true},
|
||||
};
|
||||
|
||||
check(R"(
|
||||
type IAccount = {
|
||||
__index: IAccount,
|
||||
new : (string, number) -> Account,
|
||||
report: (self: Account) -> (),
|
||||
}
|
||||
|
||||
export type Account = setmetatable<{
|
||||
name: string,
|
||||
balance: number
|
||||
}, IAccount>;
|
||||
|
||||
local Account = {} :: IAccount
|
||||
Account.__index = Account
|
||||
|
||||
function Account.new(name, balance): Account
|
||||
local self = {}
|
||||
self.name = name
|
||||
self.balance = balance
|
||||
return setmetatable(self, Account)
|
||||
end
|
||||
|
||||
function Account:report()
|
||||
print("My balance is: " .. self.@1)
|
||||
end
|
||||
)");
|
||||
|
||||
auto ac = autocomplete('1');
|
||||
CHECK_EQ(ac.entryMap.count("name"), 1);
|
||||
CHECK_EQ(ac.entryMap.count("balance"), 1);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -35,6 +35,9 @@ LUAU_FASTFLAG(LuauFragmentAutocompleteIfRecommendations)
|
|||
LUAU_FASTFLAG(LuauPopulateRefinedTypesInFragmentFromOldSolver)
|
||||
LUAU_FASTFLAG(LuauSolverAgnosticStringification)
|
||||
LUAU_FASTFLAG(LuauFragmentRequiresCanBeResolvedToAModule)
|
||||
LUAU_FASTFLAG(LuauFragmentAutocompleteTracksRValueRefinements)
|
||||
LUAU_FASTFLAG(LuauPopulateSelfTypesInFragment)
|
||||
LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement)
|
||||
|
||||
static std::optional<AutocompleteEntryMap> nullCallback(std::string tag, std::optional<const ExternType*> ptr, std::optional<std::string> contents)
|
||||
{
|
||||
|
@ -70,6 +73,8 @@ struct FragmentAutocompleteFixtureImpl : BaseType
|
|||
ScopedFastFlag luauGlobalVariableModuleIsolation{FFlag::LuauGlobalVariableModuleIsolation, true};
|
||||
ScopedFastFlag luauFragmentAutocompleteIfRecommendations{FFlag::LuauFragmentAutocompleteIfRecommendations, true};
|
||||
ScopedFastFlag luauPopulateRefinedTypesInFragmentFromOldSolver{FFlag::LuauPopulateRefinedTypesInFragmentFromOldSolver, true};
|
||||
ScopedFastFlag sffLuauFragmentAutocompleteTracksRValueRefinement{FFlag::LuauFragmentAutocompleteTracksRValueRefinements, true};
|
||||
ScopedFastFlag sffLuauPopulateSelfTypesInFragment{FFlag::LuauPopulateSelfTypesInFragment, true};
|
||||
|
||||
FragmentAutocompleteFixtureImpl()
|
||||
: BaseType(true)
|
||||
|
@ -263,6 +268,15 @@ struct FragmentAutocompleteBuiltinsFixture : FragmentAutocompleteFixtureImpl<Bui
|
|||
FragmentAutocompleteBuiltinsFixture()
|
||||
: FragmentAutocompleteFixtureImpl<BuiltinsFixture>()
|
||||
{
|
||||
}
|
||||
|
||||
Frontend& getFrontend() override
|
||||
{
|
||||
if (frontend)
|
||||
return *frontend;
|
||||
Frontend& f = BuiltinsFixture::getFrontend();
|
||||
Luau::unfreeze(f.globals.globalTypes);
|
||||
Luau::unfreeze(f.globalsForAutocomplete.globalTypes);
|
||||
const std::string fakeVecDecl = R"(
|
||||
declare class FakeVec
|
||||
function dot(self, x: FakeVec) : FakeVec
|
||||
|
@ -281,6 +295,10 @@ end
|
|||
|
||||
addGlobalBinding(getFrontend().globals, "game", Binding{getBuiltins()->anyType});
|
||||
addGlobalBinding(getFrontend().globalsForAutocomplete, "game", Binding{getBuiltins()->anyType});
|
||||
Luau::freeze(f.globals.globalTypes);
|
||||
Luau::freeze(f.globalsForAutocomplete.globalTypes);
|
||||
|
||||
return *frontend;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -3814,7 +3832,7 @@ end
|
|||
});
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "tagged_union_completion_first_branch_of_union_new_solver" * doctest::skip(true))
|
||||
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "tagged_union_completion_first_branch_of_union_new_solver")
|
||||
{
|
||||
// TODO: CLI-155619 - Fragment autocomplete needs to use stale refinement information for modules typechecked in the new solver as well
|
||||
const std::string source = R"(
|
||||
|
@ -3847,9 +3865,8 @@ end
|
|||
});
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "tagged_union_completion_second_branch_of_union_new_solver" * doctest::skip(true))
|
||||
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "tagged_union_completion_second_branch_of_union_new_solver")
|
||||
{
|
||||
// TODO: CLI-155619 - Fragment autocomplete needs to use stale refinement information for modules typechecked in the new solver as well
|
||||
const std::string source = R"(
|
||||
type Ok<T> = { type: "ok", value: T}
|
||||
type Err<E> = { type : "err", error : E}
|
||||
|
@ -3913,6 +3930,162 @@ require(script.A).
|
|||
CHECK(result.result->acResults.entryMap.count("foo"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "self_types_provide_rich_autocomplete")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauPushFunctionTypesInFunctionStatement, true};
|
||||
|
||||
const std::string source = R"(
|
||||
type Service = {
|
||||
Start: (self: Service) -> (),
|
||||
Prop: number
|
||||
}
|
||||
|
||||
local Service: Service = {}
|
||||
|
||||
function Service:Start()
|
||||
|
||||
end
|
||||
)";
|
||||
const std::string dest = R"(
|
||||
type Service = {
|
||||
Start: (self: Service) -> (),
|
||||
Prop: number
|
||||
}
|
||||
|
||||
local Service: Service = {}
|
||||
|
||||
function Service:Start()
|
||||
self.
|
||||
end
|
||||
)";
|
||||
|
||||
autocompleteFragmentInBothSolvers(
|
||||
source,
|
||||
dest,
|
||||
Position{9, 9},
|
||||
[](auto& result)
|
||||
{
|
||||
CHECK(!result.result->acResults.entryMap.empty());
|
||||
CHECK(result.result->acResults.entryMap.count("Prop"));
|
||||
CHECK(result.result->acResults.entryMap.count("Start"));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "self_with_fancy_metatable_setting_new_solver")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauPushFunctionTypesInFunctionStatement, true};
|
||||
const std::string source = R"(
|
||||
type IAccount = {
|
||||
__index: IAccount,
|
||||
new : (string, number) -> Account,
|
||||
report: (self: Account) -> (),
|
||||
}
|
||||
|
||||
export type Account = setmetatable<{
|
||||
name: string,
|
||||
balance: number
|
||||
}, IAccount>;
|
||||
|
||||
local Account = {} :: IAccount
|
||||
Account.__index = Account
|
||||
|
||||
function Account.new(name, balance): Account
|
||||
local self = {}
|
||||
self.name = name
|
||||
self.balance = balance
|
||||
return setmetatable(self, Account)
|
||||
end
|
||||
|
||||
function Account:report()
|
||||
print("My balance is: " .. )
|
||||
end
|
||||
)";
|
||||
|
||||
const std::string dest = R"(
|
||||
type IAccount = {
|
||||
__index: IAccount,
|
||||
new : (string, number) -> Account,
|
||||
report: (self: Account) -> (),
|
||||
}
|
||||
|
||||
export type Account = setmetatable<{
|
||||
name: string,
|
||||
balance: number
|
||||
}, IAccount>;
|
||||
|
||||
local Account = {} :: IAccount
|
||||
Account.__index = Account
|
||||
|
||||
function Account.new(name, balance): Account
|
||||
local self = {}
|
||||
self.name = name
|
||||
self.balance = balance
|
||||
return setmetatable(self, Account)
|
||||
end
|
||||
|
||||
function Account:report()
|
||||
print("My balance is: " .. self. )
|
||||
end
|
||||
)";
|
||||
|
||||
autocompleteFragmentInNewSolver(
|
||||
source,
|
||||
dest,
|
||||
Position{23, 44},
|
||||
[](auto& result)
|
||||
{
|
||||
CHECK(!result.result->acResults.entryMap.empty());
|
||||
CHECK(result.result->acResults.entryMap.count("new"));
|
||||
CHECK(result.result->acResults.entryMap.count("report"));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "self_with_colon_good_recommendations")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauPushFunctionTypesInFunctionStatement, true};
|
||||
|
||||
const std::string source = R"(
|
||||
type Service = {
|
||||
Start: (self: Service) -> (),
|
||||
Prop: number
|
||||
}
|
||||
|
||||
local Service: Service = {}
|
||||
|
||||
function Service:Start()
|
||||
|
||||
end
|
||||
)";
|
||||
const std::string dest = R"(
|
||||
type Service = {
|
||||
Start: (self: Service) -> (),
|
||||
Prop: number
|
||||
}
|
||||
|
||||
local Service: Service = {}
|
||||
|
||||
function Service:Start()
|
||||
self:
|
||||
end
|
||||
)";
|
||||
|
||||
autocompleteFragmentInBothSolvers(
|
||||
source,
|
||||
dest,
|
||||
Position{9, 9},
|
||||
[](auto& result)
|
||||
{
|
||||
CHECK(!result.result->acResults.entryMap.empty());
|
||||
CHECK(result.result->acResults.entryMap.count("Prop"));
|
||||
CHECK(result.result->acResults.entryMap["Prop"].wrongIndexType);
|
||||
CHECK(result.result->acResults.entryMap.count("Start"));
|
||||
CHECK(!result.result->acResults.entryMap["Start"].wrongIndexType);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// NOLINTEND(bugprone-unchecked-optional-access)
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
|
||||
LUAU_FASTFLAG(LuauNewNonStrictVisitTypes2)
|
||||
LUAU_FASTFLAG(LuauNewNonStrictFixGenericTypePacks)
|
||||
LUAU_FASTFLAG(LuauNewNonStrictMoreUnknownSymbols)
|
||||
LUAU_FASTFLAG(LuauNewNonStrictNoErrorsPassingNever)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
|
@ -190,12 +192,22 @@ local x
|
|||
abs(lower(x))
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(2, 4), "abs", result);
|
||||
if (FFlag::LuauNewNonStrictMoreUnknownSymbols)
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(2, 4), "abs", result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(2, 10), "lower", result);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(2, 4), "abs", result);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "if_then_else_warns_with_never_local")
|
||||
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "if_then_else_does_not_warn_with_never_local")
|
||||
{
|
||||
CheckResult result = checkNonStrict(R"(
|
||||
local x : never
|
||||
|
@ -205,10 +217,14 @@ else
|
|||
lower(x)
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(3, 8), "abs", result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(5, 10), "lower", result);
|
||||
if (FFlag::LuauNewNonStrictNoErrorsPassingNever)
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
else
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(3, 8), "abs", result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(5, 10), "lower", result);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "if_then_else_warns_nil_branches")
|
||||
|
@ -251,7 +267,13 @@ if cond() then
|
|||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
if (FFlag::LuauNewNonStrictMoreUnknownSymbols)
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(3, 8), "abs", result);
|
||||
}
|
||||
else
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "if_then_no_else_err_in_cond")
|
||||
|
@ -267,14 +289,31 @@ end
|
|||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "if_then_else_expr_should_warn")
|
||||
{
|
||||
CheckResult result = checkNonStrict(R"(
|
||||
local x = 42
|
||||
local y = if cond() then abs(x) else lower(x)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(2, 43), "lower", result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "if_then_else_expr_should_not_warn_for_never")
|
||||
{
|
||||
CheckResult result = checkNonStrict(R"(
|
||||
local x : never
|
||||
local y = if cond() then abs(x) else lower(x)
|
||||
)");
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(2, 29), "abs", result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(2, 43), "lower", result);
|
||||
|
||||
if (FFlag::LuauNewNonStrictNoErrorsPassingNever)
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
else
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(2, 29), "abs", result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(2, 43), "lower", result);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "if_then_else_expr_doesnt_warn_else_branch")
|
||||
|
@ -355,10 +394,20 @@ function f(x)
|
|||
lower(x)
|
||||
end
|
||||
)");
|
||||
LUAU_REQUIRE_ERROR_COUNT(3, result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(2, 8), "abs", result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(3, 10), "lower", result);
|
||||
NONSTRICT_REQUIRE_FUNC_DEFINITION_ERR(Position(1, 11), "x", result);
|
||||
|
||||
|
||||
if (FFlag::LuauNewNonStrictNoErrorsPassingNever)
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
NONSTRICT_REQUIRE_FUNC_DEFINITION_ERR(Position(1, 11), "x", result);
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(3, result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(2, 8), "abs", result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(3, 10), "lower", result);
|
||||
NONSTRICT_REQUIRE_FUNC_DEFINITION_ERR(Position(1, 11), "x", result);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "function_def_sequencing_errors_2")
|
||||
|
@ -369,10 +418,19 @@ local t = {function(x)
|
|||
lower(x)
|
||||
end}
|
||||
)");
|
||||
LUAU_REQUIRE_ERROR_COUNT(3, result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(2, 8), "abs", result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(3, 10), "lower", result);
|
||||
CHECK(toString(result.errors[2]) == "Argument x with type 'unknown' is used in a way that will run time error");
|
||||
|
||||
if (FFlag::LuauNewNonStrictNoErrorsPassingNever)
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK(toString(result.errors[0]) == "Argument x with type 'unknown' is used in a way that will run time error");
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(3, result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(2, 8), "abs", result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(3, 10), "lower", result);
|
||||
CHECK(toString(result.errors[2]) == "Argument x with type 'unknown' is used in a way that will run time error");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "local_fn_produces_error")
|
||||
|
@ -401,7 +459,7 @@ local y = function() lower(x) end
|
|||
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "function_def_if_warns_never")
|
||||
{
|
||||
CheckResult result = checkNonStrict(R"(
|
||||
function f(x)
|
||||
function f(x: never)
|
||||
if cond() then
|
||||
abs(x)
|
||||
else
|
||||
|
@ -409,9 +467,15 @@ function f(x)
|
|||
end
|
||||
end
|
||||
)");
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(3, 12), "abs", result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(5, 14), "lower", result);
|
||||
|
||||
if (FFlag::LuauNewNonStrictNoErrorsPassingNever)
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
else
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(3, 12), "abs", result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(5, 14), "lower", result);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "function_def_if_no_else")
|
||||
|
@ -721,4 +785,39 @@ TEST_CASE_FIXTURE(Fixture, "incomplete_function_annotation")
|
|||
LUAU_REQUIRE_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "unknown_globals_in_function_calls")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauNewNonStrictMoreUnknownSymbols, true};
|
||||
|
||||
CheckResult result = check(Mode::Nonstrict, R"(
|
||||
local function foo() : ()
|
||||
bar()
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
const UnknownSymbol* err = get<UnknownSymbol>(result.errors[0]);
|
||||
CHECK_EQ(err->name, "bar");
|
||||
CHECK_EQ(err->context, UnknownSymbol::Context::Binding);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "unknown_globals_in_one_sided_conditionals")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauNewNonStrictMoreUnknownSymbols, true};
|
||||
|
||||
CheckResult result = check(Mode::Nonstrict, R"(
|
||||
local function foo(cond) : ()
|
||||
if cond then
|
||||
bar()
|
||||
end
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
const UnknownSymbol* err = get<UnknownSymbol>(result.errors[0]);
|
||||
CHECK_EQ(err->name, "bar");
|
||||
CHECK_EQ(err->context, UnknownSymbol::Context::Binding);
|
||||
}
|
||||
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -33,6 +33,7 @@ LUAU_FASTFLAG(LuauAvoidGenericsLeakingDuringFunctionCallCheck)
|
|||
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
|
||||
LUAU_FASTFLAG(LuauSolverAgnosticStringification)
|
||||
LUAU_FASTFLAG(LuauSuppressErrorsForMultipleNonviableOverloads)
|
||||
LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement)
|
||||
|
||||
TEST_SUITE_BEGIN("TypeInferFunctions");
|
||||
|
||||
|
@ -3196,4 +3197,114 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "string_format_pack_variadic")
|
|||
)"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "table_annotated_explicit_self")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauPushFunctionTypesInFunctionStatement, true},
|
||||
};
|
||||
|
||||
CheckResult results = check(R"(
|
||||
type MyObject = {
|
||||
fn: (self: MyObject) -> number,
|
||||
field: number
|
||||
}
|
||||
|
||||
local Foo = {} :: MyObject
|
||||
|
||||
function Foo:fn()
|
||||
local _ = self
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, results);
|
||||
LUAU_REQUIRE_ERROR(results, FunctionExitsWithoutReturning); // `Foo:fn` should return a `number`
|
||||
CHECK_EQ("MyObject", toString(requireTypeAtPosition({9, 24})));
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "oss_1871")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauPushFunctionTypesInFunctionStatement, true},
|
||||
};
|
||||
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(check(R"(
|
||||
export type Test = {
|
||||
[string]: (string) -> ()
|
||||
}
|
||||
|
||||
local TestTbl: Test = {}
|
||||
|
||||
function TestTbl.Hello(Param)
|
||||
local _ = Param
|
||||
end
|
||||
)"));
|
||||
|
||||
CHECK_EQ("string", toString(requireTypeAtPosition({8, 25})));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "io_manager_oop_ish")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauPushFunctionTypesInFunctionStatement, true},
|
||||
};
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(check(R"(
|
||||
type IIOManager = {
|
||||
__index: IIOManager,
|
||||
write: (self: IOManager, text: string, label: string?) -> number,
|
||||
}
|
||||
|
||||
export type IOManager = setmetatable<{
|
||||
buffer: {string},
|
||||
memory: { [string]: number }
|
||||
}, IIOManager>;
|
||||
|
||||
local IO = {} :: IIOManager
|
||||
IO.__index = IO
|
||||
|
||||
function IO:write(text, label)
|
||||
local _ = self
|
||||
local _ = text
|
||||
local _ = label
|
||||
return 42
|
||||
end
|
||||
|
||||
return IO
|
||||
)"));
|
||||
CHECK_EQ("IOManager", toString(requireTypeAtPosition({15, 25})));
|
||||
CHECK_EQ("string", toString(requireTypeAtPosition({16, 25})));
|
||||
CHECK_EQ("string?", toString(requireTypeAtPosition({17, 25})));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "generic_function_statement")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauPushFunctionTypesInFunctionStatement, true},
|
||||
};
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(check(R"(
|
||||
type Object = {
|
||||
foobar: <T>(number, string, T) -> T
|
||||
}
|
||||
|
||||
local Obj = {} :: Object
|
||||
function Obj.foobar(bing, quxx, dunno)
|
||||
local _ = bing
|
||||
local _ = quxx
|
||||
return dunno
|
||||
end
|
||||
)"));
|
||||
|
||||
CHECK_EQ("number", toString(requireTypeAtPosition({7, 24})));
|
||||
CHECK_EQ("string", toString(requireTypeAtPosition({8, 24})));
|
||||
// NOTE: This specifically _isn't_ `T` as defined by `Object.foobar`
|
||||
CHECK_EQ("a", toString(requireTypeAtPosition({9, 21})));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -18,6 +18,8 @@ LUAU_FASTFLAG(LuauReportSubtypingErrors)
|
|||
LUAU_FASTFLAG(LuauEagerGeneralization4)
|
||||
LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch)
|
||||
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
|
||||
LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint)
|
||||
LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
|
@ -1234,6 +1236,13 @@ TEST_CASE_FIXTURE(Fixture, "generic_table_method")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "correctly_instantiate_polymorphic_member_functions")
|
||||
{
|
||||
// Prior to `LuauPushFunctionTypesInFunctionStatement`, we _always_ forced
|
||||
// a constraint when solving this block.
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::DebugLuauAssertOnForcedConstraint, true},
|
||||
{FFlag::LuauPushFunctionTypesInFunctionStatement, true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local T = {}
|
||||
|
||||
|
|
|
@ -13,6 +13,8 @@ LUAU_FASTFLAG(LuauSolverV2)
|
|||
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2)
|
||||
LUAU_FASTFLAG(LuauRefineTablesWithReadType)
|
||||
LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping)
|
||||
LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement)
|
||||
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2)
|
||||
|
||||
TEST_SUITE_BEGIN("IntersectionTypes");
|
||||
|
||||
|
@ -335,6 +337,11 @@ TEST_CASE_FIXTURE(Fixture, "table_intersection_write_sealed")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "table_intersection_write_sealed_indirect")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauPushFunctionTypesInFunctionStatement, true},
|
||||
{FFlag::LuauTableLiteralSubtypeSpecificCheck2, true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type X = { x: (number) -> number }
|
||||
type Y = { y: (string) -> string }
|
||||
|
@ -350,9 +357,14 @@ TEST_CASE_FIXTURE(Fixture, "table_intersection_write_sealed_indirect")
|
|||
|
||||
if (FFlag::LuauSolverV2)
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
LUAU_REQUIRE_ERROR_COUNT(3, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Cannot add property 'z' to table 'X & Y'");
|
||||
CHECK_EQ(toString(result.errors[1]), "Cannot add property 'w' to table 'X & Y'");
|
||||
// I'm not writing this as a `toString` check, those are awful.
|
||||
auto err1 = get<TypeMismatch>(result.errors[1]);
|
||||
REQUIRE(err1);
|
||||
CHECK_EQ("number", toString(err1->givenType));
|
||||
CHECK_EQ("string", toString(err1->wantedType));
|
||||
CHECK_EQ(toString(result.errors[2]), "Cannot add property 'w' to table 'X & Y'");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -38,6 +38,7 @@ LUAU_FASTFLAG(LuauTableLiteralSubtypeCheckFunctionCalls)
|
|||
LUAU_FASTFLAG(LuauAvoidGenericsLeakingDuringFunctionCallCheck)
|
||||
LUAU_FASTFLAG(LuauRefineTablesWithReadType)
|
||||
LUAU_FASTFLAG(LuauSolverAgnosticStringification)
|
||||
LUAU_FASTFLAG(LuauDfgForwardNilFromAndOr)
|
||||
|
||||
TEST_SUITE_BEGIN("TableTests");
|
||||
|
||||
|
@ -6087,4 +6088,30 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1450")
|
|||
CHECK_EQ(R"("Alt" | "Ctrl" | "Space" | "Tab")", toString(err->givenType));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "oss_1888_and_or_subscriptable")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauDfgForwardNilFromAndOr, true};
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(check(R"(
|
||||
export type CachedValue<T> = {
|
||||
future: any,
|
||||
timestamp: number,
|
||||
ttl: number?,
|
||||
}
|
||||
|
||||
type Cache<T> = { [string]: CachedValue<T> }
|
||||
type CacheMap = { [string]: Cache<any> }
|
||||
|
||||
local _caches: CacheMap = {}
|
||||
|
||||
local CacheManager = {}
|
||||
|
||||
function CacheManager:has(cacheName: string, id: string): boolean
|
||||
local cache = _caches[cacheName]
|
||||
local entry = cache and cache[id]
|
||||
return entry ~= nil
|
||||
end
|
||||
)"));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
Loading…
Add table
Reference in a new issue