Sync to upstream/release/543 (#657)

- Improve ComparisonPrecedence lint suggestions for three-way comparisons (X < Y < Z)
- Improve type checking stability
- Improve location information for errors when parsing invalid type annotations
- Compiler now generates bytecode version 3 in all configurations
- Improve performance of comparisons against numeric constants on AArch64
This commit is contained in:
Arseny Kapoulkine 2022-09-01 16:14:03 -07:00 committed by GitHub
parent 42c24f98d9
commit ae35ada579
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
75 changed files with 1504 additions and 788 deletions

View file

@ -19,6 +19,7 @@ using ScopePtr = std::shared_ptr<Scope>;
// A substitution which replaces free types by any
struct Anyification : Substitution
{
Anyification(TypeArena* arena, NotNull<Scope> scope, InternalErrorReporter* iceHandler, TypeId anyType, TypePackId anyTypePack);
Anyification(TypeArena* arena, const ScopePtr& scope, InternalErrorReporter* iceHandler, TypeId anyType, TypePackId anyTypePack);
NotNull<Scope> scope;
InternalErrorReporter* iceHandler;
@ -35,4 +36,4 @@ struct Anyification : Substitution
bool ignoreChildren(TypePackId ty) override;
};
}
} // namespace Luau

View file

@ -34,6 +34,7 @@ TypeId makeFunction( // Polymorphic
std::initializer_list<TypeId> paramTypes, std::initializer_list<std::string> paramNames, std::initializer_list<TypeId> retTypes);
void attachMagicFunction(TypeId ty, MagicFunction fn);
void attachDcrMagicFunction(TypeId ty, DcrMagicFunction fn);
Property makeProperty(TypeId ty, std::optional<std::string> documentationSymbol = std::nullopt);
void assignPropDocumentationSymbols(TableTypeVar::Props& props, const std::string& baseName);

View file

@ -56,6 +56,10 @@ struct UnaryConstraint
TypeId resultType;
};
// let L : leftType
// let R : rightType
// in
// L op R : resultType
struct BinaryConstraint
{
AstExprBinary::Op op;
@ -64,6 +68,14 @@ struct BinaryConstraint
TypeId resultType;
};
// iteratee is iterable
// iterators is the iteration types.
struct IterableConstraint
{
TypePackId iterator;
TypePackId variables;
};
// name(namedType) = name
struct NameConstraint
{
@ -78,20 +90,31 @@ struct TypeAliasExpansionConstraint
TypeId target;
};
using ConstraintV = Variant<SubtypeConstraint, PackSubtypeConstraint, GeneralizationConstraint, InstantiationConstraint, UnaryConstraint,
BinaryConstraint, NameConstraint, TypeAliasExpansionConstraint>;
using ConstraintPtr = std::unique_ptr<struct Constraint>;
struct FunctionCallConstraint
{
std::vector<NotNull<const Constraint>> innerConstraints;
TypeId fn;
TypePackId result;
class AstExprCall* astFragment;
};
using ConstraintV = Variant<SubtypeConstraint, PackSubtypeConstraint, GeneralizationConstraint, InstantiationConstraint, UnaryConstraint,
BinaryConstraint, IterableConstraint, NameConstraint, TypeAliasExpansionConstraint, FunctionCallConstraint>;
struct Constraint
{
Constraint(ConstraintV&& c, NotNull<Scope> scope);
Constraint(NotNull<Scope> scope, const Location& location, ConstraintV&& c);
Constraint(const Constraint&) = delete;
Constraint& operator=(const Constraint&) = delete;
ConstraintV c;
std::vector<NotNull<Constraint>> dependencies;
NotNull<Scope> scope;
Location location;
ConstraintV c;
std::vector<NotNull<Constraint>> dependencies;
};
inline Constraint& asMutable(const Constraint& c)

View file

@ -9,6 +9,7 @@
#include "Luau/Ast.h"
#include "Luau/Constraint.h"
#include "Luau/Module.h"
#include "Luau/ModuleResolver.h"
#include "Luau/NotNull.h"
#include "Luau/Symbol.h"
#include "Luau/TypeVar.h"
@ -51,12 +52,15 @@ struct ConstraintGraphBuilder
// It is pretty uncommon for constraint generation to itself produce errors, but it can happen.
std::vector<TypeError> errors;
// Needed to resolve modules to make 'require' import types properly.
NotNull<ModuleResolver> moduleResolver;
// Occasionally constraint generation needs to produce an ICE.
const NotNull<InternalErrorReporter> ice;
ScopePtr globalScope;
ConstraintGraphBuilder(const ModuleName& moduleName, ModulePtr module, TypeArena* arena, NotNull<InternalErrorReporter> ice, const ScopePtr& globalScope);
ConstraintGraphBuilder(const ModuleName& moduleName, ModulePtr module, TypeArena* arena, NotNull<ModuleResolver> moduleResolver,
NotNull<InternalErrorReporter> ice, const ScopePtr& globalScope);
/**
* Fabricates a new free type belonging to a given scope.
@ -82,7 +86,7 @@ struct ConstraintGraphBuilder
* @param scope the scope to add the constraint to.
* @param cv the constraint variant to add.
*/
void addConstraint(const ScopePtr& scope, ConstraintV cv);
void addConstraint(const ScopePtr& scope, const Location& location, ConstraintV cv);
/**
* Adds a constraint to a given scope.
@ -104,6 +108,7 @@ struct ConstraintGraphBuilder
void visit(const ScopePtr& scope, AstStatBlock* block);
void visit(const ScopePtr& scope, AstStatLocal* local);
void visit(const ScopePtr& scope, AstStatFor* for_);
void visit(const ScopePtr& scope, AstStatForIn* forIn);
void visit(const ScopePtr& scope, AstStatWhile* while_);
void visit(const ScopePtr& scope, AstStatRepeat* repeat);
void visit(const ScopePtr& scope, AstStatLocalFunction* function);
@ -117,8 +122,6 @@ struct ConstraintGraphBuilder
void visit(const ScopePtr& scope, AstStatDeclareClass* declareClass);
void visit(const ScopePtr& scope, AstStatDeclareFunction* declareFunction);
TypePackId checkExprList(const ScopePtr& scope, const AstArray<AstExpr*>& exprs);
TypePackId checkPack(const ScopePtr& scope, AstArray<AstExpr*> exprs);
TypePackId checkPack(const ScopePtr& scope, AstExpr* expr);

View file

@ -17,6 +17,8 @@ namespace Luau
// never dereference this pointer.
using BlockedConstraintId = const void*;
struct ModuleResolver;
struct InstantiationSignature
{
TypeFun fn;
@ -42,6 +44,7 @@ struct ConstraintSolver
// The entire set of constraints that the solver is trying to resolve.
std::vector<NotNull<Constraint>> constraints;
NotNull<Scope> rootScope;
ModuleName currentModuleName;
// Constraints that the solver has generated, rather than sourcing from the
// scope tree.
@ -63,9 +66,13 @@ struct ConstraintSolver
// Recorded errors that take place within the solver.
ErrorVec errors;
NotNull<ModuleResolver> moduleResolver;
std::vector<RequireCycle> requireCycles;
ConstraintSolverLogger logger;
explicit ConstraintSolver(TypeArena* arena, NotNull<Scope> rootScope);
explicit ConstraintSolver(TypeArena* arena, NotNull<Scope> rootScope, ModuleName moduleName, NotNull<ModuleResolver> moduleResolver,
std::vector<RequireCycle> requireCycles);
/**
* Attempts to dispatch all pending constraints and reach a type solution
@ -86,8 +93,17 @@ struct ConstraintSolver
bool tryDispatch(const InstantiationConstraint& c, NotNull<const Constraint> constraint, bool force);
bool tryDispatch(const UnaryConstraint& c, NotNull<const Constraint> constraint, bool force);
bool tryDispatch(const BinaryConstraint& c, NotNull<const Constraint> constraint, bool force);
bool tryDispatch(const IterableConstraint& c, NotNull<const Constraint> constraint, bool force);
bool tryDispatch(const NameConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const TypeAliasExpansionConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const FunctionCallConstraint& c, NotNull<const Constraint> constraint);
// for a, ... in some_table do
bool tryDispatchIterableTable(TypeId iteratorTy, const IterableConstraint& c, NotNull<const Constraint> constraint, bool force);
// for a, ... in next_function, t, ... do
bool tryDispatchIterableFunction(
TypeId nextTy, TypeId tableTy, TypeId firstIndexTy, const IterableConstraint& c, NotNull<const Constraint> constraint, bool force);
void block(NotNull<const Constraint> target, NotNull<const Constraint> constraint);
/**
@ -108,6 +124,11 @@ struct ConstraintSolver
*/
bool isBlocked(TypeId ty);
/**
* @returns true if the TypePackId is in a blocked state.
*/
bool isBlocked(TypePackId tp);
/**
* Returns whether the constraint is blocked on anything.
* @param constraint the constraint to check.
@ -133,10 +154,22 @@ struct ConstraintSolver
/** Pushes a new solver constraint to the solver.
* @param cv the body of the constraint.
**/
void pushConstraint(ConstraintV cv, NotNull<Scope> scope);
void pushConstraint(NotNull<Scope> scope, const Location& location, ConstraintV cv);
/**
* Attempts to resolve a module from its module information. Returns the
* module-level return type of the module, or the error type if one cannot
* be found. Reports errors to the solver if the module cannot be found or
* the require is illegal.
* @param module the module information to look up.
* @param location the location where the require is taking place; used for
* error locations.
**/
TypeId resolveModule(const ModuleInfo& module, const Location& location);
void reportError(TypeErrorData&& data, const Location& location);
void reportError(TypeError e);
private:
/**
* Marks a constraint as being blocked on a type or type pack. The constraint
@ -154,6 +187,8 @@ private:
* @param progressed the type or type pack pointer that has progressed.
**/
void unblock_(BlockedConstraintId progressed);
ToStringOptions opts;
};
void dump(NotNull<Scope> rootScope, struct ToStringOptions& opts);

View file

@ -16,7 +16,8 @@ struct ConstraintSolverLogger
{
std::string compileOutput();
void captureBoundarySnapshot(const Scope* rootScope, std::vector<NotNull<const Constraint>>& unsolvedConstraints);
void prepareStepSnapshot(const Scope* rootScope, NotNull<const Constraint> current, std::vector<NotNull<const Constraint>>& unsolvedConstraints, bool force);
void prepareStepSnapshot(
const Scope* rootScope, NotNull<const Constraint> current, std::vector<NotNull<const Constraint>>& unsolvedConstraints, bool force);
void commitPreparedStepSnapshot();
private:

View file

@ -157,7 +157,7 @@ struct Frontend
ScopePtr getGlobalScope();
private:
ModulePtr check(const SourceModule& sourceModule, Mode mode, const ScopePtr& environmentScope);
ModulePtr check(const SourceModule& sourceModule, Mode mode, const ScopePtr& environmentScope, std::vector<RequireCycle> requireCycles);
std::pair<SourceNode*, SourceModule*> getSourceNode(CheckResult& checkResult, const ModuleName& name);
SourceModule parse(const ModuleName& name, std::string_view src, const ParseOptions& parseOptions);

View file

@ -40,6 +40,9 @@ struct Scope
std::optional<TypePackId> varargPack;
// All constraints belonging to this scope.
std::vector<ConstraintPtr> constraints;
// Constraints belonging to this scope that are queued manually by other
// constraints.
std::vector<ConstraintPtr> unqueuedConstraints;
TypeLevel level;

View file

@ -92,7 +92,6 @@ inline std::string toString(const Constraint& c)
return toString(c, ToStringOptions{});
}
std::string toString(const TypeVar& tv, ToStringOptions& opts);
std::string toString(const TypePackVar& tp, ToStringOptions& opts);

View file

@ -29,9 +29,10 @@ struct TypeArena
TypeId addTV(TypeVar&& tv);
TypeId freshType(TypeLevel level);
TypeId freshType(Scope* scope);
TypePackId addTypePack(std::initializer_list<TypeId> types);
TypePackId addTypePack(std::vector<TypeId> types);
TypePackId addTypePack(std::vector<TypeId> types, std::optional<TypePackId> tail = {});
TypePackId addTypePack(TypePack pack);
TypePackId addTypePack(TypePackVar pack);

View file

@ -166,7 +166,8 @@ struct TypeChecker
*/
bool unify(TypeId subTy, TypeId superTy, const ScopePtr& scope, const Location& location);
bool unify(TypeId subTy, TypeId superTy, const ScopePtr& scope, const Location& location, const UnifierOptions& options);
bool unify(TypePackId subTy, TypePackId superTy, const ScopePtr& scope, const Location& location, CountMismatch::Context ctx = CountMismatch::Context::Arg);
bool unify(TypePackId subTy, TypePackId superTy, const ScopePtr& scope, const Location& location,
CountMismatch::Context ctx = CountMismatch::Context::Arg);
/** Attempt to unify the types.
* If this fails, and the subTy type can be instantiated, do so and try unification again.

View file

@ -15,6 +15,7 @@ struct TypeArena;
struct TypePack;
struct VariadicTypePack;
struct BlockedTypePack;
struct TypePackVar;
@ -24,7 +25,7 @@ using TypePackId = const TypePackVar*;
using FreeTypePack = Unifiable::Free;
using BoundTypePack = Unifiable::Bound<TypePackId>;
using GenericTypePack = Unifiable::Generic;
using TypePackVariant = Unifiable::Variant<TypePackId, TypePack, VariadicTypePack>;
using TypePackVariant = Unifiable::Variant<TypePackId, TypePack, VariadicTypePack, BlockedTypePack>;
/* A TypePack is a rope-like string of TypeIds. We use this structure to encode
* notions like packs of unknown length and packs of any length, as well as more
@ -43,6 +44,17 @@ struct VariadicTypePack
bool hidden = false; // if true, we don't display this when toString()ing a pack with this variadic as its tail.
};
/**
* Analogous to a BlockedTypeVar.
*/
struct BlockedTypePack
{
BlockedTypePack();
size_t index;
static size_t nextIndex;
};
struct TypePackVar
{
explicit TypePackVar(const TypePackVariant& ty);

View file

@ -11,12 +11,16 @@
namespace Luau
{
struct TxnLog;
using ScopePtr = std::shared_ptr<struct Scope>;
std::optional<TypeId> findMetatableEntry(ErrorVec& errors, TypeId type, const std::string& entry, Location location);
std::optional<TypeId> findTablePropertyRespectingMeta(ErrorVec& errors, TypeId ty, const std::string& name, Location location);
std::optional<TypeId> getIndexTypeFromType(
const ScopePtr& scope, ErrorVec& errors, TypeArena* arena, TypeId type, const std::string& prop, const Location& location, bool addErrors,
InternalErrorReporter& handle);
std::optional<TypeId> getIndexTypeFromType(const ScopePtr& scope, ErrorVec& errors, TypeArena* arena, TypeId type, const std::string& prop,
const Location& location, bool addErrors, InternalErrorReporter& handle);
// Returns the minimum and maximum number of types the argument list can accept.
std::pair<size_t, std::optional<size_t>> getParameterExtents(const TxnLog* log, TypePackId tp);
} // namespace Luau

View file

@ -1,11 +1,13 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "Luau/Ast.h"
#include "Luau/DenseHash.h"
#include "Luau/Predicate.h"
#include "Luau/Unifiable.h"
#include "Luau/Variant.h"
#include "Luau/Common.h"
#include "Luau/NotNull.h"
#include <set>
#include <string>
@ -262,6 +264,8 @@ struct WithPredicate
using MagicFunction = std::function<std::optional<WithPredicate<TypePackId>>(
struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>)>;
using DcrMagicFunction = std::function<bool(NotNull<struct ConstraintSolver>, TypePackId, const class AstExprCall*)>;
struct FunctionTypeVar
{
// Global monomorphic function
@ -288,6 +292,7 @@ struct FunctionTypeVar
TypePackId retTypes;
std::optional<FunctionDefinition> definition;
MagicFunction magicFunction = nullptr; // Function pointer, can be nullptr.
DcrMagicFunction dcrMagicFunction = nullptr; // can be nullptr
bool hasSelf;
Tags tags;
bool hasNoGenerics = false;
@ -462,8 +467,9 @@ struct TypeFun
*/
struct PendingExpansionTypeVar
{
PendingExpansionTypeVar(TypeFun fn, std::vector<TypeId> typeArguments, std::vector<TypePackId> packArguments);
TypeFun fn;
PendingExpansionTypeVar(std::optional<AstName> prefix, AstName name, std::vector<TypeId> typeArguments, std::vector<TypePackId> packArguments);
std::optional<AstName> prefix;
AstName name;
std::vector<TypeId> typeArguments;
std::vector<TypePackId> packArguments;
size_t index;

View file

@ -59,7 +59,8 @@ struct Unifier
UnifierSharedState& sharedState;
Unifier(TypeArena* types, Mode mode, NotNull<Scope> scope, const Location& location, Variance variance, UnifierSharedState& sharedState, TxnLog* parentLog = nullptr);
Unifier(TypeArena* types, Mode mode, NotNull<Scope> scope, const Location& location, Variance variance, UnifierSharedState& sharedState,
TxnLog* parentLog = nullptr);
// Test whether the two type vars unify. Never commits the result.
ErrorVec canUnify(TypeId subTy, TypeId superTy);

View file

@ -188,6 +188,10 @@ struct GenericTypeVarVisitor
{
return visit(tp);
}
virtual bool visit(TypePackId tp, const BlockedTypePack& btp)
{
return visit(tp);
}
void traverse(TypeId ty)
{
@ -314,24 +318,6 @@ struct GenericTypeVarVisitor
{
if (visit(ty, *petv))
{
traverse(petv->fn.type);
for (const GenericTypeDefinition& p : petv->fn.typeParams)
{
traverse(p.ty);
if (p.defaultValue)
traverse(*p.defaultValue);
}
for (const GenericTypePackDefinition& p : petv->fn.typePackParams)
{
traverse(p.tp);
if (p.defaultValue)
traverse(*p.defaultValue);
}
for (TypeId a : petv->typeArguments)
traverse(a);
@ -388,6 +374,9 @@ struct GenericTypeVarVisitor
if (res)
traverse(pack->ty);
}
else if (auto btp = get<BlockedTypePack>(tp))
visit(tp, *btp);
else
LUAU_ASSERT(!"GenericTypeVarVisitor::traverse(TypePackId) is not exhaustive!");

View file

@ -11,15 +11,20 @@ LUAU_FASTFLAG(LuauClassTypeVarsInSubstitution)
namespace Luau
{
Anyification::Anyification(TypeArena* arena, const ScopePtr& scope, InternalErrorReporter* iceHandler, TypeId anyType, TypePackId anyTypePack)
Anyification::Anyification(TypeArena* arena, NotNull<Scope> scope, InternalErrorReporter* iceHandler, TypeId anyType, TypePackId anyTypePack)
: Substitution(TxnLog::empty(), arena)
, scope(NotNull{scope.get()})
, scope(scope)
, iceHandler(iceHandler)
, anyType(anyType)
, anyTypePack(anyTypePack)
{
}
Anyification::Anyification(TypeArena* arena, const ScopePtr& scope, InternalErrorReporter* iceHandler, TypeId anyType, TypePackId anyTypePack)
: Anyification(arena, NotNull{scope.get()}, iceHandler, anyType, anyTypePack)
{
}
bool Anyification::isDirty(TypeId ty)
{
if (ty->persistent)
@ -93,4 +98,4 @@ bool Anyification::ignoreChildren(TypePackId ty)
return ty->persistent;
}
}
} // namespace Luau

View file

@ -1215,8 +1215,8 @@ static bool autocompleteIfElseExpression(
}
}
static AutocompleteContext autocompleteExpression(const SourceModule& sourceModule, const Module& module, const TypeChecker& typeChecker, TypeArena* typeArena,
const std::vector<AstNode*>& ancestry, Position position, AutocompleteEntryMap& result)
static AutocompleteContext autocompleteExpression(const SourceModule& sourceModule, const Module& module, const TypeChecker& typeChecker,
TypeArena* typeArena, const std::vector<AstNode*>& ancestry, Position position, AutocompleteEntryMap& result)
{
LUAU_ASSERT(!ancestry.empty());
@ -1422,8 +1422,8 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
PropIndexType indexType = indexName->op == ':' ? PropIndexType::Colon : PropIndexType::Point;
if (!FFlag::LuauSelfCallAutocompleteFix3 && isString(ty))
return {
autocompleteProps(*module, typeArena, typeChecker.globalScope->bindings[AstName{"string"}].typeId, indexType, ancestry), ancestry, AutocompleteContext::Property};
return {autocompleteProps(*module, typeArena, typeChecker.globalScope->bindings[AstName{"string"}].typeId, indexType, ancestry), ancestry,
AutocompleteContext::Property};
else
return {autocompleteProps(*module, typeArena, ty, indexType, ancestry), ancestry, AutocompleteContext::Property};
}
@ -1522,8 +1522,8 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
else if (AstStatIf* statIf = node->as<AstStatIf>(); statIf && !statIf->elseLocation.has_value())
{
return {
{{"else", AutocompleteEntry{AutocompleteEntryKind::Keyword}}, {"elseif", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword};
return {{{"else", AutocompleteEntry{AutocompleteEntryKind::Keyword}}, {"elseif", AutocompleteEntry{AutocompleteEntryKind::Keyword}}},
ancestry, AutocompleteContext::Keyword};
}
else if (AstStatIf* statIf = parent->as<AstStatIf>(); statIf && node->is<AstStatBlock>())
{

View file

@ -5,6 +5,7 @@
#include "Luau/Symbol.h"
#include "Luau/Common.h"
#include "Luau/ToString.h"
#include "Luau/ConstraintSolver.h"
#include <algorithm>
@ -32,6 +33,8 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionPack(
static std::optional<WithPredicate<TypePackId>> magicFunctionRequire(
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate);
static bool dcrMagicFunctionRequire(NotNull<ConstraintSolver> solver, TypePackId result, const AstExprCall* expr);
TypeId makeUnion(TypeArena& arena, std::vector<TypeId>&& types)
{
return arena.addType(UnionTypeVar{std::move(types)});
@ -105,6 +108,14 @@ void attachMagicFunction(TypeId ty, MagicFunction fn)
LUAU_ASSERT(!"Got a non functional type");
}
void attachDcrMagicFunction(TypeId ty, DcrMagicFunction fn)
{
if (auto ftv = getMutable<FunctionTypeVar>(ty))
ftv->dcrMagicFunction = fn;
else
LUAU_ASSERT(!"Got a non functional type");
}
Property makeProperty(TypeId ty, std::optional<std::string> documentationSymbol)
{
return {
@ -263,6 +274,7 @@ void registerBuiltinTypes(TypeChecker& typeChecker)
}
attachMagicFunction(getGlobalBinding(typeChecker, "require"), magicFunctionRequire);
attachDcrMagicFunction(getGlobalBinding(typeChecker, "require"), dcrMagicFunctionRequire);
}
static std::optional<WithPredicate<TypePackId>> magicFunctionSelect(
@ -509,4 +521,49 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionRequire(
return std::nullopt;
}
static bool checkRequirePathDcr(NotNull<ConstraintSolver> solver, AstExpr* expr)
{
// require(foo.parent.bar) will technically work, but it depends on legacy goop that
// Luau does not and could not support without a bunch of work. It's deprecated anyway, so
// we'll warn here if we see it.
bool good = true;
AstExprIndexName* indexExpr = expr->as<AstExprIndexName>();
while (indexExpr)
{
if (indexExpr->index == "parent")
{
solver->reportError(DeprecatedApiUsed{"parent", "Parent"}, indexExpr->indexLocation);
good = false;
}
indexExpr = indexExpr->expr->as<AstExprIndexName>();
}
return good;
}
static bool dcrMagicFunctionRequire(NotNull<ConstraintSolver> solver, TypePackId result, const AstExprCall* expr)
{
if (expr->args.size != 1)
{
solver->reportError(GenericError{"require takes 1 argument"}, expr->location);
return false;
}
if (!checkRequirePathDcr(solver, expr->args.data[0]))
return false;
if (auto moduleInfo = solver->moduleResolver->resolveModuleInfo(solver->currentModuleName, *expr))
{
TypeId moduleType = solver->resolveModule(*moduleInfo, expr->location);
TypePackId moduleResult = solver->arena->addTypePack({moduleType});
asMutable(result)->ty.emplace<BoundTypePack>(moduleResult);
return true;
}
return false;
}
} // namespace Luau

View file

@ -102,6 +102,11 @@ struct TypePackCloner
defaultClone(t);
}
void operator()(const BlockedTypePack& t)
{
defaultClone(t);
}
// While we are a-cloning, we can flatten out bound TypeVars and make things a bit tighter.
// We just need to be sure that we rewrite pointers both to the binder and the bindee to the same pointer.
void operator()(const Unifiable::Bound<TypePackId>& t)
@ -170,7 +175,7 @@ void TypeCloner::operator()(const BlockedTypeVar& t)
void TypeCloner::operator()(const PendingExpansionTypeVar& t)
{
TypeId res = dest.addType(PendingExpansionTypeVar{t.fn, t.typeArguments, t.packArguments});
TypeId res = dest.addType(PendingExpansionTypeVar{t.prefix, t.name, t.typeArguments, t.packArguments});
PendingExpansionTypeVar* petv = getMutable<PendingExpansionTypeVar>(res);
LUAU_ASSERT(petv);
@ -184,32 +189,6 @@ void TypeCloner::operator()(const PendingExpansionTypeVar& t)
for (TypePackId arg : t.packArguments)
packArguments.push_back(clone(arg, dest, cloneState));
TypeFun fn;
fn.type = clone(t.fn.type, dest, cloneState);
for (const GenericTypeDefinition& param : t.fn.typeParams)
{
TypeId ty = clone(param.ty, dest, cloneState);
std::optional<TypeId> defaultValue = param.defaultValue;
if (defaultValue)
defaultValue = clone(*defaultValue, dest, cloneState);
fn.typeParams.push_back(GenericTypeDefinition{ty, defaultValue});
}
for (const GenericTypePackDefinition& param : t.fn.typePackParams)
{
TypePackId tp = clone(param.tp, dest, cloneState);
std::optional<TypePackId> defaultValue = param.defaultValue;
if (defaultValue)
defaultValue = clone(*defaultValue, dest, cloneState);
fn.typePackParams.push_back(GenericTypePackDefinition{tp, defaultValue});
}
petv->fn = std::move(fn);
petv->typeArguments = std::move(typeArguments);
petv->packArguments = std::move(packArguments);
}
@ -461,6 +440,7 @@ TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log, bool alwaysCl
clone.generics = ftv->generics;
clone.genericPacks = ftv->genericPacks;
clone.magicFunction = ftv->magicFunction;
clone.dcrMagicFunction = ftv->dcrMagicFunction;
clone.tags = ftv->tags;
clone.argNames = ftv->argNames;
result = dest.addType(std::move(clone));
@ -502,7 +482,7 @@ TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log, bool alwaysCl
}
else if (const PendingExpansionTypeVar* petv = get<PendingExpansionTypeVar>(ty))
{
PendingExpansionTypeVar clone{petv->fn, petv->typeArguments, petv->packArguments};
PendingExpansionTypeVar clone{petv->prefix, petv->name, petv->typeArguments, petv->packArguments};
result = dest.addType(std::move(clone));
}
else if (const ClassTypeVar* ctv = get<ClassTypeVar>(ty); FFlag::LuauClonePublicInterfaceLess && ctv && alwaysClone)

View file

@ -5,9 +5,10 @@
namespace Luau
{
Constraint::Constraint(ConstraintV&& c, NotNull<Scope> scope)
: c(std::move(c))
, scope(scope)
Constraint::Constraint(NotNull<Scope> scope, const Location& location, ConstraintV&& c)
: scope(scope)
, location(location)
, c(std::move(c))
{
}

View file

@ -4,6 +4,7 @@
#include "Luau/Ast.h"
#include "Luau/Common.h"
#include "Luau/Constraint.h"
#include "Luau/ModuleResolver.h"
#include "Luau/RecursionCounter.h"
#include "Luau/ToString.h"
@ -16,13 +17,31 @@ namespace Luau
const AstStat* getFallthrough(const AstStat* node); // TypeInfer.cpp
ConstraintGraphBuilder::ConstraintGraphBuilder(
const ModuleName& moduleName, ModulePtr module, TypeArena* arena, NotNull<InternalErrorReporter> ice, const ScopePtr& globalScope)
static std::optional<AstExpr*> matchRequire(const AstExprCall& call)
{
const char* require = "require";
if (call.args.size != 1)
return std::nullopt;
const AstExprGlobal* funcAsGlobal = call.func->as<AstExprGlobal>();
if (!funcAsGlobal || funcAsGlobal->name != require)
return std::nullopt;
if (call.args.size != 1)
return std::nullopt;
return call.args.data[0];
}
ConstraintGraphBuilder::ConstraintGraphBuilder(const ModuleName& moduleName, ModulePtr module, TypeArena* arena,
NotNull<ModuleResolver> moduleResolver, NotNull<InternalErrorReporter> ice, const ScopePtr& globalScope)
: moduleName(moduleName)
, module(module)
, singletonTypes(getSingletonTypes())
, arena(arena)
, rootScope(nullptr)
, moduleResolver(moduleResolver)
, ice(ice)
, globalScope(globalScope)
{
@ -54,9 +73,9 @@ ScopePtr ConstraintGraphBuilder::childScope(AstNode* node, const ScopePtr& paren
return scope;
}
void ConstraintGraphBuilder::addConstraint(const ScopePtr& scope, ConstraintV cv)
void ConstraintGraphBuilder::addConstraint(const ScopePtr& scope, const Location& location, ConstraintV cv)
{
scope->constraints.emplace_back(new Constraint{std::move(cv), NotNull{scope.get()}});
scope->constraints.emplace_back(new Constraint{NotNull{scope.get()}, location, std::move(cv)});
}
void ConstraintGraphBuilder::addConstraint(const ScopePtr& scope, std::unique_ptr<Constraint> c)
@ -77,13 +96,6 @@ void ConstraintGraphBuilder::visit(AstStatBlock* block)
prepopulateGlobalScope(scope, block);
// TODO: We should share the global scope.
rootScope->privateTypeBindings["nil"] = TypeFun{singletonTypes.nilType};
rootScope->privateTypeBindings["number"] = TypeFun{singletonTypes.numberType};
rootScope->privateTypeBindings["string"] = TypeFun{singletonTypes.stringType};
rootScope->privateTypeBindings["boolean"] = TypeFun{singletonTypes.booleanType};
rootScope->privateTypeBindings["thread"] = TypeFun{singletonTypes.threadType};
visitBlockWithoutChildScope(scope, block);
}
@ -158,6 +170,8 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStat* stat)
visit(scope, s);
else if (auto s = stat->as<AstStatFor>())
visit(scope, s);
else if (auto s = stat->as<AstStatForIn>())
visit(scope, s);
else if (auto s = stat->as<AstStatWhile>())
visit(scope, s);
else if (auto s = stat->as<AstStatRepeat>())
@ -201,7 +215,7 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocal* local)
{
location = local->annotation->location;
TypeId annotation = resolveType(scope, local->annotation, /* topLevel */ true);
addConstraint(scope, SubtypeConstraint{ty, annotation});
addConstraint(scope, location, SubtypeConstraint{ty, annotation});
}
varTypes.push_back(ty);
@ -225,14 +239,38 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocal* local)
{
std::vector<TypeId> tailValues{varTypes.begin() + i, varTypes.end()};
TypePackId tailPack = arena->addTypePack(std::move(tailValues));
addConstraint(scope, PackSubtypeConstraint{exprPack, tailPack});
addConstraint(scope, local->location, PackSubtypeConstraint{exprPack, tailPack});
}
}
else
{
TypeId exprType = check(scope, value);
if (i < varTypes.size())
addConstraint(scope, SubtypeConstraint{varTypes[i], exprType});
addConstraint(scope, local->location, SubtypeConstraint{varTypes[i], exprType});
}
}
if (local->values.size > 0)
{
// To correctly handle 'require', we need to import the exported type bindings into the variable 'namespace'.
for (size_t i = 0; i < local->values.size && i < local->vars.size; ++i)
{
const AstExprCall* call = local->values.data[i]->as<AstExprCall>();
if (!call)
continue;
if (auto maybeRequire = matchRequire(*call))
{
AstExpr* require = *maybeRequire;
if (auto moduleInfo = moduleResolver->resolveModuleInfo(moduleName, *require))
{
const Name name{local->vars.data[i]->name.value};
if (ModulePtr module = moduleResolver->getModule(moduleInfo->name))
scope->importedTypeBindings[name] = module->getModuleScope()->exportedTypeBindings;
}
}
}
}
}
@ -244,7 +282,7 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFor* for_)
return;
TypeId t = check(scope, expr);
addConstraint(scope, SubtypeConstraint{t, singletonTypes.numberType});
addConstraint(scope, expr->location, SubtypeConstraint{t, singletonTypes.numberType});
};
checkNumber(for_->from);
@ -257,6 +295,29 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFor* for_)
visit(forScope, for_->body);
}
void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatForIn* forIn)
{
ScopePtr loopScope = childScope(forIn, scope);
TypePackId iterator = checkPack(scope, forIn->values);
std::vector<TypeId> variableTypes;
variableTypes.reserve(forIn->vars.size);
for (AstLocal* var : forIn->vars)
{
TypeId ty = freshType(loopScope);
loopScope->bindings[var] = Binding{ty, var->location};
variableTypes.push_back(ty);
}
// It is always ok to provide too few variables, so we give this pack a free tail.
TypePackId variablePack = arena->addTypePack(std::move(variableTypes), arena->addTypePack(FreeTypePack{loopScope.get()}));
addConstraint(loopScope, getLocation(forIn->values), IterableConstraint{iterator, variablePack});
visit(loopScope, forIn->body);
}
void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatWhile* while_)
{
check(scope, while_->condition);
@ -284,6 +345,9 @@ void addConstraints(Constraint* constraint, NotNull<Scope> scope)
for (const auto& c : scope->constraints)
constraint->dependencies.push_back(NotNull{c.get()});
for (const auto& c : scope->unqueuedConstraints)
constraint->dependencies.push_back(NotNull{c.get()});
for (NotNull<Scope> childScope : scope->children)
addConstraints(constraint, childScope);
}
@ -308,7 +372,8 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocalFunction*
checkFunctionBody(sig.bodyScope, function->func);
NotNull<Scope> constraintScope{sig.signatureScope ? sig.signatureScope.get() : sig.bodyScope.get()};
std::unique_ptr<Constraint> c = std::make_unique<Constraint>(GeneralizationConstraint{functionType, sig.signature}, constraintScope);
std::unique_ptr<Constraint> c =
std::make_unique<Constraint>(constraintScope, function->name->location, GeneralizationConstraint{functionType, sig.signature});
addConstraints(c.get(), NotNull(sig.bodyScope.get()));
addConstraint(scope, std::move(c));
@ -366,7 +431,7 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFunction* funct
prop.type = functionType;
prop.location = function->name->location;
addConstraint(scope, SubtypeConstraint{containingTableType, prospectiveTableType});
addConstraint(scope, indexName->location, SubtypeConstraint{containingTableType, prospectiveTableType});
}
else if (AstExprError* err = function->name->as<AstExprError>())
{
@ -378,7 +443,8 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFunction* funct
checkFunctionBody(sig.bodyScope, function->func);
NotNull<Scope> constraintScope{sig.signatureScope ? sig.signatureScope.get() : sig.bodyScope.get()};
std::unique_ptr<Constraint> c = std::make_unique<Constraint>(GeneralizationConstraint{functionType, sig.signature}, constraintScope);
std::unique_ptr<Constraint> c =
std::make_unique<Constraint>(constraintScope, function->name->location, GeneralizationConstraint{functionType, sig.signature});
addConstraints(c.get(), NotNull(sig.bodyScope.get()));
addConstraint(scope, std::move(c));
@ -387,7 +453,7 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFunction* funct
void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatReturn* ret)
{
TypePackId exprTypes = checkPack(scope, ret->list);
addConstraint(scope, PackSubtypeConstraint{exprTypes, scope->returnType});
addConstraint(scope, ret->location, PackSubtypeConstraint{exprTypes, scope->returnType});
}
void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatBlock* block)
@ -399,10 +465,10 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatBlock* block)
void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatAssign* assign)
{
TypePackId varPackId = checkExprList(scope, assign->vars);
TypePackId varPackId = checkPack(scope, assign->vars);
TypePackId valuePack = checkPack(scope, assign->values);
addConstraint(scope, PackSubtypeConstraint{valuePack, varPackId});
addConstraint(scope, assign->location, PackSubtypeConstraint{valuePack, varPackId});
}
void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatCompoundAssign* assign)
@ -435,8 +501,6 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatIf* ifStatement
void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatTypeAlias* alias)
{
// TODO: Exported type aliases
auto bindingIt = scope->privateTypeBindings.find(alias->name.value);
ScopePtr* defnIt = astTypeAliasDefiningScopes.find(alias);
// These will be undefined if the alias was a duplicate definition, in which
@ -449,6 +513,12 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatTypeAlias* alia
ScopePtr resolvingScope = *defnIt;
TypeId ty = resolveType(resolvingScope, alias->type, /* topLevel */ true);
if (alias->exported)
{
Name typeName(alias->name.value);
scope->exportedTypeBindings[typeName] = TypeFun{ty};
}
LUAU_ASSERT(get<FreeTypeVar>(bindingIt->second.type));
// Rather than using a subtype constraint, we instead directly bind
@ -457,7 +527,7 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatTypeAlias* alia
// bind the free alias type to an unrelated type, causing havoc.
asMutable(bindingIt->second.type)->ty.emplace<BoundTypeVar>(ty);
addConstraint(scope, NameConstraint{ty, alias->name.value});
addConstraint(scope, alias->location, NameConstraint{ty, alias->name.value});
}
void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatDeclareGlobal* global)
@ -615,44 +685,22 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatDeclareFunction
TypePackId ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstArray<AstExpr*> exprs)
{
if (exprs.size == 0)
return arena->addTypePack({});
std::vector<TypeId> types;
TypePackId last = nullptr;
for (size_t i = 0; i < exprs.size; ++i)
{
if (i < exprs.size - 1)
types.push_back(check(scope, exprs.data[i]));
else
last = checkPack(scope, exprs.data[i]);
}
LUAU_ASSERT(last != nullptr);
return arena->addTypePack(TypePack{std::move(types), last});
}
TypePackId ConstraintGraphBuilder::checkExprList(const ScopePtr& scope, const AstArray<AstExpr*>& exprs)
{
TypePackId result = arena->addTypePack({});
TypePack* resultPack = getMutable<TypePack>(result);
LUAU_ASSERT(resultPack);
std::vector<TypeId> head;
std::optional<TypePackId> tail;
for (size_t i = 0; i < exprs.size; ++i)
{
AstExpr* expr = exprs.data[i];
if (i < exprs.size - 1)
resultPack->head.push_back(check(scope, expr));
head.push_back(check(scope, expr));
else
resultPack->tail = checkPack(scope, expr);
tail = checkPack(scope, expr);
}
if (resultPack->head.empty() && resultPack->tail)
return *resultPack->tail;
if (head.empty() && tail)
return *tail;
else
return result;
return arena->addTypePack(TypePack{std::move(head), tail});
}
TypePackId ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExpr* expr)
@ -683,13 +731,26 @@ TypePackId ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExpr* exp
astOriginalCallTypes[call->func] = fnType;
TypeId instantiatedType = arena->addType(BlockedTypeVar{});
addConstraint(scope, InstantiationConstraint{instantiatedType, fnType});
TypePackId rets = freshTypePack(scope);
TypePackId rets = arena->addTypePack(BlockedTypePack{});
FunctionTypeVar ftv(arena->addTypePack(TypePack{args, {}}), rets);
TypeId inferredFnType = arena->addType(ftv);
addConstraint(scope, SubtypeConstraint{inferredFnType, instantiatedType});
scope->unqueuedConstraints.push_back(
std::make_unique<Constraint>(NotNull{scope.get()}, call->func->location, InstantiationConstraint{instantiatedType, fnType}));
NotNull<const Constraint> ic(scope->unqueuedConstraints.back().get());
scope->unqueuedConstraints.push_back(
std::make_unique<Constraint>(NotNull{scope.get()}, call->func->location, SubtypeConstraint{inferredFnType, instantiatedType}));
NotNull<const Constraint> sc(scope->unqueuedConstraints.back().get());
addConstraint(scope, call->func->location,
FunctionCallConstraint{
{ic, sc},
fnType,
rets,
call,
});
result = rets;
}
else if (AstExprVarargs* varargs = expr->as<AstExprVarargs>())
@ -805,7 +866,7 @@ TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIndexName* in
TypeId expectedTableType = arena->addType(std::move(ttv));
addConstraint(scope, SubtypeConstraint{obj, expectedTableType});
addConstraint(scope, indexName->expr->location, SubtypeConstraint{obj, expectedTableType});
return result;
}
@ -820,7 +881,7 @@ TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIndexExpr* in
TableIndexer indexer{indexType, result};
TypeId tableType = arena->addType(TableTypeVar{TableTypeVar::Props{}, TableIndexer{indexType, result}, TypeLevel{}, TableState::Free});
addConstraint(scope, SubtypeConstraint{obj, tableType});
addConstraint(scope, indexExpr->expr->location, SubtypeConstraint{obj, tableType});
return result;
}
@ -834,7 +895,7 @@ TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprUnary* unary)
case AstExprUnary::Minus:
{
TypeId resultType = arena->addType(BlockedTypeVar{});
addConstraint(scope, UnaryConstraint{AstExprUnary::Minus, operandType, resultType});
addConstraint(scope, unary->location, UnaryConstraint{AstExprUnary::Minus, operandType, resultType});
return resultType;
}
default:
@ -853,19 +914,19 @@ TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprBinary* binar
{
case AstExprBinary::Or:
{
addConstraint(scope, SubtypeConstraint{leftType, rightType});
addConstraint(scope, binary->location, SubtypeConstraint{leftType, rightType});
return leftType;
}
case AstExprBinary::Add:
{
TypeId resultType = arena->addType(BlockedTypeVar{});
addConstraint(scope, BinaryConstraint{AstExprBinary::Add, leftType, rightType, resultType});
addConstraint(scope, binary->location, BinaryConstraint{AstExprBinary::Add, leftType, rightType, resultType});
return resultType;
}
case AstExprBinary::Sub:
{
TypeId resultType = arena->addType(BlockedTypeVar{});
addConstraint(scope, BinaryConstraint{AstExprBinary::Sub, leftType, rightType, resultType});
addConstraint(scope, binary->location, BinaryConstraint{AstExprBinary::Sub, leftType, rightType, resultType});
return resultType;
}
default:
@ -886,8 +947,8 @@ TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIfElse* ifEls
if (ifElse->hasElse)
{
TypeId resultType = arena->addType(BlockedTypeVar{});
addConstraint(scope, SubtypeConstraint{thenType, resultType});
addConstraint(scope, SubtypeConstraint{elseType, resultType});
addConstraint(scope, ifElse->trueExpr->location, SubtypeConstraint{thenType, resultType});
addConstraint(scope, ifElse->falseExpr->location, SubtypeConstraint{elseType, resultType});
return resultType;
}
@ -906,7 +967,7 @@ TypeId ConstraintGraphBuilder::checkExprTable(const ScopePtr& scope, AstExprTabl
TableTypeVar* ttv = getMutable<TableTypeVar>(ty);
LUAU_ASSERT(ttv);
auto createIndexer = [this, scope, ttv](TypeId currentIndexType, TypeId currentResultType) {
auto createIndexer = [this, scope, ttv](const Location& location, TypeId currentIndexType, TypeId currentResultType) {
if (!ttv->indexer)
{
TypeId indexType = this->freshType(scope);
@ -914,8 +975,8 @@ TypeId ConstraintGraphBuilder::checkExprTable(const ScopePtr& scope, AstExprTabl
ttv->indexer = TableIndexer{indexType, resultType};
}
addConstraint(scope, SubtypeConstraint{ttv->indexer->indexType, currentIndexType});
addConstraint(scope, SubtypeConstraint{ttv->indexer->indexResultType, currentResultType});
addConstraint(scope, location, SubtypeConstraint{ttv->indexer->indexType, currentIndexType});
addConstraint(scope, location, SubtypeConstraint{ttv->indexer->indexResultType, currentResultType});
};
for (const AstExprTable::Item& item : expr->items)
@ -937,13 +998,15 @@ TypeId ConstraintGraphBuilder::checkExprTable(const ScopePtr& scope, AstExprTabl
}
else
{
createIndexer(keyTy, itemTy);
createIndexer(item.key->location, keyTy, itemTy);
}
}
else
{
TypeId numberType = singletonTypes.numberType;
createIndexer(numberType, itemTy);
// FIXME? The location isn't quite right here. Not sure what is
// right.
createIndexer(item.value->location, numberType, itemTy);
}
}
@ -1008,7 +1071,7 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS
if (fn->returnAnnotation)
{
TypePackId annotatedRetType = resolveTypePack(signatureScope, *fn->returnAnnotation);
addConstraint(signatureScope, PackSubtypeConstraint{returnType, annotatedRetType});
addConstraint(signatureScope, getLocation(*fn->returnAnnotation), PackSubtypeConstraint{returnType, annotatedRetType});
}
std::vector<TypeId> argTypes;
@ -1022,7 +1085,7 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS
if (local->annotation)
{
TypeId argAnnotation = resolveType(signatureScope, local->annotation, /* topLevel */ true);
addConstraint(signatureScope, SubtypeConstraint{t, argAnnotation});
addConstraint(signatureScope, local->annotation->location, SubtypeConstraint{t, argAnnotation});
}
}
@ -1056,7 +1119,7 @@ void ConstraintGraphBuilder::checkFunctionBody(const ScopePtr& scope, AstExprFun
if (nullptr != getFallthrough(fn->body))
{
TypePackId empty = arena->addTypePack({}); // TODO we could have CSG retain one of these forever
addConstraint(scope, PackSubtypeConstraint{scope->returnType, empty});
addConstraint(scope, fn->location, PackSubtypeConstraint{scope->returnType, empty});
}
}
@ -1066,16 +1129,13 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, b
if (auto ref = ty->as<AstTypeReference>())
{
// TODO: Support imported types w/ require tracing.
LUAU_ASSERT(!ref->prefix);
std::optional<TypeFun> alias = scope->lookupType(ref->name.value);
if (alias.has_value())
if (alias.has_value() || ref->prefix.has_value())
{
// If the alias is not generic, we don't need to set up a blocked
// type and an instantiation constraint.
if (alias->typeParams.empty() && alias->typePackParams.empty())
if (alias.has_value() && alias->typeParams.empty() && alias->typePackParams.empty())
{
result = alias->type;
}
@ -1104,11 +1164,11 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, b
}
}
result = arena->addType(PendingExpansionTypeVar{*alias, parameters, packParameters});
result = arena->addType(PendingExpansionTypeVar{ref->prefix, ref->name, parameters, packParameters});
if (topLevel)
{
addConstraint(scope, TypeAliasExpansionConstraint{ /* target */ result });
addConstraint(scope, ty->location, TypeAliasExpansionConstraint{/* target */ result});
}
}
}
@ -1141,8 +1201,7 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, b
};
}
// TODO: Remove TypeLevel{} here, we don't need it.
result = arena->addType(TableTypeVar{props, indexer, TypeLevel{}, TableState::Sealed});
result = arena->addType(TableTypeVar{props, indexer, scope->level, TableState::Sealed});
}
else if (auto fn = ty->as<AstTypeFunction>())
{
@ -1363,7 +1422,7 @@ TypeId ConstraintGraphBuilder::flattenPack(const ScopePtr& scope, Location locat
TypePack onePack{{typeResult}, freshTypePack(scope)};
TypePackId oneTypePack = arena->addTypePack(std::move(onePack));
addConstraint(scope, PackSubtypeConstraint{tp, oneTypePack});
addConstraint(scope, location, PackSubtypeConstraint{tp, oneTypePack});
return typeResult;
}

View file

@ -1,9 +1,11 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Anyification.h"
#include "Luau/ApplyTypeFunction.h"
#include "Luau/ConstraintSolver.h"
#include "Luau/Instantiation.h"
#include "Luau/Location.h"
#include "Luau/ModuleResolver.h"
#include "Luau/Quantify.h"
#include "Luau/ToString.h"
#include "Luau/Unifier.h"
@ -240,11 +242,17 @@ void dump(ConstraintSolver* cs, ToStringOptions& opts)
}
}
ConstraintSolver::ConstraintSolver(TypeArena* arena, NotNull<Scope> rootScope)
ConstraintSolver::ConstraintSolver(TypeArena* arena, NotNull<Scope> rootScope, ModuleName moduleName, NotNull<ModuleResolver> moduleResolver,
std::vector<RequireCycle> requireCycles)
: arena(arena)
, constraints(collectConstraints(rootScope))
, rootScope(rootScope)
, currentModuleName(std::move(moduleName))
, moduleResolver(moduleResolver)
, requireCycles(requireCycles)
{
opts.exhaustive = true;
for (NotNull<Constraint> c : constraints)
{
unsolvedConstraints.push_back(c);
@ -261,9 +269,6 @@ void ConstraintSolver::run()
if (done())
return;
ToStringOptions opts;
opts.exhaustive = true;
if (FFlag::DebugLuauLogSolver)
{
printf("Starting solver\n");
@ -371,10 +376,14 @@ bool ConstraintSolver::tryDispatch(NotNull<const Constraint> constraint, bool fo
success = tryDispatch(*uc, constraint, force);
else if (auto bc = get<BinaryConstraint>(*constraint))
success = tryDispatch(*bc, constraint, force);
else if (auto ic = get<IterableConstraint>(*constraint))
success = tryDispatch(*ic, constraint, force);
else if (auto nc = get<NameConstraint>(*constraint))
success = tryDispatch(*nc, constraint);
else if (auto taec = get<TypeAliasExpansionConstraint>(*constraint))
success = tryDispatch(*taec, constraint);
else if (auto fcc = get<FunctionCallConstraint>(*constraint))
success = tryDispatch(*fcc, constraint);
else
LUAU_ASSERT(0);
@ -400,6 +409,11 @@ bool ConstraintSolver::tryDispatch(const SubtypeConstraint& c, NotNull<const Con
bool ConstraintSolver::tryDispatch(const PackSubtypeConstraint& c, NotNull<const Constraint> constraint, bool force)
{
if (isBlocked(c.subPack))
return block(c.subPack, constraint);
else if (isBlocked(c.superPack))
return block(c.superPack, constraint);
unify(c.subPack, c.superPack, constraint->scope);
return true;
}
@ -512,6 +526,82 @@ bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNull<const Cons
return true;
}
bool ConstraintSolver::tryDispatch(const IterableConstraint& c, NotNull<const Constraint> constraint, bool force)
{
/*
* for .. in loops can play out in a bunch of different ways depending on
* the shape of iteratee.
*
* iteratee might be:
* * (nextFn)
* * (nextFn, table)
* * (nextFn, table, firstIndex)
* * table with a metatable and __index
* * table with a metatable and __call but no __index (if the metatable has
* both, __index takes precedence)
* * table with an indexer but no __index or __call (or no metatable)
*
* To dispatch this constraint, we need first to know enough about iteratee
* to figure out which of the above shapes we are actually working with.
*
* If `force` is true and we still do not know, we must flag a warning. Type
* families are the fix for this.
*
* Since we need to know all of this stuff about the types of the iteratee,
* we have no choice but for ConstraintSolver to also be the thing that
* applies constraints to the types of the iterators.
*/
auto block_ = [&](auto&& t) {
if (force)
{
// If we haven't figured out the type of the iteratee by now,
// there's nothing we can do.
return true;
}
block(t, constraint);
return false;
};
auto [iteratorTypes, iteratorTail] = flatten(c.iterator);
if (iteratorTail)
return block_(*iteratorTail);
if (0 == iteratorTypes.size())
{
Anyification anyify{
arena, constraint->scope, &iceReporter, getSingletonTypes().errorRecoveryType(), getSingletonTypes().errorRecoveryTypePack()};
std::optional<TypePackId> anyified = anyify.substitute(c.variables);
LUAU_ASSERT(anyified);
unify(*anyified, c.variables, constraint->scope);
return true;
}
TypeId nextTy = follow(iteratorTypes[0]);
if (get<FreeTypeVar>(nextTy))
return block_(nextTy);
if (get<FunctionTypeVar>(nextTy))
{
TypeId tableTy = getSingletonTypes().nilType;
if (iteratorTypes.size() >= 2)
tableTy = iteratorTypes[1];
TypeId firstIndexTy = getSingletonTypes().nilType;
if (iteratorTypes.size() >= 3)
firstIndexTy = iteratorTypes[2];
return tryDispatchIterableFunction(nextTy, tableTy, firstIndexTy, c, constraint, force);
}
else
return tryDispatchIterableTable(iteratorTypes[0], c, constraint, force);
return true;
}
bool ConstraintSolver::tryDispatch(const NameConstraint& c, NotNull<const Constraint> constraint)
{
if (isBlocked(c.namedType))
@ -519,7 +609,7 @@ bool ConstraintSolver::tryDispatch(const NameConstraint& c, NotNull<const Constr
TypeId target = follow(c.namedType);
if (target->persistent)
if (target->persistent || target->owningArena != arena)
return true;
if (TableTypeVar* ttv = getMutable<TableTypeVar>(target))
@ -536,19 +626,27 @@ struct InfiniteTypeFinder : TypeVarOnceVisitor
{
ConstraintSolver* solver;
const InstantiationSignature& signature;
NotNull<Scope> scope;
bool foundInfiniteType = false;
explicit InfiniteTypeFinder(ConstraintSolver* solver, const InstantiationSignature& signature)
explicit InfiniteTypeFinder(ConstraintSolver* solver, const InstantiationSignature& signature, NotNull<Scope> scope)
: solver(solver)
, signature(signature)
, scope(scope)
{
}
bool visit(TypeId ty, const PendingExpansionTypeVar& petv) override
{
auto [typeArguments, packArguments] = saturateArguments(petv.fn, petv.typeArguments, petv.packArguments, solver->arena);
std::optional<TypeFun> tf =
(petv.prefix) ? scope->lookupImportedType(petv.prefix->value, petv.name.value) : scope->lookupType(petv.name.value);
if (follow(petv.fn.type) == follow(signature.fn.type) && (signature.arguments != typeArguments || signature.packArguments != packArguments))
if (!tf.has_value())
return true;
auto [typeArguments, packArguments] = saturateArguments(*tf, petv.typeArguments, petv.packArguments, solver->arena);
if (follow(tf->type) == follow(signature.fn.type) && (signature.arguments != typeArguments || signature.packArguments != packArguments))
{
foundInfiniteType = true;
return false;
@ -563,17 +661,19 @@ struct InstantiationQueuer : TypeVarOnceVisitor
ConstraintSolver* solver;
const InstantiationSignature& signature;
NotNull<Scope> scope;
Location location;
explicit InstantiationQueuer(ConstraintSolver* solver, const InstantiationSignature& signature, NotNull<Scope> scope)
explicit InstantiationQueuer(NotNull<Scope> scope, const Location& location, ConstraintSolver* solver, const InstantiationSignature& signature)
: solver(solver)
, signature(signature)
, scope(scope)
, location(location)
{
}
bool visit(TypeId ty, const PendingExpansionTypeVar& petv) override
{
solver->pushConstraint(TypeAliasExpansionConstraint{ty}, scope);
solver->pushConstraint(scope, location, TypeAliasExpansionConstraint{ty});
return false;
}
};
@ -592,23 +692,32 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
unblock(c.target);
};
// If there are no parameters to the type function we can just use the type
// directly.
if (petv->fn.typeParams.empty() && petv->fn.typePackParams.empty())
std::optional<TypeFun> tf = (petv->prefix) ? constraint->scope->lookupImportedType(petv->prefix->value, petv->name.value)
: constraint->scope->lookupType(petv->name.value);
if (!tf.has_value())
{
bindResult(petv->fn.type);
reportError(UnknownSymbol{petv->name.value, UnknownSymbol::Context::Type}, constraint->location);
bindResult(getSingletonTypes().errorRecoveryType());
return true;
}
auto [typeArguments, packArguments] = saturateArguments(petv->fn, petv->typeArguments, petv->packArguments, arena);
// If there are no parameters to the type function we can just use the type
// directly.
if (tf->typeParams.empty() && tf->typePackParams.empty())
{
bindResult(tf->type);
return true;
}
bool sameTypes =
std::equal(typeArguments.begin(), typeArguments.end(), petv->fn.typeParams.begin(), petv->fn.typeParams.end(), [](auto&& itp, auto&& p) {
auto [typeArguments, packArguments] = saturateArguments(*tf, petv->typeArguments, petv->packArguments, arena);
bool sameTypes = std::equal(typeArguments.begin(), typeArguments.end(), tf->typeParams.begin(), tf->typeParams.end(), [](auto&& itp, auto&& p) {
return itp == p.ty;
});
bool samePacks = std::equal(
packArguments.begin(), packArguments.end(), petv->fn.typePackParams.begin(), petv->fn.typePackParams.end(), [](auto&& itp, auto&& p) {
bool samePacks =
std::equal(packArguments.begin(), packArguments.end(), tf->typePackParams.begin(), tf->typePackParams.end(), [](auto&& itp, auto&& p) {
return itp == p.tp;
});
@ -617,12 +726,12 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
// to the TypeFun's type.
if (sameTypes && samePacks)
{
bindResult(petv->fn.type);
bindResult(tf->type);
return true;
}
InstantiationSignature signature{
petv->fn,
*tf,
typeArguments,
packArguments,
};
@ -642,8 +751,8 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
// https://github.com/Roblox/luau/pull/68 for the RFC responsible for this.
// This is a little nicer than using a recursion limit because we can catch
// the infinite expansion before actually trying to expand it.
InfiniteTypeFinder itf{this, signature};
itf.traverse(petv->fn.type);
InfiniteTypeFinder itf{this, signature, constraint->scope};
itf.traverse(tf->type);
if (itf.foundInfiniteType)
{
@ -655,15 +764,15 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
ApplyTypeFunction applyTypeFunction{arena};
for (size_t i = 0; i < typeArguments.size(); ++i)
{
applyTypeFunction.typeArguments[petv->fn.typeParams[i].ty] = typeArguments[i];
applyTypeFunction.typeArguments[tf->typeParams[i].ty] = typeArguments[i];
}
for (size_t i = 0; i < packArguments.size(); ++i)
{
applyTypeFunction.typePackArguments[petv->fn.typePackParams[i].tp] = packArguments[i];
applyTypeFunction.typePackArguments[tf->typePackParams[i].tp] = packArguments[i];
}
std::optional<TypeId> maybeInstantiated = applyTypeFunction.substitute(petv->fn.type);
std::optional<TypeId> maybeInstantiated = applyTypeFunction.substitute(tf->type);
// Note that ApplyTypeFunction::encounteredForwardedType is never set in
// DCR, because we do not use free types for forward-declared generic
// aliases.
@ -683,7 +792,7 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
// Type function application will happily give us the exact same type if
// there are e.g. generic saturatedTypeArguments that go unused.
bool needsClone = follow(petv->fn.type) == target;
bool needsClone = follow(tf->type) == target;
// Only tables have the properties we're trying to set.
TableTypeVar* ttv = getMutableTableType(target);
@ -722,7 +831,7 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
// The application is not recursive, so we need to queue up application of
// any child type function instantiations within the result in order for it
// to be complete.
InstantiationQueuer queuer{this, signature, constraint->scope};
InstantiationQueuer queuer{constraint->scope, constraint->location, this, signature};
queuer.traverse(target);
instantiatedAliases[signature] = target;
@ -730,6 +839,152 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
return true;
}
bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<const Constraint> constraint)
{
TypeId fn = follow(c.fn);
TypePackId result = follow(c.result);
if (isBlocked(c.fn))
{
return block(c.fn, constraint);
}
const FunctionTypeVar* ftv = get<FunctionTypeVar>(fn);
bool usedMagic = false;
if (ftv && ftv->dcrMagicFunction != nullptr)
{
usedMagic = ftv->dcrMagicFunction(NotNull(this), result, c.astFragment);
}
if (!usedMagic)
{
for (const auto& inner : c.innerConstraints)
{
unsolvedConstraints.push_back(inner);
}
asMutable(c.result)->ty.emplace<FreeTypePack>(constraint->scope);
}
unblock(c.result);
return true;
}
bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const IterableConstraint& c, NotNull<const Constraint> constraint, bool force)
{
auto block_ = [&](auto&& t) {
if (force)
{
// TODO: I believe it is the case that, if we are asked to force
// this constraint, then we can do nothing but fail. I'd like to
// find a code sample that gets here.
LUAU_ASSERT(0);
}
else
block(t, constraint);
return false;
};
// We may have to block here if we don't know what the iteratee type is,
// if it's a free table, if we don't know it has a metatable, and so on.
iteratorTy = follow(iteratorTy);
if (get<FreeTypeVar>(iteratorTy))
return block_(iteratorTy);
auto anyify = [&](auto ty) {
Anyification anyify{arena, constraint->scope, &iceReporter, getSingletonTypes().anyType, getSingletonTypes().anyTypePack};
std::optional anyified = anyify.substitute(ty);
if (!anyified)
reportError(CodeTooComplex{}, constraint->location);
else
unify(*anyified, ty, constraint->scope);
};
auto errorify = [&](auto ty) {
Anyification anyify{
arena, constraint->scope, &iceReporter, getSingletonTypes().errorRecoveryType(), getSingletonTypes().errorRecoveryTypePack()};
std::optional errorified = anyify.substitute(ty);
if (!errorified)
reportError(CodeTooComplex{}, constraint->location);
else
unify(*errorified, ty, constraint->scope);
};
if (get<AnyTypeVar>(iteratorTy))
{
anyify(c.variables);
return true;
}
if (get<ErrorTypeVar>(iteratorTy))
{
errorify(c.variables);
return true;
}
// Irksome: I don't think we have any way to guarantee that this table
// type never has a metatable.
if (auto iteratorTable = get<TableTypeVar>(iteratorTy))
{
if (iteratorTable->state == TableState::Free)
return block_(iteratorTy);
if (iteratorTable->indexer)
{
TypePackId expectedVariablePack = arena->addTypePack({iteratorTable->indexer->indexType, iteratorTable->indexer->indexResultType});
unify(c.variables, expectedVariablePack, constraint->scope);
}
else
errorify(c.variables);
}
else if (auto iteratorMetatable = get<MetatableTypeVar>(iteratorTy))
{
TypeId metaTy = follow(iteratorMetatable->metatable);
if (get<FreeTypeVar>(metaTy))
return block_(metaTy);
LUAU_ASSERT(0);
}
else
errorify(c.variables);
return true;
}
bool ConstraintSolver::tryDispatchIterableFunction(
TypeId nextTy, TypeId tableTy, TypeId firstIndexTy, const IterableConstraint& c, NotNull<const Constraint> constraint, bool force)
{
// We need to know whether or not this type is nil or not.
// If we don't know, block and reschedule ourselves.
firstIndexTy = follow(firstIndexTy);
if (get<FreeTypeVar>(firstIndexTy))
{
if (force)
LUAU_ASSERT(0);
else
block(firstIndexTy, constraint);
return false;
}
const TypeId firstIndex = isNil(firstIndexTy) ? arena->freshType(constraint->scope) // FIXME: Surely this should be a union (free | nil)
: firstIndexTy;
// nextTy : (tableTy, indexTy?) -> (indexTy, valueTailTy...)
const TypePackId nextArgPack = arena->addTypePack({tableTy, arena->addType(UnionTypeVar{{firstIndex, getSingletonTypes().nilType}})});
const TypePackId valueTailTy = arena->addTypePack(FreeTypePack{constraint->scope});
const TypePackId nextRetPack = arena->addTypePack(TypePack{{firstIndex}, valueTailTy});
const TypeId expectedNextTy = arena->addType(FunctionTypeVar{nextArgPack, nextRetPack});
unify(nextTy, expectedNextTy, constraint->scope);
pushConstraint(constraint->scope, constraint->location, PackSubtypeConstraint{c.variables, nextRetPack});
return true;
}
void ConstraintSolver::block_(BlockedConstraintId target, NotNull<const Constraint> constraint)
{
blocked[target].push_back(constraint);
@ -741,14 +996,14 @@ void ConstraintSolver::block_(BlockedConstraintId target, NotNull<const Constrai
void ConstraintSolver::block(NotNull<const Constraint> target, NotNull<const Constraint> constraint)
{
if (FFlag::DebugLuauLogSolver)
printf("block Constraint %s on\t%s\n", toString(*target).c_str(), toString(*constraint).c_str());
printf("block Constraint %s on\t%s\n", toString(*target, opts).c_str(), toString(*constraint, opts).c_str());
block_(target, constraint);
}
bool ConstraintSolver::block(TypeId target, NotNull<const Constraint> constraint)
{
if (FFlag::DebugLuauLogSolver)
printf("block TypeId %s on\t%s\n", toString(target).c_str(), toString(*constraint).c_str());
printf("block TypeId %s on\t%s\n", toString(target, opts).c_str(), toString(*constraint, opts).c_str());
block_(target, constraint);
return false;
}
@ -756,7 +1011,7 @@ bool ConstraintSolver::block(TypeId target, NotNull<const Constraint> constraint
bool ConstraintSolver::block(TypePackId target, NotNull<const Constraint> constraint)
{
if (FFlag::DebugLuauLogSolver)
printf("block TypeId %s on\t%s\n", toString(target).c_str(), toString(*constraint).c_str());
printf("block TypeId %s on\t%s\n", toString(target, opts).c_str(), toString(*constraint, opts).c_str());
block_(target, constraint);
return false;
}
@ -772,7 +1027,7 @@ void ConstraintSolver::unblock_(BlockedConstraintId progressed)
{
auto& count = blockedConstraints[unblockedConstraint];
if (FFlag::DebugLuauLogSolver)
printf("Unblocking count=%d\t%s\n", int(count), toString(*unblockedConstraint).c_str());
printf("Unblocking count=%d\t%s\n", int(count), toString(*unblockedConstraint, opts).c_str());
// This assertion being hit indicates that `blocked` and
// `blockedConstraints` desynchronized at some point. This is problematic
@ -817,6 +1072,11 @@ bool ConstraintSolver::isBlocked(TypeId ty)
return nullptr != get<BlockedTypeVar>(follow(ty)) || nullptr != get<PendingExpansionTypeVar>(follow(ty));
}
bool ConstraintSolver::isBlocked(TypePackId tp)
{
return nullptr != get<BlockedTypePack>(follow(tp));
}
bool ConstraintSolver::isBlocked(NotNull<const Constraint> constraint)
{
auto blockedIt = blockedConstraints.find(constraint);
@ -830,6 +1090,13 @@ void ConstraintSolver::unify(TypeId subType, TypeId superType, NotNull<Scope> sc
u.tryUnify(subType, superType);
if (!u.errors.empty())
{
TypeId errorType = getSingletonTypes().errorRecoveryType();
u.tryUnify(subType, errorType);
u.tryUnify(superType, errorType);
}
const auto [changedTypes, changedPacks] = u.log.getChanges();
u.log.commit();
@ -853,22 +1120,69 @@ void ConstraintSolver::unify(TypePackId subPack, TypePackId superPack, NotNull<S
unblock(changedPacks);
}
void ConstraintSolver::pushConstraint(ConstraintV cv, NotNull<Scope> scope)
void ConstraintSolver::pushConstraint(NotNull<Scope> scope, const Location& location, ConstraintV cv)
{
std::unique_ptr<Constraint> c = std::make_unique<Constraint>(std::move(cv), scope);
std::unique_ptr<Constraint> c = std::make_unique<Constraint>(scope, location, std::move(cv));
NotNull<Constraint> borrow = NotNull(c.get());
solverConstraints.push_back(std::move(c));
unsolvedConstraints.push_back(borrow);
}
TypeId ConstraintSolver::resolveModule(const ModuleInfo& info, const Location& location)
{
if (info.name.empty())
{
reportError(UnknownRequire{}, location);
return getSingletonTypes().errorRecoveryType();
}
std::string humanReadableName = moduleResolver->getHumanReadableModuleName(info.name);
for (const auto& [location, path] : requireCycles)
{
if (!path.empty() && path.front() == humanReadableName)
return getSingletonTypes().anyType;
}
ModulePtr module = moduleResolver->getModule(info.name);
if (!module)
{
if (!moduleResolver->moduleExists(info.name) && !info.optional)
reportError(UnknownRequire{humanReadableName}, location);
return getSingletonTypes().errorRecoveryType();
}
if (module->type != SourceCode::Type::Module)
{
reportError(IllegalRequire{humanReadableName, "Module is not a ModuleScript. It cannot be required."}, location);
return getSingletonTypes().errorRecoveryType();
}
TypePackId modulePack = module->getModuleScope()->returnType;
if (get<Unifiable::Error>(modulePack))
return getSingletonTypes().errorRecoveryType();
std::optional<TypeId> moduleType = first(modulePack);
if (!moduleType)
{
reportError(IllegalRequire{humanReadableName, "Module does not return exactly 1 value. It cannot be required."}, location);
return getSingletonTypes().errorRecoveryType();
}
return *moduleType;
}
void ConstraintSolver::reportError(TypeErrorData&& data, const Location& location)
{
errors.emplace_back(location, std::move(data));
errors.back().moduleName = currentModuleName;
}
void ConstraintSolver::reportError(TypeError e)
{
errors.emplace_back(std::move(e));
errors.back().moduleName = currentModuleName;
}
} // namespace Luau

View file

@ -3,6 +3,7 @@
#include "Luau/ConstraintSolverLogger.h"
#include "Luau/JsonEmitter.h"
#include "Luau/ToString.h"
LUAU_FASTFLAG(LuauFixNameMaps);

View file

@ -14,6 +14,7 @@
#include "Luau/TypeChecker2.h"
#include "Luau/TypeInfer.h"
#include "Luau/Variant.h"
#include "Luau/BuiltinDefinitions.h"
#include <algorithm>
#include <chrono>
@ -99,7 +100,7 @@ LoadDefinitionFileResult Frontend::loadDefinitionFile(std::string_view source, c
module.root = parseResult.root;
module.mode = Mode::Definition;
ModulePtr checkedModule = check(module, Mode::Definition, globalScope);
ModulePtr checkedModule = check(module, Mode::Definition, globalScope, {});
if (checkedModule->errors.size() > 0)
return LoadDefinitionFileResult{false, parseResult, checkedModule};
@ -526,7 +527,7 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
typeChecker.requireCycles = requireCycles;
ModulePtr module = FFlag::DebugLuauDeferredConstraintResolution ? check(sourceModule, mode, environmentScope)
ModulePtr module = FFlag::DebugLuauDeferredConstraintResolution ? check(sourceModule, mode, environmentScope, requireCycles)
: typeChecker.check(sourceModule, mode, environmentScope);
stats.timeCheck += getTimestamp() - timestamp;
@ -832,15 +833,15 @@ ScopePtr Frontend::getGlobalScope()
return globalScope;
}
ModulePtr Frontend::check(const SourceModule& sourceModule, Mode mode, const ScopePtr& environmentScope)
ModulePtr Frontend::check(const SourceModule& sourceModule, Mode mode, const ScopePtr& environmentScope, std::vector<RequireCycle> requireCycles)
{
ModulePtr result = std::make_shared<Module>();
ConstraintGraphBuilder cgb{sourceModule.name, result, &result->internalTypes, NotNull(&iceHandler), getGlobalScope()};
ConstraintGraphBuilder cgb{sourceModule.name, result, &result->internalTypes, NotNull(&moduleResolver), NotNull(&iceHandler), getGlobalScope()};
cgb.visit(sourceModule.root);
result->errors = std::move(cgb.errors);
ConstraintSolver cs{&result->internalTypes, NotNull(cgb.rootScope)};
ConstraintSolver cs{&result->internalTypes, NotNull(cgb.rootScope), sourceModule.name, NotNull(&moduleResolver), requireCycles};
cs.run();
for (TypeError& e : cs.errors)
@ -852,11 +853,12 @@ ModulePtr Frontend::check(const SourceModule& sourceModule, Mode mode, const Sco
result->astOriginalCallTypes = std::move(cgb.astOriginalCallTypes);
result->astResolvedTypes = std::move(cgb.astResolvedTypes);
result->astResolvedTypePacks = std::move(cgb.astResolvedTypePacks);
result->clonePublicInterface(iceHandler);
result->type = sourceModule.type;
Luau::check(sourceModule, result.get());
result->clonePublicInterface(iceHandler);
return result;
}

View file

@ -46,6 +46,7 @@ TypeId Instantiation::clean(TypeId ty)
FunctionTypeVar clone = FunctionTypeVar{level, ftv->argTypes, ftv->retTypes, ftv->definition, ftv->hasSelf};
clone.magicFunction = ftv->magicFunction;
clone.dcrMagicFunction = ftv->dcrMagicFunction;
clone.tags = ftv->tags;
clone.argNames = ftv->argNames;
TypeId result = addType(std::move(clone));

View file

@ -11,7 +11,8 @@ namespace Luau::Json
static constexpr int CHUNK_SIZE = 1024;
ObjectEmitter::ObjectEmitter(NotNull<JsonEmitter> emitter)
: emitter(emitter), finished(false)
: emitter(emitter)
, finished(false)
{
comma = emitter->pushComma();
emitter->writeRaw('{');
@ -33,7 +34,8 @@ void ObjectEmitter::finish()
}
ArrayEmitter::ArrayEmitter(NotNull<JsonEmitter> emitter)
: emitter(emitter), finished(false)
: emitter(emitter)
, finished(false)
{
comma = emitter->pushComma();
emitter->writeRaw('[');

View file

@ -216,7 +216,8 @@ static bool similar(AstExpr* lhs, AstExpr* rhs)
return false;
for (size_t i = 0; i < le->strings.size; ++i)
if (le->strings.data[i].size != re->strings.data[i].size || memcmp(le->strings.data[i].data, re->strings.data[i].data, le->strings.data[i].size) != 0)
if (le->strings.data[i].size != re->strings.data[i].size ||
memcmp(le->strings.data[i].data, re->strings.data[i].data, le->strings.data[i].size) != 0)
return false;
for (size_t i = 0; i < le->expressions.size; ++i)
@ -2675,13 +2676,18 @@ public:
private:
LintContext* context;
bool isComparison(AstExprBinary::Op op)
static bool isEquality(AstExprBinary::Op op)
{
return op == AstExprBinary::CompareNe || op == AstExprBinary::CompareEq;
}
static bool isComparison(AstExprBinary::Op op)
{
return op == AstExprBinary::CompareNe || op == AstExprBinary::CompareEq || op == AstExprBinary::CompareLt || op == AstExprBinary::CompareLe ||
op == AstExprBinary::CompareGt || op == AstExprBinary::CompareGe;
}
bool isNot(AstExpr* node)
static bool isNot(AstExpr* node)
{
AstExprUnary* expr = node->as<AstExprUnary>();
@ -2698,21 +2704,25 @@ private:
{
std::string op = toString(node->op);
if (node->op == AstExprBinary::CompareEq || node->op == AstExprBinary::CompareNe)
if (isEquality(node->op))
emitWarning(*context, LintWarning::Code_ComparisonPrecedence, node->location,
"not X %s Y is equivalent to (not X) %s Y; consider using X %s Y, or wrap one of the expressions in parentheses to silence",
op.c_str(), op.c_str(), node->op == AstExprBinary::CompareEq ? "~=" : "==");
"not X %s Y is equivalent to (not X) %s Y; consider using X %s Y, or add parentheses to silence", op.c_str(), op.c_str(),
node->op == AstExprBinary::CompareEq ? "~=" : "==");
else
emitWarning(*context, LintWarning::Code_ComparisonPrecedence, node->location,
"not X %s Y is equivalent to (not X) %s Y; wrap one of the expressions in parentheses to silence", op.c_str(), op.c_str());
"not X %s Y is equivalent to (not X) %s Y; add parentheses to silence", op.c_str(), op.c_str());
}
else if (AstExprBinary* left = node->left->as<AstExprBinary>(); left && isComparison(left->op))
{
std::string lop = toString(left->op);
std::string rop = toString(node->op);
if (isEquality(left->op) || isEquality(node->op))
emitWarning(*context, LintWarning::Code_ComparisonPrecedence, node->location,
"X %s Y %s Z is equivalent to (X %s Y) %s Z; wrap one of the expressions in parentheses to silence", lop.c_str(), rop.c_str(),
"X %s Y %s Z is equivalent to (X %s Y) %s Z; add parentheses to silence", lop.c_str(), rop.c_str(), lop.c_str(), rop.c_str());
else
emitWarning(*context, LintWarning::Code_ComparisonPrecedence, node->location,
"X %s Y %s Z is equivalent to (X %s Y) %s Z; did you mean X %s Y and Y %s Z?", lop.c_str(), rop.c_str(), lop.c_str(), rop.c_str(),
lop.c_str(), rop.c_str());
}

View file

@ -219,8 +219,7 @@ void Module::clonePublicInterface(InternalErrorReporter& ice)
TypePackId returnType = moduleScope->returnType;
std::optional<TypePackId> varargPack = FFlag::DebugLuauDeferredConstraintResolution ? std::nullopt : moduleScope->varargPack;
std::unordered_map<Name, TypeFun>* exportedTypeBindings =
FFlag::DebugLuauDeferredConstraintResolution ? nullptr : &moduleScope->exportedTypeBindings;
std::unordered_map<Name, TypeFun>* exportedTypeBindings = &moduleScope->exportedTypeBindings;
TxnLog log;
ClonePublicInterface clonePublicInterface{&log, this};

View file

@ -1036,6 +1036,13 @@ struct TypePackStringifier
{
stringify(btv.boundTo);
}
void operator()(TypePackId, const BlockedTypePack& btp)
{
state.emit("*blocked-tp-");
state.emit(btp.index);
state.emit("*");
}
};
void TypeVarStringifier::stringify(TypePackId tp)
@ -1099,9 +1106,8 @@ ToStringResult toStringDetailed(TypeId ty, ToStringOptions& opts)
ToStringResult result;
StringifierState state = FFlag::LuauFixNameMaps
? StringifierState{opts, result, opts.nameMap}
: StringifierState{opts, result, opts.DEPRECATED_nameMap};
StringifierState state =
FFlag::LuauFixNameMaps ? StringifierState{opts, result, opts.nameMap} : StringifierState{opts, result, opts.DEPRECATED_nameMap};
std::set<TypeId> cycles;
std::set<TypePackId> cycleTPs;
@ -1211,9 +1217,8 @@ ToStringResult toStringDetailed(TypePackId tp, ToStringOptions& opts)
* 4. Print out the root of the type using the same algorithm as step 3.
*/
ToStringResult result;
StringifierState state = FFlag::LuauFixNameMaps
? StringifierState{opts, result, opts.nameMap}
: StringifierState{opts, result, opts.DEPRECATED_nameMap};
StringifierState state =
FFlag::LuauFixNameMaps ? StringifierState{opts, result, opts.nameMap} : StringifierState{opts, result, opts.DEPRECATED_nameMap};
std::set<TypeId> cycles;
std::set<TypePackId> cycleTPs;
@ -1302,9 +1307,8 @@ std::string toString(const TypePackVar& tp, ToStringOptions& opts)
std::string toStringNamedFunction(const std::string& funcName, const FunctionTypeVar& ftv, ToStringOptions& opts)
{
ToStringResult result;
StringifierState state = FFlag::LuauFixNameMaps
? StringifierState{opts, result, opts.nameMap}
: StringifierState{opts, result, opts.DEPRECATED_nameMap};
StringifierState state =
FFlag::LuauFixNameMaps ? StringifierState{opts, result, opts.nameMap} : StringifierState{opts, result, opts.DEPRECATED_nameMap};
TypeVarStringifier tvs{state};
state.emit(funcName);
@ -1437,8 +1441,7 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts)
using T = std::decay_t<decltype(c)>;
// TODO: Inline and delete this function when clipping FFlag::LuauFixNameMaps
auto tos = [](auto&& a, ToStringOptions& opts)
{
auto tos = [](auto&& a, ToStringOptions& opts) {
if (FFlag::LuauFixNameMaps)
return toString(a, opts);
else
@ -1488,6 +1491,13 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts)
return resultStr + " ~ Binary<" + toString(c.op) + ", " + leftStr + ", " + rightStr + ">";
}
else if constexpr (std::is_same_v<T, IterableConstraint>)
{
std::string iteratorStr = tos(c.iterator, opts);
std::string variableStr = tos(c.variables, opts);
return variableStr + " ~ Iterate<" + iteratorStr + ">";
}
else if constexpr (std::is_same_v<T, NameConstraint>)
{
std::string namedStr = tos(c.namedType, opts);
@ -1498,6 +1508,10 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts)
std::string targetStr = tos(c.target, opts);
return "expand " + targetStr;
}
else if constexpr (std::is_same_v<T, FunctionCallConstraint>)
{
return "call " + tos(c.fn, opts) + " with { result = " + tos(c.result, opts) + " }";
}
else
static_assert(always_false_v<T>, "Non-exhaustive constraint switch");
};

View file

@ -31,6 +31,15 @@ TypeId TypeArena::freshType(TypeLevel level)
return allocated;
}
TypeId TypeArena::freshType(Scope* scope)
{
TypeId allocated = typeVars.allocate(FreeTypeVar{scope});
asMutable(allocated)->owningArena = this;
return allocated;
}
TypePackId TypeArena::addTypePack(std::initializer_list<TypeId> types)
{
TypePackId allocated = typePacks.allocate(TypePack{std::move(types)});
@ -40,9 +49,9 @@ TypePackId TypeArena::addTypePack(std::initializer_list<TypeId> types)
return allocated;
}
TypePackId TypeArena::addTypePack(std::vector<TypeId> types)
TypePackId TypeArena::addTypePack(std::vector<TypeId> types, std::optional<TypePackId> tail)
{
TypePackId allocated = typePacks.allocate(TypePack{std::move(types)});
TypePackId allocated = typePacks.allocate(TypePack{std::move(types), tail});
asMutable(allocated)->owningArena = this;

View file

@ -373,6 +373,11 @@ public:
return Luau::visit(*this, btp.boundTo->ty);
}
AstTypePack* operator()(const BlockedTypePack& btp) const
{
return allocator->alloc<AstTypePackGeneric>(Location(), AstName("*blocked*"));
}
AstTypePack* operator()(const TypePack& tp) const
{
AstArray<AstType*> head;

View file

@ -166,7 +166,8 @@ struct TypeChecker2
auto pusher = pushStack(stat);
if (0)
{}
{
}
else if (auto s = stat->as<AstStatBlock>())
return visit(s);
else if (auto s = stat->as<AstStatIf>())
@ -239,11 +240,9 @@ struct TypeChecker2
visit(repeatStatement->condition);
}
void visit(AstStatBreak*)
{}
void visit(AstStatBreak*) {}
void visit(AstStatContinue*)
{}
void visit(AstStatContinue*) {}
void visit(AstStatReturn* ret)
{
@ -339,6 +338,50 @@ struct TypeChecker2
visit(forStatement->body);
}
// "Render" a type pack out to an array of a given length. Expands
// variadics and various other things to get there.
static std::vector<TypeId> flatten(TypeArena& arena, TypePackId pack, size_t length)
{
std::vector<TypeId> result;
auto it = begin(pack);
auto endIt = end(pack);
while (it != endIt)
{
result.push_back(*it);
if (result.size() >= length)
return result;
++it;
}
if (!it.tail())
return result;
TypePackId tail = *it.tail();
if (get<TypePack>(tail))
LUAU_ASSERT(0);
else if (auto vtp = get<VariadicTypePack>(tail))
{
while (result.size() < length)
result.push_back(vtp->ty);
}
else if (get<FreeTypePack>(tail) || get<GenericTypePack>(tail))
{
while (result.size() < length)
result.push_back(arena.addType(FreeTypeVar{nullptr}));
}
else if (auto etp = get<Unifiable::Error>(tail))
{
while (result.size() < length)
result.push_back(getSingletonTypes().errorRecoveryType());
}
return result;
}
void visit(AstStatForIn* forInStatement)
{
for (AstLocal* local : forInStatement->vars)
@ -351,6 +394,128 @@ struct TypeChecker2
visit(expr);
visit(forInStatement->body);
// Rule out crazy stuff. Maybe possible if the file is not syntactically valid.
if (!forInStatement->vars.size || !forInStatement->values.size)
return;
NotNull<Scope> scope = stack.back();
TypeArena tempArena;
std::vector<TypeId> variableTypes;
for (AstLocal* var : forInStatement->vars)
{
std::optional<TypeId> ty = scope->lookup(var);
LUAU_ASSERT(ty);
variableTypes.emplace_back(*ty);
}
// ugh. There's nothing in the AST to hang a whole type pack on for the
// set of iteratees, so we have to piece it back together by hand.
std::vector<TypeId> valueTypes;
for (size_t i = 0; i < forInStatement->values.size - 1; ++i)
valueTypes.emplace_back(lookupType(forInStatement->values.data[i]));
TypePackId iteratorTail = lookupPack(forInStatement->values.data[forInStatement->values.size - 1]);
TypePackId iteratorPack = tempArena.addTypePack(valueTypes, iteratorTail);
// ... and then expand it out to 3 values (if possible)
const std::vector<TypeId> iteratorTypes = flatten(tempArena, iteratorPack, 3);
if (iteratorTypes.empty())
{
reportError(GenericError{"for..in loops require at least one value to iterate over. Got zero"}, getLocation(forInStatement->values));
return;
}
TypeId iteratorTy = follow(iteratorTypes[0]);
/*
* If the first iterator argument is a function
* * There must be 1 to 3 iterator arguments. Name them (nextTy,
* arrayTy, startIndexTy)
* * The return type of nextTy() must correspond to the variables'
* types and counts. HOWEVER the first iterator will never be nil.
* * The first return value of nextTy must be compatible with
* startIndexTy.
* * The first argument to nextTy() must be compatible with arrayTy if
* present. nil if not.
* * The second argument to nextTy() must be compatible with
* startIndexTy if it is present. Else, it must be compatible with
* nil.
* * nextTy() must be callable with only 2 arguments.
*/
if (const FunctionTypeVar* nextFn = get<FunctionTypeVar>(iteratorTy))
{
if (iteratorTypes.size() < 1 || iteratorTypes.size() > 3)
reportError(GenericError{"for..in loops must be passed (next, [table[, state]])"}, getLocation(forInStatement->values));
// It is okay if there aren't enough iterators, but the iteratee must provide enough.
std::vector<TypeId> expectedVariableTypes = flatten(tempArena, nextFn->retTypes, variableTypes.size());
if (expectedVariableTypes.size() < variableTypes.size())
reportError(GenericError{"next() does not return enough values"}, forInStatement->vars.data[0]->location);
for (size_t i = 0; i < std::min(expectedVariableTypes.size(), variableTypes.size()); ++i)
reportErrors(tryUnify(scope, forInStatement->vars.data[i]->location, variableTypes[i], expectedVariableTypes[i]));
// nextFn is going to be invoked with (arrayTy, startIndexTy)
// It will be passed two arguments on every iteration save the
// first.
// It may be invoked with 0 or 1 argument on the first iteration.
// This depends on the types in iterateePack and therefore
// iteratorTypes.
// If iteratorTypes is too short to be a valid call to nextFn, we have to report a count mismatch error.
// If 2 is too short to be a valid call to nextFn, we have to report a count mismatch error.
// If 2 is too long to be a valid call to nextFn, we have to report a count mismatch error.
auto [minCount, maxCount] = getParameterExtents(TxnLog::empty(), nextFn->argTypes);
if (minCount > 2)
reportError(CountMismatch{2, minCount, CountMismatch::Arg}, forInStatement->vars.data[0]->location);
if (maxCount && *maxCount < 2)
reportError(CountMismatch{2, *maxCount, CountMismatch::Arg}, forInStatement->vars.data[0]->location);
const std::vector<TypeId> flattenedArgTypes = flatten(tempArena, nextFn->argTypes, 2);
const auto [argTypes, argsTail] = Luau::flatten(nextFn->argTypes);
size_t firstIterationArgCount = iteratorTypes.empty() ? 0 : iteratorTypes.size() - 1;
size_t actualArgCount = expectedVariableTypes.size();
if (firstIterationArgCount < minCount)
reportError(CountMismatch{2, firstIterationArgCount, CountMismatch::Arg}, forInStatement->vars.data[0]->location);
else if (actualArgCount < minCount)
reportError(CountMismatch{2, actualArgCount, CountMismatch::Arg}, forInStatement->vars.data[0]->location);
if (iteratorTypes.size() >= 2 && flattenedArgTypes.size() > 0)
{
size_t valueIndex = forInStatement->values.size > 1 ? 1 : 0;
reportErrors(tryUnify(scope, forInStatement->values.data[valueIndex]->location, iteratorTypes[1], flattenedArgTypes[0]));
}
if (iteratorTypes.size() == 3 && flattenedArgTypes.size() > 1)
{
size_t valueIndex = forInStatement->values.size > 2 ? 2 : 0;
reportErrors(tryUnify(scope, forInStatement->values.data[valueIndex]->location, iteratorTypes[2], flattenedArgTypes[1]));
}
}
else if (const TableTypeVar* ttv = get<TableTypeVar>(iteratorTy))
{
if ((forInStatement->vars.size == 1 || forInStatement->vars.size == 2) && ttv->indexer)
{
reportErrors(tryUnify(scope, forInStatement->vars.data[0]->location, variableTypes[0], ttv->indexer->indexType));
if (variableTypes.size() == 2)
reportErrors(tryUnify(scope, forInStatement->vars.data[1]->location, variableTypes[1], ttv->indexer->indexResultType));
}
else
reportError(GenericError{"Cannot iterate over a table without indexer"}, forInStatement->values.data[0]->location);
}
else if (get<AnyTypeVar>(iteratorTy) || get<ErrorTypeVar>(iteratorTy))
{
// nothing
}
else
{
reportError(CannotCallNonFunction{iteratorTy}, forInStatement->values.data[0]->location);
}
}
void visit(AstStatAssign* assign)
@ -456,7 +621,8 @@ struct TypeChecker2
auto StackPusher = pushStack(expr);
if (0)
{}
{
}
else if (auto e = expr->as<AstExprGroup>())
return visit(e);
else if (auto e = expr->as<AstExprConstantNil>())
@ -561,9 +727,21 @@ struct TypeChecker2
TypePackId expectedRetType = lookupPack(call);
TypeId functionType = lookupType(call->func);
TypeId instantiatedFunctionType = instantiation.substitute(functionType).value_or(nullptr);
LUAU_ASSERT(functionType);
if (get<AnyTypeVar>(functionType) || get<ErrorTypeVar>(functionType))
return;
// TODO: Lots of other types are callable: intersections of functions
// and things with the __call metamethod.
if (!get<FunctionTypeVar>(functionType))
{
reportError(CannotCallNonFunction{functionType}, call->func->location);
return;
}
TypeId instantiatedFunctionType = follow(instantiation.substitute(functionType).value_or(nullptr));
TypePack args;
for (AstExpr* arg : call->args)
{
@ -575,12 +753,11 @@ struct TypeChecker2
TypePackId argsTp = arena.addTypePack(args);
FunctionTypeVar ftv{argsTp, expectedRetType};
TypeId expectedType = arena.addType(ftv);
if (!isSubtype(expectedType, instantiatedFunctionType, stack.back(), ice))
{
unfreeze(module->interfaceTypes);
CloneState cloneState;
expectedType = clone(expectedType, module->interfaceTypes, cloneState);
freeze(module->interfaceTypes);
expectedType = clone(expectedType, module->internalTypes, cloneState);
reportError(TypeMismatch{expectedType, functionType}, call->location);
}
}
@ -592,7 +769,8 @@ struct TypeChecker2
// leftType must have a property called indexName->index
std::optional<TypeId> ty = getIndexTypeFromType(module->getModuleScope(), leftType, indexName->index.value, indexName->location, /* addErrors */ true);
std::optional<TypeId> ty =
getIndexTypeFromType(module->getModuleScope(), leftType, indexName->index.value, indexName->location, /* addErrors */ true);
if (ty)
{
if (!isSubtype(resultType, *ty, stack.back(), ice))
@ -972,18 +1150,34 @@ struct TypeChecker2
}
}
void reportError(TypeErrorData&& data, const Location& location)
template<typename TID>
ErrorVec tryUnify(NotNull<Scope> scope, const Location& location, TID subTy, TID superTy)
{
UnifierSharedState sharedState{&ice};
Unifier u{&module->internalTypes, Mode::Strict, scope, location, Covariant, sharedState};
u.anyIsTop = true;
u.tryUnify(subTy, superTy);
return std::move(u.errors);
}
void reportError(TypeErrorData data, const Location& location)
{
module->errors.emplace_back(location, sourceModule->name, std::move(data));
}
void reportError(TypeError e)
{
module->errors.emplace_back(std::move(e));
reportError(std::move(e.data), e.location);
}
std::optional<TypeId> getIndexTypeFromType(
const ScopePtr& scope, TypeId type, const std::string& prop, const Location& location, bool addErrors)
void reportErrors(ErrorVec errors)
{
for (TypeError e : errors)
reportError(std::move(e));
}
std::optional<TypeId> getIndexTypeFromType(const ScopePtr& scope, TypeId type, const std::string& prop, const Location& location, bool addErrors)
{
return Luau::getIndexTypeFromType(scope, module->errors, &module->internalTypes, type, prop, location, addErrors, ice);
}

View file

@ -32,7 +32,6 @@ LUAU_FASTINTVARIABLE(LuauCheckRecursionLimit, 300)
LUAU_FASTINTVARIABLE(LuauVisitRecursionLimit, 500)
LUAU_FASTFLAG(LuauKnowsTheDataModel3)
LUAU_FASTFLAG(LuauAutocompleteDynamicLimits)
LUAU_FASTFLAGVARIABLE(LuauExpectedTableUnionIndexerType, false)
LUAU_FASTFLAGVARIABLE(LuauInplaceDemoteSkipAllBound, false)
LUAU_FASTFLAGVARIABLE(LuauLowerBoundsCalculation, false)
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false)
@ -45,6 +44,7 @@ LUAU_FASTFLAGVARIABLE(LuauBinaryNeedsExpectedTypesToo, false)
LUAU_FASTFLAGVARIABLE(LuauNeverTypesAndOperatorsInference, false)
LUAU_FASTFLAGVARIABLE(LuauReturnsFromCallsitesAreNotWidened, false)
LUAU_FASTFLAGVARIABLE(LuauCompleteVisitor, false)
LUAU_FASTFLAGVARIABLE(LuauUnionOfTypesFollow, false)
namespace Luau
{
@ -2343,7 +2343,7 @@ WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExp
{
if (auto prop = ttv->props.find(key->value.data); prop != ttv->props.end())
expectedResultTypes.push_back(prop->second.type);
else if (FFlag::LuauExpectedTableUnionIndexerType && ttv->indexer && maybeString(ttv->indexer->indexType))
else if (ttv->indexer && maybeString(ttv->indexer->indexType))
expectedResultTypes.push_back(ttv->indexer->indexResultType);
}
}
@ -2498,6 +2498,12 @@ std::string opToMetaTableEntry(const AstExprBinary::Op& op)
TypeId TypeChecker::unionOfTypes(TypeId a, TypeId b, const ScopePtr& scope, const Location& location, bool unifyFreeTypes)
{
if (FFlag::LuauUnionOfTypesFollow)
{
a = follow(a);
b = follow(b);
}
if (unifyFreeTypes && (get<FreeTypeVar>(a) || get<FreeTypeVar>(b)))
{
if (unify(b, a, scope, location))
@ -3659,33 +3665,6 @@ WithPredicate<TypePackId> TypeChecker::checkExprPackHelper(const ScopePtr& scope
}
}
// Returns the minimum number of arguments the argument list can accept.
static size_t getMinParameterCount(TxnLog* log, TypePackId tp)
{
size_t minCount = 0;
size_t optionalCount = 0;
auto it = begin(tp, log);
auto endIter = end(tp);
while (it != endIter)
{
TypeId ty = *it;
if (isOptional(ty))
++optionalCount;
else
{
minCount += optionalCount;
optionalCount = 0;
minCount++;
}
++it;
}
return minCount;
}
void TypeChecker::checkArgumentList(
const ScopePtr& scope, Unifier& state, TypePackId argPack, TypePackId paramPack, const std::vector<Location>& argLocations)
{
@ -3705,7 +3684,7 @@ void TypeChecker::checkArgumentList(
if (!argLocations.empty())
location = {state.location.begin, argLocations.back().end};
size_t minParams = getMinParameterCount(&state.log, paramPack);
size_t minParams = getParameterExtents(&state.log, paramPack).first;
state.reportError(TypeError{location, CountMismatch{minParams, std::distance(begin(argPack), end(argPack))}});
};
@ -3804,7 +3783,7 @@ void TypeChecker::checkArgumentList(
} // ok
else
{
size_t minParams = getMinParameterCount(&state.log, paramPack);
size_t minParams = getParameterExtents(&state.log, paramPack).first;
std::optional<TypePackId> tail = flatten(paramPack, state.log).second;
bool isVariadic = tail && Luau::isVariadic(*tail);

View file

@ -8,6 +8,13 @@
namespace Luau
{
BlockedTypePack::BlockedTypePack()
: index(++nextIndex)
{
}
size_t BlockedTypePack::nextIndex = 0;
TypePackVar::TypePackVar(const TypePackVariant& tp)
: ty(tp)
{

View file

@ -84,9 +84,8 @@ std::optional<TypeId> findTablePropertyRespectingMeta(ErrorVec& errors, TypeId t
return std::nullopt;
}
std::optional<TypeId> getIndexTypeFromType(
const ScopePtr& scope, ErrorVec& errors, TypeArena* arena, TypeId type, const std::string& prop, const Location& location, bool addErrors,
InternalErrorReporter& handle)
std::optional<TypeId> getIndexTypeFromType(const ScopePtr& scope, ErrorVec& errors, TypeArena* arena, TypeId type, const std::string& prop,
const Location& location, bool addErrors, InternalErrorReporter& handle)
{
type = follow(type);
@ -190,4 +189,33 @@ std::optional<TypeId> getIndexTypeFromType(
return std::nullopt;
}
std::pair<size_t, std::optional<size_t>> getParameterExtents(const TxnLog* log, TypePackId tp)
{
size_t minCount = 0;
size_t optionalCount = 0;
auto it = begin(tp, log);
auto endIter = end(tp);
while (it != endIter)
{
TypeId ty = *it;
if (isOptional(ty))
++optionalCount;
else
{
minCount += optionalCount;
optionalCount = 0;
minCount++;
}
++it;
}
if (it.tail())
return {minCount, std::nullopt};
else
return {minCount, minCount + optionalCount};
}
} // namespace Luau

View file

@ -24,9 +24,7 @@ LUAU_FASTINTVARIABLE(LuauTypeMaximumStringifierLength, 500)
LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0)
LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauUnknownAndNeverType)
LUAU_FASTFLAGVARIABLE(LuauDeduceGmatchReturnTypes, false)
LUAU_FASTFLAGVARIABLE(LuauMaybeGenericIntersectionTypes, false)
LUAU_FASTFLAGVARIABLE(LuauDeduceFindMatchReturnTypes, false)
LUAU_FASTFLAGVARIABLE(LuauStringFormatArgumentErrorFix, false)
namespace Luau
@ -446,8 +444,10 @@ BlockedTypeVar::BlockedTypeVar()
int BlockedTypeVar::nextIndex = 0;
PendingExpansionTypeVar::PendingExpansionTypeVar(TypeFun fn, std::vector<TypeId> typeArguments, std::vector<TypePackId> packArguments)
: fn(fn)
PendingExpansionTypeVar::PendingExpansionTypeVar(
std::optional<AstName> prefix, AstName name, std::vector<TypeId> typeArguments, std::vector<TypePackId> packArguments)
: prefix(prefix)
, name(name)
, typeArguments(typeArguments)
, packArguments(packArguments)
, index(++nextIndex)
@ -787,8 +787,8 @@ TypeId SingletonTypes::makeStringMetatable()
makeFunction(*arena, stringType, {}, {}, {stringType}, {}, {arena->addType(FunctionTypeVar{emptyPack, stringVariadicList})});
attachMagicFunction(gmatchFunc, magicFunctionGmatch);
const TypeId matchFunc = arena->addType(FunctionTypeVar{arena->addTypePack({stringType, stringType, optionalNumber}),
arena->addTypePack(TypePackVar{VariadicTypePack{FFlag::LuauDeduceFindMatchReturnTypes ? stringType : optionalString}})});
const TypeId matchFunc = arena->addType(
FunctionTypeVar{arena->addTypePack({stringType, stringType, optionalNumber}), arena->addTypePack(TypePackVar{VariadicTypePack{stringType}})});
attachMagicFunction(matchFunc, magicFunctionMatch);
const TypeId findFunc = arena->addType(FunctionTypeVar{arena->addTypePack({stringType, stringType, optionalNumber, optionalBoolean}),
@ -1221,9 +1221,6 @@ static std::vector<TypeId> parsePatternString(TypeChecker& typechecker, const ch
static std::optional<WithPredicate<TypePackId>> magicFunctionGmatch(
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate)
{
if (!FFlag::LuauDeduceGmatchReturnTypes)
return std::nullopt;
auto [paramPack, _predicates] = withPredicate;
const auto& [params, tail] = flatten(paramPack);
@ -1256,9 +1253,6 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionGmatch(
static std::optional<WithPredicate<TypePackId>> magicFunctionMatch(
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate)
{
if (!FFlag::LuauDeduceFindMatchReturnTypes)
return std::nullopt;
auto [paramPack, _predicates] = withPredicate;
const auto& [params, tail] = flatten(paramPack);
@ -1295,9 +1289,6 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionMatch(
static std::optional<WithPredicate<TypePackId>> magicFunctionFind(
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate)
{
if (!FFlag::LuauDeduceFindMatchReturnTypes)
return std::nullopt;
auto [paramPack, _predicates] = withPredicate;
const auto& [params, tail] = flatten(paramPack);

View file

@ -36,7 +36,9 @@ void* pagedAllocate(size_t size)
{
// By default we use operator new/delete instead of malloc/free so that they can be overridden externally
if (!FFlag::DebugLuauFreezeArena)
{
return ::operator new(size, std::nothrow);
}
// On Windows, VirtualAlloc results in 64K granularity allocations; we allocate in chunks of ~32K so aligned_malloc is a little more efficient
// On Linux, we must use mmap because using regular heap results in mprotect() fragmenting the page table and us bumping into 64K mmap limit.

View file

@ -13,7 +13,8 @@ Free::Free(TypeLevel level)
}
Free::Free(Scope* scope)
: scope(scope)
: index(++nextIndex)
, scope(scope)
{
}

View file

@ -317,7 +317,8 @@ static std::optional<std::pair<Luau::Name, const SingletonTypeVar*>> getTableMat
return std::nullopt;
}
Unifier::Unifier(TypeArena* types, Mode mode, NotNull<Scope> scope, const Location& location, Variance variance, UnifierSharedState& sharedState, TxnLog* parentLog)
Unifier::Unifier(TypeArena* types, Mode mode, NotNull<Scope> scope, const Location& location, Variance variance, UnifierSharedState& sharedState,
TxnLog* parentLog)
: types(types)
, mode(mode)
, scope(scope)
@ -492,9 +493,9 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
if (log.get<ConstrainedTypeVar>(subTy))
tryUnifyWithConstrainedSubTypeVar(subTy, superTy);
else if (const UnionTypeVar* uv = log.getMutable<UnionTypeVar>(subTy))
else if (const UnionTypeVar* subUnion = log.getMutable<UnionTypeVar>(subTy))
{
tryUnifyUnionWithType(subTy, uv, superTy);
tryUnifyUnionWithType(subTy, subUnion, superTy);
}
else if (const UnionTypeVar* uv = log.getMutable<UnionTypeVar>(superTy))
{
@ -555,14 +556,14 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
log.popSeen(superTy, subTy);
}
void Unifier::tryUnifyUnionWithType(TypeId subTy, const UnionTypeVar* uv, TypeId superTy)
void Unifier::tryUnifyUnionWithType(TypeId subTy, const UnionTypeVar* subUnion, TypeId superTy)
{
// A | B <: T if A <: T and B <: T
bool failed = false;
std::optional<TypeError> unificationTooComplex;
std::optional<TypeError> firstFailedOption;
for (TypeId type : uv->options)
for (TypeId type : subUnion->options)
{
Unifier innerState = makeChildUnifier();
innerState.tryUnify_(type, superTy);
@ -608,9 +609,9 @@ void Unifier::tryUnifyUnionWithType(TypeId subTy, const UnionTypeVar* uv, TypeId
}
};
if (auto utv = log.getMutable<UnionTypeVar>(superTy))
if (auto superUnion = log.getMutable<UnionTypeVar>(superTy))
{
for (TypeId ty : utv)
for (TypeId ty : superUnion)
tryBind(ty);
}
else

View file

@ -1276,6 +1276,16 @@ public:
};
AstName getIdentifier(AstExpr*);
Location getLocation(const AstTypeList& typeList);
template<typename T> // AstNode, AstExpr, AstLocal, etc
Location getLocation(AstArray<T*> array)
{
if (0 == array.size)
return {};
return Location{array.data[0]->location.begin, array.data[array.size - 1]->location.end};
}
#undef LUAU_RTTI

View file

@ -304,6 +304,12 @@ private:
AstExprError* reportExprError(const Location& location, const AstArray<AstExpr*>& expressions, const char* format, ...) LUAU_PRINTF_ATTR(4, 5);
AstTypeError* reportTypeAnnotationError(const Location& location, const AstArray<AstType*>& types, bool isMissing, const char* format, ...)
LUAU_PRINTF_ATTR(5, 6);
// `parseErrorLocation` is associated with the parser error
// `astErrorLocation` is associated with the AstTypeError created
// It can be useful to have different error locations so that the parse error can include the next lexeme, while the AstTypeError can precisely
// define the location (possibly of zero size) where a type annotation is expected.
AstTypeError* reportMissingTypeAnnotationError(const Location& parseErrorLocation, const Location& astErrorLocation, const char* format, ...)
LUAU_PRINTF_ATTR(4, 5);
AstExpr* reportFunctionArgsError(AstExpr* func, bool self);
void reportAmbiguousCallError();

View file

@ -952,4 +952,17 @@ AstName getIdentifier(AstExpr* node)
return AstName();
}
Location getLocation(const AstTypeList& typeList)
{
Location result;
if (typeList.types.size)
{
result = Location{typeList.types.data[0]->location, typeList.types.data[typeList.types.size - 1]->location};
}
if (typeList.tailType)
result.end = typeList.tailType->location.end;
return result;
}
} // namespace Luau

View file

@ -91,18 +91,8 @@ Lexeme::Lexeme(const Location& location, Type type, const char* data, size_t siz
, length(unsigned(size))
, data(data)
{
LUAU_ASSERT(
type == RawString
|| type == QuotedString
|| type == InterpStringBegin
|| type == InterpStringMid
|| type == InterpStringEnd
|| type == InterpStringSimple
|| type == BrokenInterpDoubleBrace
|| type == Number
|| type == Comment
|| type == BlockComment
);
LUAU_ASSERT(type == RawString || type == QuotedString || type == InterpStringBegin || type == InterpStringMid || type == InterpStringEnd ||
type == InterpStringSimple || type == BrokenInterpDoubleBrace || type == Number || type == Comment || type == BlockComment);
}
Lexeme::Lexeme(const Location& location, Type type, const char* name)
@ -644,7 +634,8 @@ Lexeme Lexer::readInterpolatedStringSection(Position start, Lexeme::Type formatT
if (peekch(1) == '{')
{
Lexeme brokenDoubleBrace = Lexeme(Location(start, position()), Lexeme::BrokenInterpDoubleBrace, &buffer[startOffset], offset - startOffset);
Lexeme brokenDoubleBrace =
Lexeme(Location(start, position()), Lexeme::BrokenInterpDoubleBrace, &buffer[startOffset], offset - startOffset);
consume();
consume();
return brokenDoubleBrace;

View file

@ -24,6 +24,7 @@ LUAU_FASTFLAGVARIABLE(LuauLintParseIntegerIssues, false)
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuaReportParseIntegerIssues, false)
LUAU_FASTFLAGVARIABLE(LuauInterpolatedStringBaseSupport, false)
LUAU_FASTFLAGVARIABLE(LuauTypeAnnotationLocationChange, false)
bool lua_telemetry_parsed_out_of_range_bin_integer = false;
bool lua_telemetry_parsed_out_of_range_hex_integer = false;
@ -1564,44 +1565,43 @@ AstTypeOrPack Parser::parseSimpleTypeAnnotation(bool allowPack)
{
incrementRecursionCounter("type annotation");
Location begin = lexer.current().location;
Location start = lexer.current().location;
if (lexer.current().type == Lexeme::ReservedNil)
{
nextLexeme();
return {allocator.alloc<AstTypeReference>(begin, std::nullopt, nameNil), {}};
return {allocator.alloc<AstTypeReference>(start, std::nullopt, nameNil), {}};
}
else if (lexer.current().type == Lexeme::ReservedTrue)
{
nextLexeme();
return {allocator.alloc<AstTypeSingletonBool>(begin, true)};
return {allocator.alloc<AstTypeSingletonBool>(start, true)};
}
else if (lexer.current().type == Lexeme::ReservedFalse)
{
nextLexeme();
return {allocator.alloc<AstTypeSingletonBool>(begin, false)};
return {allocator.alloc<AstTypeSingletonBool>(start, false)};
}
else if (lexer.current().type == Lexeme::RawString || lexer.current().type == Lexeme::QuotedString)
{
if (std::optional<AstArray<char>> value = parseCharArray())
{
AstArray<char> svalue = *value;
return {allocator.alloc<AstTypeSingletonString>(begin, svalue)};
return {allocator.alloc<AstTypeSingletonString>(start, svalue)};
}
else
return {reportTypeAnnotationError(begin, {}, /*isMissing*/ false, "String literal contains malformed escape sequence")};
return {reportTypeAnnotationError(start, {}, /*isMissing*/ false, "String literal contains malformed escape sequence")};
}
else if (lexer.current().type == Lexeme::InterpStringBegin || lexer.current().type == Lexeme::InterpStringSimple)
{
parseInterpString();
return {reportTypeAnnotationError(begin, {}, /*isMissing*/ false, "Interpolated string literals cannot be used as types")};
return {reportTypeAnnotationError(start, {}, /*isMissing*/ false, "Interpolated string literals cannot be used as types")};
}
else if (lexer.current().type == Lexeme::BrokenString)
{
Location location = lexer.current().location;
nextLexeme();
return {reportTypeAnnotationError(location, {}, /*isMissing*/ false, "Malformed string")};
return {reportTypeAnnotationError(start, {}, /*isMissing*/ false, "Malformed string")};
}
else if (lexer.current().type == Lexeme::Name)
{
@ -1632,7 +1632,7 @@ AstTypeOrPack Parser::parseSimpleTypeAnnotation(bool allowPack)
expectMatchAndConsume(')', typeofBegin);
return {allocator.alloc<AstTypeTypeof>(Location(begin, end), expr), {}};
return {allocator.alloc<AstTypeTypeof>(Location(start, end), expr), {}};
}
bool hasParameters = false;
@ -1646,7 +1646,7 @@ AstTypeOrPack Parser::parseSimpleTypeAnnotation(bool allowPack)
Location end = lexer.previousLocation();
return {allocator.alloc<AstTypeReference>(Location(begin, end), prefix, name.name, hasParameters, parameters), {}};
return {allocator.alloc<AstTypeReference>(Location(start, end), prefix, name.name, hasParameters, parameters), {}};
}
else if (lexer.current().type == '{')
{
@ -1658,16 +1658,27 @@ AstTypeOrPack Parser::parseSimpleTypeAnnotation(bool allowPack)
}
else if (lexer.current().type == Lexeme::ReservedFunction)
{
Location location = lexer.current().location;
nextLexeme();
return {reportTypeAnnotationError(location, {}, /*isMissing*/ false,
return {reportTypeAnnotationError(start, {}, /*isMissing*/ false,
"Using 'function' as a type annotation is not supported, consider replacing with a function type annotation e.g. '(...any) -> "
"...any'"),
{}};
}
else
{
if (FFlag::LuauTypeAnnotationLocationChange)
{
// For a missing type annotation, capture 'space' between last token and the next one
Location astErrorlocation(lexer.previousLocation().end, start.begin);
// The parse error includes the next lexeme to make it easier to display where the error is (e.g. in an IDE or a CLI error message).
// Including the current lexeme also makes the parse error consistent with other parse errors returned by Luau.
Location parseErrorLocation(lexer.previousLocation().end, start.end);
return {
reportMissingTypeAnnotationError(parseErrorLocation, astErrorlocation, "Expected type, got %s", lexer.current().toString().c_str()),
{}};
}
else
{
Location location = lexer.current().location;
@ -1676,6 +1687,7 @@ AstTypeOrPack Parser::parseSimpleTypeAnnotation(bool allowPack)
return {reportTypeAnnotationError(location, {}, /*isMissing*/ true, "Expected type, got %s", lexer.current().toString().c_str()), {}};
}
}
}
AstTypePack* Parser::parseVariadicArgumentAnnotation()
@ -2245,7 +2257,8 @@ AstExpr* Parser::parseSimpleExpr()
{
return parseNumber();
}
else if (lexer.current().type == Lexeme::RawString || lexer.current().type == Lexeme::QuotedString || (FFlag::LuauInterpolatedStringBaseSupport && lexer.current().type == Lexeme::InterpStringSimple))
else if (lexer.current().type == Lexeme::RawString || lexer.current().type == Lexeme::QuotedString ||
(FFlag::LuauInterpolatedStringBaseSupport && lexer.current().type == Lexeme::InterpStringSimple))
{
return parseString();
}
@ -2653,7 +2666,8 @@ AstArray<AstTypeOrPack> Parser::parseTypeParams()
std::optional<AstArray<char>> Parser::parseCharArray()
{
LUAU_ASSERT(lexer.current().type == Lexeme::QuotedString || lexer.current().type == Lexeme::RawString || lexer.current().type == Lexeme::InterpStringSimple);
LUAU_ASSERT(lexer.current().type == Lexeme::QuotedString || lexer.current().type == Lexeme::RawString ||
lexer.current().type == Lexeme::InterpStringSimple);
scratchData.assign(lexer.current().data, lexer.current().length);
@ -2691,14 +2705,11 @@ AstExpr* Parser::parseInterpString()
Location startLocation = lexer.current().location;
do {
do
{
Lexeme currentLexeme = lexer.current();
LUAU_ASSERT(
currentLexeme.type == Lexeme::InterpStringBegin
|| currentLexeme.type == Lexeme::InterpStringMid
|| currentLexeme.type == Lexeme::InterpStringEnd
|| currentLexeme.type == Lexeme::InterpStringSimple
);
LUAU_ASSERT(currentLexeme.type == Lexeme::InterpStringBegin || currentLexeme.type == Lexeme::InterpStringMid ||
currentLexeme.type == Lexeme::InterpStringEnd || currentLexeme.type == Lexeme::InterpStringSimple);
Location location = currentLexeme.location;
@ -2973,8 +2984,7 @@ bool Parser::expectMatchEndAndConsume(Lexeme::Type type, const MatchLexeme& begi
{
// If the token matches on a different line and a different column, it suggests misleading indentation
// This can be used to pinpoint the problem location for a possible future *actual* mismatch
if (lexer.current().location.begin.line != begin.position.line &&
lexer.current().location.begin.column != begin.position.column &&
if (lexer.current().location.begin.line != begin.position.line && lexer.current().location.begin.column != begin.position.column &&
endMismatchSuspect.position.line < begin.position.line) // Only replace the previous suspect with more recent suspects
{
endMismatchSuspect = begin;
@ -3108,6 +3118,13 @@ AstExprError* Parser::reportExprError(const Location& location, const AstArray<A
AstTypeError* Parser::reportTypeAnnotationError(const Location& location, const AstArray<AstType*>& types, bool isMissing, const char* format, ...)
{
if (FFlag::LuauTypeAnnotationLocationChange)
{
// Missing type annotations should be using `reportMissingTypeAnnotationError` when LuauTypeAnnotationLocationChange is enabled
// Note: `isMissing` can be removed once FFlag::LuauTypeAnnotationLocationChange is removed since it will always be true.
LUAU_ASSERT(!isMissing);
}
va_list args;
va_start(args, format);
report(location, format, args);
@ -3116,6 +3133,18 @@ AstTypeError* Parser::reportTypeAnnotationError(const Location& location, const
return allocator.alloc<AstTypeError>(location, types, isMissing, unsigned(parseErrors.size() - 1));
}
AstTypeError* Parser::reportMissingTypeAnnotationError(const Location& parseErrorLocation, const Location& astErrorLocation, const char* format, ...)
{
LUAU_ASSERT(FFlag::LuauTypeAnnotationLocationChange);
va_list args;
va_start(args, format);
report(parseErrorLocation, format, args);
va_end(args);
return allocator.alloc<AstTypeError>(astErrorLocation, AstArray<AstType*>{}, true, unsigned(parseErrors.size() - 1));
}
void Parser::nextLexeme()
{
Lexeme::Type type = lexer.next(/* skipComments= */ false, true).type;

View file

@ -414,7 +414,7 @@ enum LuauBytecodeTag
// Bytecode version; runtime supports [MIN, MAX], compiler emits TARGET by default but may emit a higher version when flags are enabled
LBC_VERSION_MIN = 2,
LBC_VERSION_MAX = 3,
LBC_VERSION_TARGET = 2,
LBC_VERSION_TARGET = 3,
// Types of constant table entries
LBC_CONSTANT_NIL = 0,
LBC_CONSTANT_BOOLEAN,

View file

@ -13,7 +13,8 @@ inline bool isFlagExperimental(const char* flag)
static const char* kList[] = {
"LuauLowerBoundsCalculation",
"LuauInterpolatedStringBaseSupport",
nullptr, // makes sure we always have at least one entry
// makes sure we always have at least one entry
nullptr,
};
for (const char* item : kList)

View file

@ -6,8 +6,6 @@
#include <algorithm>
#include <string.h>
LUAU_FASTFLAGVARIABLE(LuauCompileBytecodeV3, false)
namespace Luau
{
@ -1079,9 +1077,6 @@ std::string BytecodeBuilder::getError(const std::string& message)
uint8_t BytecodeBuilder::getVersion()
{
if (FFlag::LuauCompileBytecodeV3)
return 3;
// This function usually returns LBC_VERSION_TARGET but may sometimes return a higher number (within LBC_VERSION_MIN/MAX) under fast flags
return LBC_VERSION_TARGET;
}

View file

@ -25,14 +25,8 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineThreshold, 25)
LUAU_FASTINTVARIABLE(LuauCompileInlineThresholdMaxBoost, 300)
LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5)
LUAU_FASTFLAGVARIABLE(LuauCompileXEQ, false)
LUAU_FASTFLAG(LuauInterpolatedStringBaseSupport)
LUAU_FASTFLAGVARIABLE(LuauCompileOptimalAssignment, false)
LUAU_FASTFLAGVARIABLE(LuauCompileExtractK, false)
namespace Luau
{
@ -406,25 +400,19 @@ struct Compiler
}
}
void compileExprFastcallN(AstExprCall* expr, uint8_t target, uint8_t targetCount, bool targetTop, bool multRet, uint8_t regs, int bfid, int bfK = -1)
void compileExprFastcallN(
AstExprCall* expr, uint8_t target, uint8_t targetCount, bool targetTop, bool multRet, uint8_t regs, int bfid, int bfK = -1)
{
LUAU_ASSERT(!expr->self);
LUAU_ASSERT(expr->args.size >= 1);
LUAU_ASSERT(expr->args.size <= 2 || (bfid == LBF_BIT32_EXTRACTK && expr->args.size == 3));
LUAU_ASSERT(bfid == LBF_BIT32_EXTRACTK ? bfK >= 0 : bfK < 0);
LuauOpcode opc = expr->args.size == 1 ? LOP_FASTCALL1 : LOP_FASTCALL2;
if (FFlag::LuauCompileExtractK)
{
opc = expr->args.size == 1 ? LOP_FASTCALL1 : (bfK >= 0 || isConstant(expr->args.data[1])) ? LOP_FASTCALL2K : LOP_FASTCALL2;
}
LuauOpcode opc = expr->args.size == 1 ? LOP_FASTCALL1 : (bfK >= 0 || isConstant(expr->args.data[1])) ? LOP_FASTCALL2K : LOP_FASTCALL2;
uint32_t args[3] = {};
for (size_t i = 0; i < expr->args.size; ++i)
{
if (FFlag::LuauCompileExtractK)
{
if (i > 0 && opc == LOP_FASTCALL2K)
{
@ -433,20 +421,8 @@ struct Compiler
CompileError::raise(expr->location, "Exceeded constant limit; simplify the code to compile");
args[i] = cid;
continue; // TODO: remove this and change if below to else if
}
}
else if (i > 0)
{
if (int32_t cid = getConstantIndex(expr->args.data[i]); cid >= 0)
{
opc = LOP_FASTCALL2K;
args[i] = cid;
break;
}
}
if (int reg = getExprLocalReg(expr->args.data[i]); reg >= 0)
else if (int reg = getExprLocalReg(expr->args.data[i]); reg >= 0)
{
args[i] = uint8_t(reg);
}
@ -467,26 +443,12 @@ struct Compiler
// Note, as with other instructions that immediately follow FASTCALL, these are normally not executed and are used as a fallback for
// these FASTCALL variants.
for (size_t i = 0; i < expr->args.size; ++i)
{
if (FFlag::LuauCompileExtractK)
{
if (i > 0 && opc == LOP_FASTCALL2K)
emitLoadK(uint8_t(regs + 1 + i), args[i]);
else if (args[i] != regs + 1 + i)
bytecode.emitABC(LOP_MOVE, uint8_t(regs + 1 + i), uint8_t(args[i]), 0);
}
else
{
if (i > 0 && opc == LOP_FASTCALL2K)
{
emitLoadK(uint8_t(regs + 1 + i), args[i]);
break;
}
if (args[i] != regs + 1 + i)
bytecode.emitABC(LOP_MOVE, uint8_t(regs + 1 + i), uint8_t(args[i]), 0);
}
}
// note, these instructions are normally not executed and are used as a fallback for FASTCALL
// we can't use TempTop variant here because we need to make sure the arguments we already computed aren't overwritten
@ -758,7 +720,7 @@ struct Compiler
}
// Optimization: for bit32.extract with constant in-range f/w we compile using FASTCALL2K and a special builtin
if (FFlag::LuauCompileExtractK && bfid == LBF_BIT32_EXTRACT && expr->args.size == 3 && isConstant(expr->args.data[1]) && isConstant(expr->args.data[2]))
if (bfid == LBF_BIT32_EXTRACT && expr->args.size == 3 && isConstant(expr->args.data[1]) && isConstant(expr->args.data[2]))
{
Constant fc = getConstant(expr->args.data[1]);
Constant wc = getConstant(expr->args.data[2]);
@ -1080,8 +1042,6 @@ struct Compiler
std::swap(left, right);
}
if (FFlag::LuauCompileXEQ)
{
uint8_t rl = compileExprAuto(left, rs);
if (isEq && operandIsConstant)
@ -1151,42 +1111,6 @@ struct Compiler
return jumpLabel;
}
}
else
{
LuauOpcode opc = getJumpOpCompare(expr->op, not_);
uint8_t rl = compileExprAuto(left, rs);
int32_t rr = -1;
if (isEq && operandIsConstant)
{
if (opc == LOP_JUMPIFEQ)
opc = LOP_JUMPIFEQK;
else if (opc == LOP_JUMPIFNOTEQ)
opc = LOP_JUMPIFNOTEQK;
rr = getConstantIndex(right);
LUAU_ASSERT(rr >= 0);
}
else
rr = compileExprAuto(right, rs);
size_t jumpLabel = bytecode.emitLabel();
if (expr->op == AstExprBinary::CompareGt || expr->op == AstExprBinary::CompareGe)
{
bytecode.emitAD(opc, uint8_t(rr), 0);
bytecode.emitAux(rl);
}
else
{
bytecode.emitAD(opc, rl, 0);
bytecode.emitAux(rr);
}
return jumpLabel;
}
}
int32_t getConstantNumber(AstExpr* node)
{
@ -2979,62 +2903,6 @@ struct Compiler
loops.pop_back();
}
void resolveAssignConflicts(AstStat* stat, std::vector<LValue>& vars)
{
LUAU_ASSERT(!FFlag::LuauCompileOptimalAssignment);
// regsUsed[i] is true if we have assigned the register during earlier assignments
// regsRemap[i] is set to the register where the original (pre-assignment) copy was made
// note: regsRemap is uninitialized intentionally to speed small assignments up; regsRemap[i] is valid iff regsUsed[i]
std::bitset<256> regsUsed;
uint8_t regsRemap[256];
for (size_t i = 0; i < vars.size(); ++i)
{
LValue& li = vars[i];
if (li.kind == LValue::Kind_Local)
{
if (!regsUsed[li.reg])
{
regsUsed[li.reg] = true;
regsRemap[li.reg] = li.reg;
}
}
else if (li.kind == LValue::Kind_IndexName || li.kind == LValue::Kind_IndexNumber || li.kind == LValue::Kind_IndexExpr)
{
// we're looking for assignments before this one that invalidate any of the registers involved
if (regsUsed[li.reg])
{
// the register may have been evacuated previously, but if it wasn't - move it now
if (regsRemap[li.reg] == li.reg)
{
uint8_t reg = allocReg(stat, 1);
bytecode.emitABC(LOP_MOVE, reg, li.reg, 0);
regsRemap[li.reg] = reg;
}
li.reg = regsRemap[li.reg];
}
if (li.kind == LValue::Kind_IndexExpr && regsUsed[li.index])
{
// the register may have been evacuated previously, but if it wasn't - move it now
if (regsRemap[li.index] == li.index)
{
uint8_t reg = allocReg(stat, 1);
bytecode.emitABC(LOP_MOVE, reg, li.index, 0);
regsRemap[li.index] = reg;
}
li.index = regsRemap[li.index];
}
}
}
}
struct Assignment
{
LValue lvalue;
@ -3146,8 +3014,6 @@ struct Compiler
return;
}
if (FFlag::LuauCompileOptimalAssignment)
{
// compute all l-values: note that this doesn't assign anything yet but it allocates registers and computes complex expressions on the
// left hand side - for example, in "a[expr] = foo" expr will get evaluated here
std::vector<Assignment> vars(stat->vars.size);
@ -3225,33 +3091,6 @@ struct Compiler
bytecode.emitABC(LOP_MOVE, var.lvalue.reg, var.valueReg, 0);
}
}
else
{
// compute all l-values: note that this doesn't assign anything yet but it allocates registers and computes complex expressions on the
// left hand side for example, in "a[expr] = foo" expr will get evaluated here
std::vector<LValue> vars(stat->vars.size);
for (size_t i = 0; i < stat->vars.size; ++i)
vars[i] = compileLValue(stat->vars.data[i], rs);
// perform conflict resolution: if any lvalue refers to a local reg that will be reassigned before that, we save the local variable in a
// temporary reg
resolveAssignConflicts(stat, vars);
// compute values into temporaries
uint8_t regs = allocReg(stat, unsigned(stat->vars.size));
compileExprListTemp(stat->values, regs, uint8_t(stat->vars.size), /* targetTop= */ true);
// assign variables that have associated values; note that if we have fewer values than variables, we'll assign nil because
// compileExprListTemp will generate nils
for (size_t i = 0; i < stat->vars.size; ++i)
{
setDebugLine(stat->vars.data[i]);
compileAssign(vars[i], uint8_t(regs + i));
}
}
}
void compileStatCompoundAssign(AstStatCompoundAssign* stat)
{

View file

@ -15,8 +15,6 @@
#include <intrin.h>
#endif
LUAU_FASTFLAGVARIABLE(LuauFasterBit32NoWidth, false)
// luauF functions implement FASTCALL instruction that performs a direct execution of some builtin functions from the VM
// The rule of thumb is that FASTCALL functions can not call user code, yield, fail, or reallocate stack.
// If types of the arguments mismatch, luauF_* needs to return -1 and the execution will fall back to the usual call path
@ -602,7 +600,7 @@ static int luauF_btest(lua_State* L, StkId res, TValue* arg0, int nresults, StkI
static int luauF_extract(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams)
{
if (nparams >= (3 - FFlag::LuauFasterBit32NoWidth) && nresults <= 1 && ttisnumber(arg0) && ttisnumber(args))
if (nparams >= 2 && nresults <= 1 && ttisnumber(arg0) && ttisnumber(args))
{
double a1 = nvalue(arg0);
double a2 = nvalue(args);
@ -693,7 +691,7 @@ static int luauF_lshift(lua_State* L, StkId res, TValue* arg0, int nresults, Stk
static int luauF_replace(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams)
{
if (nparams >= (4 - FFlag::LuauFasterBit32NoWidth) && nresults <= 1 && ttisnumber(arg0) && ttisnumber(args) && ttisnumber(args + 1))
if (nparams >= 3 && nresults <= 1 && ttisnumber(arg0) && ttisnumber(args) && ttisnumber(args + 1))
{
double a1 = nvalue(arg0);
double a2 = nvalue(args);

View file

@ -8,8 +8,6 @@
#include <string.h>
#include <stdio.h>
LUAU_FASTFLAGVARIABLE(LuauTostringFormatSpecifier, false);
// macro to `unsign' a character
#define uchar(c) ((unsigned char)(c))
@ -1036,9 +1034,6 @@ static int str_format(lua_State* L)
}
case '*':
{
if (!FFlag::LuauTostringFormatSpecifier)
luaL_error(L, "invalid option '%%*' to 'format'");
if (formatItemSize != 1)
luaL_error(L, "'%%*' does not take a form");

View file

@ -3031,7 +3031,16 @@ static void luau_execute(lua_State* L)
TValue* kv = VM_KV(aux & 0xffffff);
LUAU_ASSERT(ttisnumber(kv));
#if defined(__aarch64__)
// On several ARM chips (Apple M1/M2, Neoverse N1), comparing the result of a floating-point comparison is expensive, and a branch
// is much cheaper; on some 32-bit ARM chips (Cortex A53) the performance is about the same so we prefer less branchy variant there
if (aux >> 31)
pc += !(ttisnumber(ra) && nvalue(ra) == nvalue(kv)) ? LUAU_INSN_D(insn) : 1;
else
pc += (ttisnumber(ra) && nvalue(ra) == nvalue(kv)) ? LUAU_INSN_D(insn) : 1;
#else
pc += int(ttisnumber(ra) && nvalue(ra) == nvalue(kv)) != (aux >> 31) ? LUAU_INSN_D(insn) : 1;
#endif
LUAU_ASSERT(unsigned(pc - cl->l.p->code) < unsigned(cl->l.p->sizecode));
VM_NEXT();
}

View file

@ -4,7 +4,6 @@
#include "doctest.h"
#include <functional>
#include <string.h>
using namespace Luau::CodeGen;
@ -22,7 +21,7 @@ std::string bytecodeAsArray(const std::vector<uint8_t>& bytecode)
class AssemblyBuilderX64Fixture
{
public:
void check(std::function<void(AssemblyBuilderX64& build)> f, std::vector<uint8_t> code, std::vector<uint8_t> data = {})
void check(void (*f)(AssemblyBuilderX64& build), std::vector<uint8_t> code, std::vector<uint8_t> data = {})
{
AssemblyBuilderX64 build(/* logText= */ false);

View file

@ -1241,8 +1241,7 @@ TEST_CASE("InterpStringZeroCost")
{
ScopedFastFlag sff{"LuauInterpolatedStringBaseSupport", true};
CHECK_EQ(
"\n" + compileFunction0(R"(local _ = `hello, {"world"}!`)"),
CHECK_EQ("\n" + compileFunction0(R"(local _ = `hello, {"world"}!`)"),
R"(
LOADK R1 K0
LOADK R3 K1
@ -1250,16 +1249,14 @@ NAMECALL R1 R1 K2
CALL R1 2 1
MOVE R0 R1
RETURN R0 0
)"
);
)");
}
TEST_CASE("InterpStringRegisterCleanup")
{
ScopedFastFlag sff{"LuauInterpolatedStringBaseSupport", true};
CHECK_EQ(
"\n" + compileFunction0(R"(
CHECK_EQ("\n" + compileFunction0(R"(
local a, b, c = nil, "um", "uh oh"
a = `foo{"bar"}`
print(a)
@ -1278,8 +1275,7 @@ GETIMPORT R3 6
MOVE R4 R0
CALL R3 1 0
RETURN R0 0
)"
);
)");
}
TEST_CASE("ConstantFoldArith")
@ -2488,8 +2484,6 @@ end
TEST_CASE("DebugLineInfoRepeatUntil")
{
ScopedFastFlag sff("LuauCompileXEQ", true);
CHECK_EQ("\n" + compileFunction0Coverage(R"(
local f = 0
repeat
@ -2834,8 +2828,6 @@ RETURN R0 0
TEST_CASE("AssignmentConflict")
{
ScopedFastFlag sff("LuauCompileOptimalAssignment", true);
// assignments are left to right
CHECK_EQ("\n" + compileFunction0("local a, b a, b = 1, 2"), R"(
LOADNIL R0
@ -3610,8 +3602,6 @@ RETURN R0 1
TEST_CASE("ConstantJumpCompare")
{
ScopedFastFlag sff("LuauCompileXEQ", true);
CHECK_EQ("\n" + compileFunction0(R"(
local obj = ...
local b = obj == 1
@ -6210,8 +6200,6 @@ L4: RETURN R0 -1
TEST_CASE("BuiltinFoldingMultret")
{
ScopedFastFlag sff("LuauCompileXEQ", true);
CHECK_EQ("\n" + compileFunction(R"(
local NoLanes: Lanes = --[[ ]] 0b0000000000000000000000000000000
local OffscreenLane: Lane = --[[ ]] 0b1000000000000000000000000000000
@ -6350,8 +6338,6 @@ RETURN R2 1
TEST_CASE("MultipleAssignments")
{
ScopedFastFlag sff("LuauCompileOptimalAssignment", true);
// order of assignments is left to right
CHECK_EQ("\n" + compileFunction0(R"(
local a, b
@ -6574,15 +6560,14 @@ RETURN R0 0
TEST_CASE("BuiltinExtractK")
{
ScopedFastFlag sff("LuauCompileExtractK", true);
// below, K0 refers to a packed f+w constant for bit32.extractk builtin
// K1 and K2 refer to 1 and 3 and are only used during fallback path
CHECK_EQ("\n" + compileFunction0(R"(
local v = ...
return bit32.extract(v, 1, 3)
)"), R"(
)"),
R"(
GETVARARGS R0 1
FASTCALL2K 59 R0 K0 L0
MOVE R2 R0

View file

@ -289,15 +289,12 @@ TEST_CASE("Clear")
TEST_CASE("Strings")
{
ScopedFastFlag sff{"LuauTostringFormatSpecifier", true};
runConformance("strings.lua");
}
TEST_CASE("StringInterp")
{
ScopedFastFlag sffInterpStrings{"LuauInterpolatedStringBaseSupport", true};
ScopedFastFlag sffTostringFormat{"LuauTostringFormatSpecifier", true};
runConformance("stringinterp.lua");
}
@ -725,13 +722,16 @@ TEST_CASE("NewUserdataOverflow")
StateRef globalState(luaL_newstate(), lua_close);
lua_State* L = globalState.get();
lua_pushcfunction(L, [](lua_State* L1) {
lua_pushcfunction(
L,
[](lua_State* L1) {
// The following userdata request might cause an overflow.
lua_newuserdatadtor(L1, SIZE_MAX, [](void* d){});
lua_newuserdatadtor(L1, SIZE_MAX, [](void* d) {});
// The overflow might segfault in the following call.
lua_getmetatable(L1, -1);
return 0;
}, nullptr);
},
nullptr);
CHECK(lua_pcall(L, 0, 0, 0) == LUA_ERRRUN);
CHECK(strcmp(lua_tostring(L, -1), "memory allocation error: block too big") == 0);

View file

@ -57,13 +57,12 @@ TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "nil_primitive")
auto constraints = collectConstraints(NotNull(cgb.rootScope));
ToStringOptions opts;
REQUIRE(5 <= constraints.size());
REQUIRE(4 <= constraints.size());
CHECK("*blocked-1* ~ gen () -> (a...)" == toString(*constraints[0], opts));
CHECK("*blocked-2* ~ inst *blocked-1*" == toString(*constraints[1], opts));
CHECK("() -> (b...) <: *blocked-2*" == toString(*constraints[2], opts));
CHECK("b... <: c" == toString(*constraints[3], opts));
CHECK("nil <: a..." == toString(*constraints[4], opts));
CHECK("call *blocked-1* with { result = *blocked-tp-1* }" == toString(*constraints[1], opts));
CHECK("*blocked-tp-1* <: b" == toString(*constraints[2], opts));
CHECK("nil <: a..." == toString(*constraints[3], opts));
}
TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "function_application")
@ -76,13 +75,12 @@ TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "function_application")
cgb.visit(block);
auto constraints = collectConstraints(NotNull(cgb.rootScope));
REQUIRE(4 == constraints.size());
REQUIRE(3 == constraints.size());
ToStringOptions opts;
CHECK("string <: a" == toString(*constraints[0], opts));
CHECK("*blocked-1* ~ inst a" == toString(*constraints[1], opts));
CHECK("(string) -> (b...) <: *blocked-1*" == toString(*constraints[2], opts));
CHECK("b... <: c" == toString(*constraints[3], opts));
CHECK("call a with { result = *blocked-tp-1* }" == toString(*constraints[1], opts));
CHECK("*blocked-tp-1* <: b" == toString(*constraints[2], opts));
}
TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "local_function_definition")
@ -114,13 +112,12 @@ TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "recursive_function")
cgb.visit(block);
auto constraints = collectConstraints(NotNull(cgb.rootScope));
REQUIRE(4 == constraints.size());
REQUIRE(3 == constraints.size());
ToStringOptions opts;
CHECK("*blocked-1* ~ gen (a) -> (b...)" == toString(*constraints[0], opts));
CHECK("*blocked-2* ~ inst (a) -> (b...)" == toString(*constraints[1], opts));
CHECK("(a) -> (c...) <: *blocked-2*" == toString(*constraints[2], opts));
CHECK("c... <: b..." == toString(*constraints[3], opts));
CHECK("call (a) -> (b...) with { result = *blocked-tp-1* }" == toString(*constraints[1], opts));
CHECK("*blocked-tp-1* <: b..." == toString(*constraints[2], opts));
}
TEST_SUITE_END();

View file

@ -28,7 +28,8 @@ TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "hello")
cgb.visit(block);
NotNull<Scope> rootScope = NotNull(cgb.rootScope);
ConstraintSolver cs{&arena, rootScope};
NullModuleResolver resolver;
ConstraintSolver cs{&arena, rootScope, "MainModule", NotNull(&resolver), {}};
cs.run();
@ -48,7 +49,8 @@ TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "generic_function")
cgb.visit(block);
NotNull<Scope> rootScope = NotNull(cgb.rootScope);
ConstraintSolver cs{&arena, rootScope};
NullModuleResolver resolver;
ConstraintSolver cs{&arena, rootScope, "MainModule", NotNull(&resolver), {}};
cs.run();
@ -57,7 +59,6 @@ TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "generic_function")
CHECK("<a>(a) -> a" == toString(idType));
}
#if 1
TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "proper_let_generalization")
{
AstStatBlock* block = parse(R"(
@ -77,7 +78,8 @@ TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "proper_let_generalization")
ToStringOptions opts;
ConstraintSolver cs{&arena, rootScope};
NullModuleResolver resolver;
ConstraintSolver cs{&arena, rootScope, "MainModule", NotNull(&resolver), {}};
cs.run();
@ -85,6 +87,5 @@ TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "proper_let_generalization")
CHECK("<a>(a) -> number" == toString(idType, opts));
}
#endif
TEST_SUITE_END();

View file

@ -2,6 +2,8 @@
#include "Fixture.h"
#include "Luau/AstQuery.h"
#include "Luau/ModuleResolver.h"
#include "Luau/NotNull.h"
#include "Luau/Parser.h"
#include "Luau/TypeVar.h"
#include "Luau/TypeAttach.h"
@ -444,10 +446,11 @@ BuiltinsFixture::BuiltinsFixture(bool freeze, bool prepareAutocomplete)
ConstraintGraphBuilderFixture::ConstraintGraphBuilderFixture()
: Fixture()
, mainModule(new Module)
, cgb(mainModuleName, mainModule, &arena, NotNull(&ice), frontend.getGlobalScope())
, cgb(mainModuleName, mainModule, &arena, NotNull(&moduleResolver), NotNull(&ice), frontend.getGlobalScope())
, forceTheFlag{"DebugLuauDeferredConstraintResolution", true}
{
BlockedTypeVar::nextIndex = 0;
BlockedTypePack::nextIndex = 0;
}
ModuleName fromString(std::string_view name)

View file

@ -132,6 +132,7 @@ struct Fixture
TestFileResolver fileResolver;
TestConfigResolver configResolver;
NullModuleResolver moduleResolver;
std::unique_ptr<SourceModule> sourceModule;
Frontend frontend;
InternalErrorReporter ice;
@ -244,7 +245,7 @@ struct FindNthOccurenceOf : public AstVisitor
* 2. Luau::query<AstExprBinary>(Luau::query<AstStatFunction>(block))
* 3. Luau::query<AstExprBinary>(block, {nth<AstExprBinary>(2)})
*/
template<typename T, size_t N = 1>
template<typename T, int N = 1>
T* query(AstNode* node, const std::vector<Nth>& nths = {nth<T>(N)})
{
static_assert(std::is_base_of_v<AstNode, T>, "T must be a derived class of AstNode");

View file

@ -1181,7 +1181,7 @@ s:match("[]")
nons:match("[]")
)~");
CHECK_EQ(result.warnings.size(), 2);
REQUIRE_EQ(result.warnings.size(), 2);
CHECK_EQ(result.warnings[0].text, "Invalid match pattern: expected ] at the end of the string to close a set");
CHECK_EQ(result.warnings[0].location.begin.line, 3);
CHECK_EQ(result.warnings[1].text, "Invalid match pattern: expected ] at the end of the string to close a set");
@ -1746,6 +1746,7 @@ local _ = not a == b
local _ = not a ~= b
local _ = not a <= b
local _ = a <= b == 0
local _ = a <= b <= 0
local _ = not a == not b -- weird but ok
@ -1760,11 +1761,12 @@ local _ = (a <= b) == 0
local _ = a <= (b == 0)
)");
REQUIRE_EQ(result.warnings.size(), 4);
CHECK_EQ(result.warnings[0].text, "not X == Y is equivalent to (not X) == Y; consider using X ~= Y, or wrap one of the expressions in parentheses to silence");
CHECK_EQ(result.warnings[1].text, "not X ~= Y is equivalent to (not X) ~= Y; consider using X == Y, or wrap one of the expressions in parentheses to silence");
CHECK_EQ(result.warnings[2].text, "not X <= Y is equivalent to (not X) <= Y; wrap one of the expressions in parentheses to silence");
CHECK_EQ(result.warnings[3].text, "X <= Y == Z is equivalent to (X <= Y) == Z; wrap one of the expressions in parentheses to silence");
REQUIRE_EQ(result.warnings.size(), 5);
CHECK_EQ(result.warnings[0].text, "not X == Y is equivalent to (not X) == Y; consider using X ~= Y, or add parentheses to silence");
CHECK_EQ(result.warnings[1].text, "not X ~= Y is equivalent to (not X) ~= Y; consider using X == Y, or add parentheses to silence");
CHECK_EQ(result.warnings[2].text, "not X <= Y is equivalent to (not X) <= Y; add parentheses to silence");
CHECK_EQ(result.warnings[3].text, "X <= Y == Z is equivalent to (X <= Y) == Z; add parentheses to silence");
CHECK_EQ(result.warnings[4].text, "X <= Y <= Z is equivalent to (X <= Y) <= Z; did you mean X <= Y and Y <= Z?");
}
TEST_SUITE_END();

View file

@ -943,8 +943,7 @@ TEST_CASE_FIXTURE(Fixture, "parse_interpolated_string_without_end_brace")
{
ScopedFastFlag sff{"LuauInterpolatedStringBaseSupport", true};
auto columnOfEndBraceError = [this](const char* code)
{
auto columnOfEndBraceError = [this](const char* code) {
try
{
parse(code);
@ -1737,6 +1736,48 @@ TEST_CASE_FIXTURE(Fixture, "parse_error_type_annotation")
matchParseError("local a : 2 = 2", "Expected type, got '2'");
}
TEST_CASE_FIXTURE(Fixture, "parse_error_missing_type_annotation")
{
ScopedFastFlag LuauTypeAnnotationLocationChange{"LuauTypeAnnotationLocationChange", true};
{
ParseResult result = tryParse("local x:");
CHECK(result.errors.size() == 1);
Position begin = result.errors[0].getLocation().begin;
Position end = result.errors[0].getLocation().end;
CHECK(begin.line == end.line);
int width = end.column - begin.column;
CHECK(width == 0);
CHECK(result.errors[0].getMessage() == "Expected type, got <eof>");
}
{
ParseResult result = tryParse(R"(
local x:=42
)");
CHECK(result.errors.size() == 1);
Position begin = result.errors[0].getLocation().begin;
Position end = result.errors[0].getLocation().end;
CHECK(begin.line == end.line);
int width = end.column - begin.column;
CHECK(width == 1); // Length of `=`
CHECK(result.errors[0].getMessage() == "Expected type, got '='");
}
{
ParseResult result = tryParse(R"(
function func():end
)");
CHECK(result.errors.size() == 1);
Position begin = result.errors[0].getLocation().begin;
Position end = result.errors[0].getLocation().end;
CHECK(begin.line == end.line);
int width = end.column - begin.column;
CHECK(width == 3); // Length of `end`
CHECK(result.errors[0].getMessage() == "Expected type, got 'end'");
}
}
TEST_CASE_FIXTURE(Fixture, "parse_declarations")
{
AstStatBlock* stat = parseEx(R"(

View file

@ -346,4 +346,17 @@ TEST_CASE_FIXTURE(Fixture, "prop_access_on_any_with_other_options")
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "union_of_types_regression_test")
{
ScopedFastFlag LuauUnionOfTypesFollow{"LuauUnionOfTypesFollow", true};
CheckResult result = check(R"(
--!strict
local stat
stat = stat and tonumber(stat) or stat
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_SUITE_END();

View file

@ -1061,7 +1061,6 @@ end
TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types")
{
ScopedFastFlag sffs{"LuauDeduceGmatchReturnTypes", true};
CheckResult result = check(R"END(
local a, b, c = string.gmatch("This is a string", "(.()(%a+))")()
)END");
@ -1075,7 +1074,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types")
TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types2")
{
ScopedFastFlag sffs{"LuauDeduceGmatchReturnTypes", true};
CheckResult result = check(R"END(
local a, b, c = ("This is a string"):gmatch("(.()(%a+))")()
)END");
@ -1089,7 +1087,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types2")
TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_default_capture")
{
ScopedFastFlag sffs{"LuauDeduceGmatchReturnTypes", true};
CheckResult result = check(R"END(
local a, b, c, d = string.gmatch("T(his)() is a string", ".")()
)END");
@ -1107,7 +1104,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_default_capture")
TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_balanced_escaped_parens")
{
ScopedFastFlag sffs{"LuauDeduceGmatchReturnTypes", true};
CheckResult result = check(R"END(
local a, b, c, d = string.gmatch("T(his) is a string", "((.)%b()())")()
)END");
@ -1127,7 +1123,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_balanced_escaped_parens
TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_parens_in_sets_are_ignored")
{
ScopedFastFlag sffs{"LuauDeduceGmatchReturnTypes", true};
CheckResult result = check(R"END(
local a, b, c = string.gmatch("T(his)() is a string", "(T[()])()")()
)END");
@ -1146,7 +1141,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_parens_in_sets_are_igno
TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_set_containing_lbracket")
{
ScopedFastFlag sffs{"LuauDeduceGmatchReturnTypes", true};
CheckResult result = check(R"END(
local a, b = string.gmatch("[[[", "()([[])")()
)END");
@ -1196,7 +1190,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_invalid_pattern_fallbac
TEST_CASE_FIXTURE(BuiltinsFixture, "match_capture_types")
{
ScopedFastFlag sffs{"LuauDeduceFindMatchReturnTypes", true};
CheckResult result = check(R"END(
local a, b, c = string.match("This is a string", "(.()(%a+))")
)END");
@ -1210,7 +1203,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "match_capture_types")
TEST_CASE_FIXTURE(BuiltinsFixture, "match_capture_types2")
{
ScopedFastFlag sffs{"LuauDeduceFindMatchReturnTypes", true};
CheckResult result = check(R"END(
local a, b, c = string.match("This is a string", "(.()(%a+))", "this should be a number")
)END");
@ -1229,7 +1221,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "match_capture_types2")
TEST_CASE_FIXTURE(BuiltinsFixture, "find_capture_types")
{
ScopedFastFlag sffs{"LuauDeduceFindMatchReturnTypes", true};
CheckResult result = check(R"END(
local d, e, a, b, c = string.find("This is a string", "(.()(%a+))")
)END");
@ -1245,7 +1236,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "find_capture_types")
TEST_CASE_FIXTURE(BuiltinsFixture, "find_capture_types2")
{
ScopedFastFlag sffs{"LuauDeduceFindMatchReturnTypes", true};
CheckResult result = check(R"END(
local d, e, a, b, c = string.find("This is a string", "(.()(%a+))", "this should be a number")
)END");
@ -1266,7 +1256,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "find_capture_types2")
TEST_CASE_FIXTURE(BuiltinsFixture, "find_capture_types3")
{
ScopedFastFlag sffs{"LuauDeduceFindMatchReturnTypes", true};
CheckResult result = check(R"END(
local d, e, a, b, c = string.find("This is a string", "(.()(%a+))", 1, "this should be a bool")
)END");
@ -1287,7 +1276,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "find_capture_types3")
TEST_CASE_FIXTURE(BuiltinsFixture, "find_capture_types3")
{
ScopedFastFlag sffs{"LuauDeduceFindMatchReturnTypes", true};
CheckResult result = check(R"END(
local d, e, a, b = string.find("This is a string", "(.()(%a+))", 1, true)
)END");

View file

@ -133,6 +133,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "vararg_function_is_quantified")
TableTypeVar* ttv = getMutable<TableTypeVar>(*r);
REQUIRE(ttv);
REQUIRE(ttv->props.count("f"));
TypeId k = ttv->props["f"].type;
REQUIRE(k);

View file

@ -14,6 +14,7 @@
using namespace Luau;
LUAU_FASTFLAG(LuauSpecialTypesAsterisked)
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
TEST_SUITE_BEGIN("TypeInferLoops");
@ -109,6 +110,18 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_with_just_one_iterator_is_ok")
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_loop_with_zero_iterators_dcr")
{
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
CheckResult result = check(R"(
function no_iter() end
for key in no_iter() do end
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_with_a_custom_iterator_should_type_check")
{
CheckResult result = check(R"(
@ -141,7 +154,7 @@ TEST_CASE_FIXTURE(Fixture, "for_in_loop_on_error")
end
)");
CHECK_EQ(2, result.errors.size());
LUAU_REQUIRE_ERROR_COUNT(2, result);
TypeId p = requireType("p");
if (FFlag::LuauSpecialTypesAsterisked)
@ -232,6 +245,30 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_loop_error_on_iterator_requiring_args
CHECK_EQ(0, acm->actual);
}
TEST_CASE_FIXTURE(Fixture, "for_in_loop_with_incompatible_args_to_iterator")
{
CheckResult result = check(R"(
function my_iter(state: string, index: number)
return state, index
end
local my_state = {}
local first_index = "first"
-- Type errors here. my_state and first_index cannot be passed to my_iter
for a, b in my_iter, my_state, first_index do
end
)");
LUAU_REQUIRE_ERROR_COUNT(2, result);
CHECK(get<TypeMismatch>(result.errors[1]));
CHECK(Location{{9, 29}, {9, 37}} == result.errors[0].location);
CHECK(get<TypeMismatch>(result.errors[1]));
CHECK(Location{{9, 39}, {9, 50}} == result.errors[1].location);
}
TEST_CASE_FIXTURE(Fixture, "for_in_loop_with_custom_iterator")
{
CheckResult result = check(R"(
@ -503,7 +540,7 @@ TEST_CASE_FIXTURE(Fixture, "loop_iter_basic")
end
)");
LUAU_REQUIRE_ERROR_COUNT(0, result);
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ(*typeChecker.numberType, *requireType("key"));
}

View file

@ -16,6 +16,35 @@ LUAU_FASTFLAG(LuauSpecialTypesAsterisked)
TEST_SUITE_BEGIN("TypeInferModules");
TEST_CASE_FIXTURE(BuiltinsFixture, "dcr_require_basic")
{
fileResolver.source["game/A"] = R"(
--!strict
return {
a = 1,
}
)";
fileResolver.source["game/B"] = R"(
--!strict
local A = require(game.A)
local b = A.a
)";
CheckResult aResult = frontend.check("game/A");
LUAU_REQUIRE_NO_ERRORS(aResult);
CheckResult bResult = frontend.check("game/B");
LUAU_REQUIRE_NO_ERRORS(bResult);
ModulePtr b = frontend.moduleResolver.modules["game/B"];
REQUIRE(b != nullptr);
std::optional<TypeId> bType = requireType(b, "b");
REQUIRE(bType);
CHECK(toString(*bType) == "number");
}
TEST_CASE_FIXTURE(BuiltinsFixture, "require")
{
fileResolver.source["game/A"] = R"(

View file

@ -11,7 +11,6 @@
#include "doctest.h"
LUAU_FASTFLAG(LuauDeduceFindMatchReturnTypes)
LUAU_FASTFLAG(LuauSpecialTypesAsterisked)
using namespace Luau;
@ -61,8 +60,8 @@ TEST_CASE_FIXTURE(Fixture, "string_method")
CheckResult result = check(R"(
local p = ("tacos"):len()
)");
CHECK_EQ(0, result.errors.size());
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ(*requireType("p"), *typeChecker.numberType);
}
@ -73,8 +72,8 @@ TEST_CASE_FIXTURE(Fixture, "string_function_indirect")
local l = s.lower
local p = l(s)
)");
CHECK_EQ(0, result.errors.size());
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ(*requireType("p"), *typeChecker.stringType);
}
@ -84,12 +83,9 @@ TEST_CASE_FIXTURE(Fixture, "string_function_other")
local s:string
local p = s:match("foo")
)");
CHECK_EQ(0, result.errors.size());
if (FFlag::LuauDeduceFindMatchReturnTypes)
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ(toString(requireType("p")), "string");
else
CHECK_EQ(toString(requireType("p")), "string?");
}
TEST_CASE_FIXTURE(Fixture, "CheckMethodsOfNumber")

View file

@ -62,7 +62,13 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "xpcall_returns_what_f_returns")
local a:boolean,b:number,c:string=xpcall(function(): (number,string)return 1,'foo'end,function(): (string,number)return'foo',1 end)
)";
CHECK_EQ(expected, decorateWithTypes(code));
CheckResult result = check(code);
CHECK("boolean" == toString(requireType("a")));
CHECK("number" == toString(requireType("b")));
CHECK("string" == toString(requireType("c")));
CHECK(expected == decorateWithTypes(code));
}
// We had a bug where if you have two type packs that looks like:
@ -609,4 +615,16 @@ TEST_CASE_FIXTURE(Fixture, "free_options_cannot_be_unified_together")
CHECK("b?" == toString(option2, opts)); // This should not hold.
}
TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_loop_with_zero_iterators")
{
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", false};
CheckResult result = check(R"(
function no_iter() end
for key in no_iter() do end -- This should not be ok
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_SUITE_END();

View file

@ -2994,8 +2994,6 @@ TEST_CASE_FIXTURE(Fixture, "expected_indexer_value_type_extra_2")
TEST_CASE_FIXTURE(Fixture, "expected_indexer_from_table_union")
{
ScopedFastFlag luauExpectedTableUnionIndexerType{"LuauExpectedTableUnionIndexerType", true};
LUAU_REQUIRE_NO_ERRORS(check(R"(local a: {[string]: {number | string}} = {a = {2, 's'}})"));
LUAU_REQUIRE_NO_ERRORS(check(R"(local a: {[string]: {number | string}}? = {a = {2, 's'}})"));
LUAU_REQUIRE_NO_ERRORS(check(R"(local a: {[string]: {[string]: {string?}}?} = {["a"] = {["b"] = {"a", "b"}}})"));

View file

@ -111,6 +111,11 @@ assert((function() local a = nil a = a and 2 return a end)() == nil)
assert((function() local a = 1 a = a or 2 return a end)() == 1)
assert((function() local a = nil a = a or 2 return a end)() == 2)
assert((function() local a a = 1 local b = 2 b = a and b return b end)() == 2)
assert((function() local a a = nil local b = 2 b = a and b return b end)() == nil)
assert((function() local a a = 1 local b = 2 b = a or b return b end)() == 1)
assert((function() local a a = nil local b = 2 b = a or b return b end)() == 2)
-- binary arithmetics coerces strings to numbers (sadly)
assert(1 + "2" == 3)
assert(2 * "0xa" == 20)

View file

@ -369,21 +369,31 @@ assert(not a and b:match('[^ ]+') == "short:1:")
local a,b = loadstring("nope", "=" .. string.rep("thisisaverylongstringitssolongthatitwontfitintotheinternalbufferprovidedtovariousdebugfacilities", 10))
assert(not a and b:match('[^ ]+') == "thisisaverylongstringitssolongthatitwontfitintotheinternalbufferprovidedtovariousdebugfacilitiesthisisaverylongstringitssolongthatitwontfitintotheinternalbufferprovidedtovariousdebugfacilitiesthisisaverylongstringitssolongthatitwontfitintotheinternalbuffe:1:")
-- arith errors
function ecall(fn, ...)
local ok, err = pcall(fn, ...)
assert(not ok)
return err:sub(err:find(": ") + 2, #err)
return err:sub((err:find(": ") or -1) + 2, #err)
end
-- arith errors
assert(ecall(function() return nil + 5 end) == "attempt to perform arithmetic (add) on nil and number")
assert(ecall(function() return "a" + "b" end) == "attempt to perform arithmetic (add) on string")
assert(ecall(function() return 1 > nil end) == "attempt to compare nil < number") -- note reversed order (by design)
assert(ecall(function() return "a" <= 5 end) == "attempt to compare string <= number")
-- table errors
assert(ecall(function() local t = {} t[nil] = 2 end) == "table index is nil")
assert(ecall(function() local t = {} t[0/0] = 2 end) == "table index is NaN")
assert(ecall(function() local t = {} rawset(t, nil, 2) end) == "table index is nil")
assert(ecall(function() local t = {} rawset(t, 0/0, 2) end) == "table index is NaN")
assert(ecall(function() local t = {} t[nil] = nil end) == "table index is nil")
assert(ecall(function() local t = {} t[0/0] = nil end) == "table index is NaN")
assert(ecall(function() local t = {} rawset(t, nil, nil) end) == "table index is nil")
assert(ecall(function() local t = {} rawset(t, 0/0, nil) end) == "table index is NaN")
-- for loop type errors
assert(ecall(function() for i='a',2 do end end) == "invalid 'for' initial value (number expected, got string)")
assert(ecall(function() for i=1,'a' do end end) == "invalid 'for' limit (number expected, got string)")

View file

@ -1,10 +1,8 @@
AnnotationTests.builtin_types_are_not_exported
AnnotationTests.cannot_use_nonexported_type
AnnotationTests.cloned_interface_maintains_pointers_between_definitions
AnnotationTests.duplicate_type_param_name
AnnotationTests.for_loop_counter_annotation_is_checked
AnnotationTests.generic_aliases_are_cloned_properly
AnnotationTests.interface_types_belong_to_interface_arena
AnnotationTests.instantiation_clone_has_to_follow
AnnotationTests.luau_ice_triggers_an_ice
AnnotationTests.luau_ice_triggers_an_ice_exception_with_flag
AnnotationTests.luau_ice_triggers_an_ice_exception_with_flag_handler
@ -26,6 +24,7 @@ AutocompleteTest.autocomplete_first_function_arg_expected_type
AutocompleteTest.autocomplete_for_in_middle_keywords
AutocompleteTest.autocomplete_for_middle_keywords
AutocompleteTest.autocomplete_if_middle_keywords
AutocompleteTest.autocomplete_interpolated_string
AutocompleteTest.autocomplete_on_string_singletons
AutocompleteTest.autocomplete_oop_implicit_self
AutocompleteTest.autocomplete_repeat_middle_keyword
@ -56,14 +55,12 @@ AutocompleteTest.global_functions_are_not_scoped_lexically
AutocompleteTest.globals_are_order_independent
AutocompleteTest.if_then_else_elseif_completions
AutocompleteTest.keyword_methods
AutocompleteTest.keyword_types
AutocompleteTest.library_non_self_calls_are_fine
AutocompleteTest.library_self_calls_are_invalid
AutocompleteTest.local_function
AutocompleteTest.local_function_params
AutocompleteTest.local_functions_fall_out_of_scope
AutocompleteTest.method_call_inside_function_body
AutocompleteTest.module_type_members
AutocompleteTest.nested_member_completions
AutocompleteTest.nested_recursive_function
AutocompleteTest.no_function_name_suggestions
@ -78,7 +75,6 @@ AutocompleteTest.return_types
AutocompleteTest.sometimes_the_metatable_is_an_error
AutocompleteTest.source_module_preservation_and_invalidation
AutocompleteTest.statement_between_two_statements
AutocompleteTest.stop_at_first_stat_when_recommending_keywords
AutocompleteTest.string_prim_non_self_calls_are_avoided
AutocompleteTest.string_prim_self_calls_are_fine
AutocompleteTest.suggest_external_module_type
@ -155,6 +151,7 @@ BuiltinTests.string_format_tostring_specifier
BuiltinTests.string_format_tostring_specifier_type_constraint
BuiltinTests.string_format_use_correct_argument
BuiltinTests.string_format_use_correct_argument2
BuiltinTests.string_format_use_correct_argument3
BuiltinTests.string_lib_self_noself
BuiltinTests.table_concat_returns_string
BuiltinTests.table_dot_remove_optionally_returns_generic
@ -168,31 +165,21 @@ BuiltinTests.tonumber_returns_optional_number_type
BuiltinTests.tonumber_returns_optional_number_type2
DefinitionTests.declaring_generic_functions
DefinitionTests.definition_file_classes
DefinitionTests.definition_file_loading
DefinitionTests.definitions_documentation_symbols
DefinitionTests.documentation_symbols_dont_attach_to_persistent_types
DefinitionTests.single_class_type_identity_in_global_types
FrontendTest.ast_node_at_position
FrontendTest.automatically_check_dependent_scripts
FrontendTest.check_without_builtin_next
FrontendTest.dont_reparse_clean_file_when_linting
FrontendTest.environments
FrontendTest.imported_table_modification_2
FrontendTest.it_should_be_safe_to_stringify_errors_when_full_type_graph_is_discarded
FrontendTest.no_use_after_free_with_type_fun_instantiation
FrontendTest.nocheck_cycle_used_by_checked
FrontendTest.nocheck_modules_are_typed
FrontendTest.produce_errors_for_unchanged_file_with_a_syntax_error
FrontendTest.recheck_if_dependent_script_is_dirty
FrontendTest.reexport_cyclic_type
FrontendTest.reexport_type_alias
FrontendTest.report_require_to_nonexistent_file
FrontendTest.report_syntax_error_in_required_file
FrontendTest.trace_requires_in_nonstrict_mode
GenericsTests.apply_type_function_nested_generics1
GenericsTests.apply_type_function_nested_generics2
GenericsTests.better_mismatch_error_messages
GenericsTests.bound_tables_do_not_clone_original_fields
GenericsTests.calling_self_generic_methods
GenericsTests.check_generic_typepack_function
GenericsTests.check_mutual_generic_functions
@ -208,7 +195,6 @@ GenericsTests.factories_of_generics
GenericsTests.generic_argument_count_too_few
GenericsTests.generic_argument_count_too_many
GenericsTests.generic_factories
GenericsTests.generic_functions_dont_cache_type_parameters
GenericsTests.generic_functions_in_types
GenericsTests.generic_functions_should_be_memory_safe
GenericsTests.generic_table_method
@ -245,7 +231,6 @@ IntersectionTypes.no_stack_overflow_from_flattenintersection
IntersectionTypes.overload_is_not_a_function
IntersectionTypes.select_correct_union_fn
IntersectionTypes.should_still_pick_an_overload_whose_arguments_are_unions
IntersectionTypes.table_intersection_write
IntersectionTypes.table_intersection_write_sealed
IntersectionTypes.table_intersection_write_sealed_indirect
IntersectionTypes.table_write_sealed_indirect
@ -255,7 +240,6 @@ Linter.TableOperations
ModuleTests.clone_self_property
ModuleTests.deepClone_cyclic_table
ModuleTests.do_not_clone_reexports
ModuleTests.do_not_clone_types_of_reexported_values
NonstrictModeTests.delay_function_does_not_require_its_argument_to_return_anything
NonstrictModeTests.for_in_iterator_variables_are_any
NonstrictModeTests.function_parameters_are_any
@ -319,6 +303,7 @@ ProvisionalTests.typeguard_inference_incomplete
ProvisionalTests.weird_fail_to_unify_type_pack
ProvisionalTests.weirditer_should_not_loop_forever
ProvisionalTests.while_body_are_also_refined
ProvisionalTests.xpcall_returns_what_f_returns
RefinementTest.and_constraint
RefinementTest.and_or_peephole_refinement
RefinementTest.apply_refinements_on_astexprindexexpr_whose_subscript_expr_is_constant_string
@ -358,6 +343,7 @@ RefinementTest.not_and_constraint
RefinementTest.not_t_or_some_prop_of_t
RefinementTest.or_predicate_with_truthy_predicates
RefinementTest.parenthesized_expressions_are_followed_through
RefinementTest.refine_a_property_not_to_be_nil_through_an_intersection_table
RefinementTest.refine_the_correct_types_opposite_of_when_a_is_not_number_or_string
RefinementTest.refine_unknowns
RefinementTest.string_not_equal_to_string_or_nil
@ -394,7 +380,6 @@ TableTests.augment_table
TableTests.builtin_table_names
TableTests.call_method
TableTests.cannot_augment_sealed_table
TableTests.cannot_call_tables
TableTests.cannot_change_type_of_unsealed_table_prop
TableTests.casting_sealed_tables_with_props_into_table_with_indexer
TableTests.casting_tables_with_props_into_table_with_indexer3
@ -409,7 +394,6 @@ TableTests.defining_a_self_method_for_a_builtin_sealed_table_must_fail
TableTests.defining_a_self_method_for_a_local_sealed_table_must_fail
TableTests.dont_crash_when_setmetatable_does_not_produce_a_metatabletypevar
TableTests.dont_hang_when_trying_to_look_up_in_cyclic_metatable_index
TableTests.dont_invalidate_the_properties_iterator_of_free_table_when_rolled_back
TableTests.dont_leak_free_table_props
TableTests.dont_quantify_table_that_belongs_to_outer_scope
TableTests.dont_suggest_exact_match_keys
@ -448,6 +432,7 @@ TableTests.length_operator_intersection
TableTests.length_operator_non_table_union
TableTests.length_operator_union
TableTests.length_operator_union_errors
TableTests.less_exponential_blowup_please
TableTests.meta_add
TableTests.meta_add_both_ways
TableTests.meta_add_inferred
@ -470,7 +455,6 @@ TableTests.quantify_even_that_table_was_never_exported_at_all
TableTests.quantify_metatables_of_metatables_of_table
TableTests.quantifying_a_bound_var_works
TableTests.reasonable_error_when_adding_a_nonexistent_property_to_an_array_like_table
TableTests.recursive_metatable_type_call
TableTests.result_is_always_any_if_lhs_is_any
TableTests.result_is_bool_for_equality_operators_if_lhs_is_any
TableTests.right_table_missing_key
@ -480,7 +464,6 @@ TableTests.scalar_is_not_a_subtype_of_a_compatible_polymorphic_shape_type
TableTests.shared_selfs
TableTests.shared_selfs_from_free_param
TableTests.shared_selfs_through_metatables
TableTests.table_function_check_use_after_free
TableTests.table_indexing_error_location
TableTests.table_insert_should_cope_with_optional_properties_in_nonstrict
TableTests.table_insert_should_cope_with_optional_properties_in_strict
@ -525,13 +508,10 @@ TryUnifyTests.result_of_failed_typepack_unification_is_constrained
TryUnifyTests.typepack_unification_should_trim_free_tails
TryUnifyTests.variadics_should_use_reversed_properly
TypeAliases.cli_38393_recursive_intersection_oom
TypeAliases.corecursive_types_generic
TypeAliases.forward_declared_alias_is_not_clobbered_by_prior_unification_with_any
TypeAliases.general_require_multi_assign
TypeAliases.generic_param_remap
TypeAliases.mismatched_generic_pack_type_param
TypeAliases.mismatched_generic_type_param
TypeAliases.mutually_recursive_types_errors
TypeAliases.mutually_recursive_types_restriction_not_ok_1
TypeAliases.mutually_recursive_types_restriction_not_ok_2
TypeAliases.mutually_recursive_types_swapsies_not_ok
@ -543,7 +523,6 @@ TypeAliases.type_alias_fwd_declaration_is_precise
TypeAliases.type_alias_local_mutation
TypeAliases.type_alias_local_rename
TypeAliases.type_alias_of_an_imported_recursive_generic_type
TypeAliases.type_alias_of_an_imported_recursive_type
TypeInfer.checking_should_not_ice
TypeInfer.cyclic_follow
TypeInfer.do_not_bind_a_free_table_to_a_union_containing_that_table
@ -559,20 +538,21 @@ TypeInfer.tc_if_else_expressions_expected_type_1
TypeInfer.tc_if_else_expressions_expected_type_2
TypeInfer.tc_if_else_expressions_expected_type_3
TypeInfer.tc_if_else_expressions_type_union
TypeInfer.tc_interpolated_string_basic
TypeInfer.tc_interpolated_string_constant_type
TypeInfer.tc_interpolated_string_with_invalid_expression
TypeInfer.type_infer_recursion_limit_no_ice
TypeInfer.warn_on_lowercase_parent_property
TypeInferAnyError.assign_prop_to_table_by_calling_any_yields_any
TypeInferAnyError.can_get_length_of_any
TypeInferAnyError.for_in_loop_iterator_is_any
TypeInferAnyError.for_in_loop_iterator_is_any2
TypeInferAnyError.for_in_loop_iterator_is_error
TypeInferAnyError.for_in_loop_iterator_is_error2
TypeInferAnyError.for_in_loop_iterator_returns_any
TypeInferAnyError.for_in_loop_iterator_returns_any2
TypeInferAnyError.length_of_error_type_does_not_produce_an_error
TypeInferAnyError.replace_every_free_type_when_unifying_a_complex_function_with_any
TypeInferAnyError.union_of_types_regression_test
TypeInferClasses.call_base_method
TypeInferClasses.call_instance_method
TypeInferClasses.can_assign_to_prop_of_base_class_using_string
TypeInferClasses.can_read_prop_of_base_class_using_string
TypeInferClasses.class_type_mismatch_with_name_conflict
TypeInferClasses.classes_can_have_overloaded_operators
TypeInferClasses.classes_without_overloaded_operators_cannot_be_added
@ -582,10 +562,13 @@ TypeInferClasses.higher_order_function_return_type_is_not_contravariant
TypeInferClasses.higher_order_function_return_values_are_covariant
TypeInferClasses.optional_class_field_access_error
TypeInferClasses.table_class_unification_reports_sane_errors_for_missing_properties
TypeInferClasses.table_indexers_are_invariant
TypeInferClasses.table_properties_are_invariant
TypeInferClasses.warn_when_prop_almost_matches
TypeInferClasses.we_can_report_when_someone_is_trying_to_use_a_table_rather_than_a_class
TypeInferFunctions.another_indirect_function_case_where_it_is_ok_to_provide_too_many_arguments
TypeInferFunctions.another_recursive_local_function
TypeInferFunctions.call_o_with_another_argument_after_foo_was_quantified
TypeInferFunctions.calling_function_with_anytypepack_doesnt_leak_free_types
TypeInferFunctions.calling_function_with_incorrect_argument_type_yields_errors_spanning_argument
TypeInferFunctions.complicated_return_types_require_an_explicit_annotation
@ -634,43 +617,23 @@ TypeInferFunctions.too_many_arguments
TypeInferFunctions.too_many_return_values
TypeInferFunctions.vararg_function_is_quantified
TypeInferFunctions.vararg_functions_should_allow_calls_of_any_types_and_size
TypeInferLoops.for_in_loop
TypeInferLoops.for_in_loop_error_on_factory_not_returning_the_right_amount_of_values
TypeInferLoops.for_in_loop_error_on_iterator_requiring_args_but_none_given
TypeInferLoops.for_in_loop_on_error
TypeInferLoops.for_in_loop_on_non_function
TypeInferLoops.for_in_loop_should_fail_with_non_function_iterator
TypeInferLoops.for_in_loop_where_iteratee_is_free
TypeInferLoops.for_in_loop_with_custom_iterator
TypeInferLoops.for_in_loop_with_next
TypeInferLoops.for_in_with_a_custom_iterator_should_type_check
TypeInferLoops.for_in_with_an_iterator_of_type_any
TypeInferLoops.for_in_with_just_one_iterator_is_ok
TypeInferLoops.fuzz_fail_missing_instantitation_follow
TypeInferLoops.ipairs_produces_integral_indices
TypeInferLoops.loop_iter_basic
TypeInferLoops.loop_iter_iter_metamethod
TypeInferLoops.loop_iter_no_indexer_nonstrict
TypeInferLoops.loop_iter_no_indexer_strict
TypeInferLoops.loop_iter_trailing_nil
TypeInferLoops.loop_typecheck_crash_on_empty_optional
TypeInferLoops.properly_infer_iteratee_is_a_free_table
TypeInferLoops.unreachable_code_after_infinite_loop
TypeInferLoops.varlist_declared_by_for_in_loop_should_be_free
TypeInferModules.do_not_modify_imported_types
TypeInferModules.do_not_modify_imported_types_2
TypeInferModules.do_not_modify_imported_types_3
TypeInferModules.general_require_call_expression
TypeInferModules.custom_require_global
TypeInferModules.general_require_type_mismatch
TypeInferModules.module_type_conflict
TypeInferModules.module_type_conflict_instantiated
TypeInferModules.require
TypeInferModules.require_a_variadic_function
TypeInferModules.require_failed_module
TypeInferModules.require_module_that_does_not_export
TypeInferModules.require_types
TypeInferModules.type_error_of_unknown_qualified_type
TypeInferModules.warn_if_you_try_to_require_a_non_modulescript
TypeInferOOP.CheckMethodsOfSealed
TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_another_overload_works
TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_it_wont_help_2
@ -725,7 +688,6 @@ TypeInferOperators.typecheck_unary_minus_error
TypeInferOperators.unary_not_is_boolean
TypeInferOperators.unknown_type_in_comparison
TypeInferOperators.UnknownGlobalCompoundAssign
TypeInferPrimitives.cannot_call_primitives
TypeInferPrimitives.CheckMethodsOfNumber
TypeInferPrimitives.string_function_other
TypeInferPrimitives.string_index
@ -768,7 +730,6 @@ TypePackTests.type_alias_type_packs_nested
TypePackTests.type_pack_hidden_free_tail_infinite_growth
TypePackTests.type_pack_type_parameters
TypePackTests.varargs_inference_through_multiple_scopes
TypePackTests.variadic_argument_tail
TypePackTests.variadic_pack_syntax
TypePackTests.variadic_packs
TypeSingletons.bool_singleton_subtype
@ -793,7 +754,6 @@ TypeSingletons.string_singletons_escape_chars
TypeSingletons.string_singletons_mismatch
TypeSingletons.table_insert_with_a_singleton_argument
TypeSingletons.table_properties_type_error_escapes
TypeSingletons.tagged_unions_immutable_tag
TypeSingletons.tagged_unions_using_singletons
TypeSingletons.taking_the_length_of_string_singleton
TypeSingletons.taking_the_length_of_union_of_string_singleton

View file

@ -84,12 +84,16 @@ def main():
failList = loadFailList()
p = sp.Popen(
[
commandLine = [
args.path,
"--reporters=xml",
"--fflags=true,DebugLuauDeferredConstraintResolution=true",
],
]
print('>', ' '.join(commandLine), file=sys.stderr)
p = sp.Popen(
commandLine,
stdout=sp.PIPE,
)
@ -122,7 +126,7 @@ def main():
with open(FAIL_LIST_PATH, "w", newline="\n") as f:
for name in newFailList:
print(name, file=f)
print("Updated faillist.txt")
print("Updated faillist.txt", file=sys.stderr)
if handler.numSkippedTests > 0:
print(