mirror of
https://github.com/luau-lang/luau.git
synced 2024-11-15 06:15:44 +08:00
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:
parent
42c24f98d9
commit
ae35ada579
@ -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
|
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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:
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
@ -287,7 +291,8 @@ struct FunctionTypeVar
|
||||
std::vector<std::optional<FunctionArgument>> argNames;
|
||||
TypePackId retTypes;
|
||||
std::optional<FunctionDefinition> definition;
|
||||
MagicFunction magicFunction = nullptr; // Function pointer, can be nullptr.
|
||||
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;
|
||||
|
@ -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);
|
||||
|
@ -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!");
|
||||
|
||||
|
@ -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
|
||||
|
@ -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>())
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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))
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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) {
|
||||
return itp == p.ty;
|
||||
});
|
||||
auto [typeArguments, packArguments] = saturateArguments(*tf, petv->typeArguments, petv->packArguments, arena);
|
||||
|
||||
bool samePacks = std::equal(
|
||||
packArguments.begin(), packArguments.end(), petv->fn.typePackParams.begin(), petv->fn.typePackParams.end(), [](auto&& itp, auto&& p) {
|
||||
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(), 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
|
||||
|
@ -3,6 +3,7 @@
|
||||
#include "Luau/ConstraintSolverLogger.h"
|
||||
|
||||
#include "Luau/JsonEmitter.h"
|
||||
#include "Luau/ToString.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauFixNameMaps);
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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));
|
||||
|
@ -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('[');
|
||||
|
@ -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,22 +2704,26 @@ 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);
|
||||
|
||||
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(),
|
||||
lop.c_str(), rop.c_str());
|
||||
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; 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());
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -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};
|
||||
|
@ -510,7 +510,7 @@ void Substitution::foundDirty(TypeId ty)
|
||||
ty = log->follow(ty);
|
||||
|
||||
if (FFlag::LuauSubstitutionReentrant && newTypes.contains(ty))
|
||||
return;
|
||||
return;
|
||||
|
||||
if (isDirty(ty))
|
||||
newTypes[ty] = follow(clean(ty));
|
||||
@ -523,7 +523,7 @@ void Substitution::foundDirty(TypePackId tp)
|
||||
tp = log->follow(tp);
|
||||
|
||||
if (FFlag::LuauSubstitutionReentrant && newPacks.contains(tp))
|
||||
return;
|
||||
return;
|
||||
|
||||
if (isDirty(tp))
|
||||
newPacks[tp] = follow(clean(tp));
|
||||
|
@ -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");
|
||||
};
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -8,6 +8,13 @@
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
BlockedTypePack::BlockedTypePack()
|
||||
: index(++nextIndex)
|
||||
{
|
||||
}
|
||||
|
||||
size_t BlockedTypePack::nextIndex = 0;
|
||||
|
||||
TypePackVar::TypePackVar(const TypePackVariant& tp)
|
||||
: ty(tp)
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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.
|
||||
|
@ -13,7 +13,8 @@ Free::Free(TypeLevel level)
|
||||
}
|
||||
|
||||
Free::Free(Scope* scope)
|
||||
: scope(scope)
|
||||
: index(++nextIndex)
|
||||
, scope(scope)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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,23 +1658,35 @@ 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
|
||||
{
|
||||
Location location = lexer.current().location;
|
||||
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;
|
||||
|
||||
// For a missing type annotation, capture 'space' between last token and the next one
|
||||
location = Location(lexer.previousLocation().end, lexer.current().location.begin);
|
||||
// For a missing type annotation, capture 'space' between last token and the next one
|
||||
location = Location(lexer.previousLocation().end, lexer.current().location.begin);
|
||||
|
||||
return {reportTypeAnnotationError(location, {}, /*isMissing*/ true, "Expected type, got %s", lexer.current().toString().c_str()), {}};
|
||||
return {reportTypeAnnotationError(location, {}, /*isMissing*/ true, "Expected type, got %s", lexer.current().toString().c_str()), {}};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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,47 +400,29 @@ 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)
|
||||
{
|
||||
if (i > 0 && opc == LOP_FASTCALL2K)
|
||||
{
|
||||
int32_t cid = getConstantIndex(expr->args.data[i]);
|
||||
if (cid < 0)
|
||||
CompileError::raise(expr->location, "Exceeded constant limit; simplify the code to compile");
|
||||
int32_t cid = getConstantIndex(expr->args.data[i]);
|
||||
if (cid < 0)
|
||||
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
|
||||
}
|
||||
args[i] = cid;
|
||||
}
|
||||
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);
|
||||
}
|
||||
@ -468,24 +444,10 @@ struct Compiler
|
||||
// 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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
// note, these instructions are normally not executed and are used as a fallback for FASTCALL
|
||||
@ -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,102 +1042,64 @@ struct Compiler
|
||||
std::swap(left, right);
|
||||
}
|
||||
|
||||
if (FFlag::LuauCompileXEQ)
|
||||
uint8_t rl = compileExprAuto(left, rs);
|
||||
|
||||
if (isEq && operandIsConstant)
|
||||
{
|
||||
uint8_t rl = compileExprAuto(left, rs);
|
||||
const Constant* cv = constants.find(right);
|
||||
LUAU_ASSERT(cv && cv->type != Constant::Type_Unknown);
|
||||
|
||||
if (isEq && operandIsConstant)
|
||||
LuauOpcode opc = LOP_NOP;
|
||||
int32_t cid = -1;
|
||||
uint32_t flip = (expr->op == AstExprBinary::CompareEq) == not_ ? 0x80000000 : 0;
|
||||
|
||||
switch (cv->type)
|
||||
{
|
||||
const Constant* cv = constants.find(right);
|
||||
LUAU_ASSERT(cv && cv->type != Constant::Type_Unknown);
|
||||
case Constant::Type_Nil:
|
||||
opc = LOP_JUMPXEQKNIL;
|
||||
cid = 0;
|
||||
break;
|
||||
|
||||
LuauOpcode opc = LOP_NOP;
|
||||
int32_t cid = -1;
|
||||
uint32_t flip = (expr->op == AstExprBinary::CompareEq) == not_ ? 0x80000000 : 0;
|
||||
case Constant::Type_Boolean:
|
||||
opc = LOP_JUMPXEQKB;
|
||||
cid = cv->valueBoolean;
|
||||
break;
|
||||
|
||||
switch (cv->type)
|
||||
{
|
||||
case Constant::Type_Nil:
|
||||
opc = LOP_JUMPXEQKNIL;
|
||||
cid = 0;
|
||||
break;
|
||||
case Constant::Type_Number:
|
||||
opc = LOP_JUMPXEQKN;
|
||||
cid = getConstantIndex(right);
|
||||
break;
|
||||
|
||||
case Constant::Type_Boolean:
|
||||
opc = LOP_JUMPXEQKB;
|
||||
cid = cv->valueBoolean;
|
||||
break;
|
||||
case Constant::Type_String:
|
||||
opc = LOP_JUMPXEQKS;
|
||||
cid = getConstantIndex(right);
|
||||
break;
|
||||
|
||||
case Constant::Type_Number:
|
||||
opc = LOP_JUMPXEQKN;
|
||||
cid = getConstantIndex(right);
|
||||
break;
|
||||
|
||||
case Constant::Type_String:
|
||||
opc = LOP_JUMPXEQKS;
|
||||
cid = getConstantIndex(right);
|
||||
break;
|
||||
|
||||
default:
|
||||
LUAU_ASSERT(!"Unexpected constant type");
|
||||
}
|
||||
|
||||
if (cid < 0)
|
||||
CompileError::raise(expr->location, "Exceeded constant limit; simplify the code to compile");
|
||||
|
||||
size_t jumpLabel = bytecode.emitLabel();
|
||||
|
||||
bytecode.emitAD(opc, rl, 0);
|
||||
bytecode.emitAux(cid | flip);
|
||||
|
||||
return jumpLabel;
|
||||
default:
|
||||
LUAU_ASSERT(!"Unexpected constant type");
|
||||
}
|
||||
else
|
||||
{
|
||||
LuauOpcode opc = getJumpOpCompare(expr->op, not_);
|
||||
|
||||
uint8_t rr = compileExprAuto(right, rs);
|
||||
if (cid < 0)
|
||||
CompileError::raise(expr->location, "Exceeded constant limit; simplify the code to compile");
|
||||
|
||||
size_t jumpLabel = bytecode.emitLabel();
|
||||
size_t jumpLabel = bytecode.emitLabel();
|
||||
|
||||
if (expr->op == AstExprBinary::CompareGt || expr->op == AstExprBinary::CompareGe)
|
||||
{
|
||||
bytecode.emitAD(opc, rr, 0);
|
||||
bytecode.emitAux(rl);
|
||||
}
|
||||
else
|
||||
{
|
||||
bytecode.emitAD(opc, rl, 0);
|
||||
bytecode.emitAux(rr);
|
||||
}
|
||||
bytecode.emitAD(opc, rl, 0);
|
||||
bytecode.emitAux(cid | flip);
|
||||
|
||||
return jumpLabel;
|
||||
}
|
||||
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);
|
||||
uint8_t 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.emitAD(opc, rr, 0);
|
||||
bytecode.emitAux(rl);
|
||||
}
|
||||
else
|
||||
@ -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,111 +3014,82 @@ 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);
|
||||
|
||||
for (size_t i = 0; i < stat->vars.size; ++i)
|
||||
vars[i].lvalue = compileLValue(stat->vars.data[i], rs);
|
||||
|
||||
// perform conflict resolution: if any expression refers to a local that is assigned before evaluating it, we assign to a temporary
|
||||
// register after this, vars[i].conflictReg is set for locals that need to be assigned in the second pass
|
||||
resolveAssignConflicts(stat, vars, stat->values);
|
||||
|
||||
// compute rhs into (mostly) fresh registers
|
||||
// note that when the lhs assigment is a local, we evaluate directly into that register
|
||||
// this is possible because resolveAssignConflicts renamed conflicting locals into temporaries
|
||||
// after this, vars[i].valueReg is set to a register with the value for *all* vars, but some have already been assigned
|
||||
for (size_t i = 0; i < stat->vars.size && i < stat->values.size; ++i)
|
||||
{
|
||||
// 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);
|
||||
AstExpr* value = stat->values.data[i];
|
||||
|
||||
for (size_t i = 0; i < stat->vars.size; ++i)
|
||||
vars[i].lvalue = compileLValue(stat->vars.data[i], rs);
|
||||
|
||||
// perform conflict resolution: if any expression refers to a local that is assigned before evaluating it, we assign to a temporary
|
||||
// register after this, vars[i].conflictReg is set for locals that need to be assigned in the second pass
|
||||
resolveAssignConflicts(stat, vars, stat->values);
|
||||
|
||||
// compute rhs into (mostly) fresh registers
|
||||
// note that when the lhs assigment is a local, we evaluate directly into that register
|
||||
// this is possible because resolveAssignConflicts renamed conflicting locals into temporaries
|
||||
// after this, vars[i].valueReg is set to a register with the value for *all* vars, but some have already been assigned
|
||||
for (size_t i = 0; i < stat->vars.size && i < stat->values.size; ++i)
|
||||
if (i + 1 == stat->values.size && stat->vars.size > stat->values.size)
|
||||
{
|
||||
AstExpr* value = stat->values.data[i];
|
||||
// allocate a consecutive range of regs for all remaining vars and compute everything into temps
|
||||
// note, this also handles trailing nils
|
||||
uint8_t rest = uint8_t(stat->vars.size - stat->values.size + 1);
|
||||
uint8_t temp = allocReg(stat, rest);
|
||||
|
||||
if (i + 1 == stat->values.size && stat->vars.size > stat->values.size)
|
||||
compileExprTempN(value, temp, rest, /* targetTop= */ true);
|
||||
|
||||
for (size_t j = i; j < stat->vars.size; ++j)
|
||||
vars[j].valueReg = uint8_t(temp + (j - i));
|
||||
}
|
||||
else
|
||||
{
|
||||
Assignment& var = vars[i];
|
||||
|
||||
// if target is a local, use compileExpr directly to target
|
||||
if (var.lvalue.kind == LValue::Kind_Local)
|
||||
{
|
||||
// allocate a consecutive range of regs for all remaining vars and compute everything into temps
|
||||
// note, this also handles trailing nils
|
||||
uint8_t rest = uint8_t(stat->vars.size - stat->values.size + 1);
|
||||
uint8_t temp = allocReg(stat, rest);
|
||||
var.valueReg = (var.conflictReg == kInvalidReg) ? var.lvalue.reg : var.conflictReg;
|
||||
|
||||
compileExprTempN(value, temp, rest, /* targetTop= */ true);
|
||||
|
||||
for (size_t j = i; j < stat->vars.size; ++j)
|
||||
vars[j].valueReg = uint8_t(temp + (j - i));
|
||||
compileExpr(stat->values.data[i], var.valueReg);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assignment& var = vars[i];
|
||||
|
||||
// if target is a local, use compileExpr directly to target
|
||||
if (var.lvalue.kind == LValue::Kind_Local)
|
||||
{
|
||||
var.valueReg = (var.conflictReg == kInvalidReg) ? var.lvalue.reg : var.conflictReg;
|
||||
|
||||
compileExpr(stat->values.data[i], var.valueReg);
|
||||
}
|
||||
else
|
||||
{
|
||||
var.valueReg = compileExprAuto(stat->values.data[i], rs);
|
||||
}
|
||||
var.valueReg = compileExprAuto(stat->values.data[i], rs);
|
||||
}
|
||||
}
|
||||
|
||||
// compute expressions with side effects for lulz
|
||||
for (size_t i = stat->vars.size; i < stat->values.size; ++i)
|
||||
{
|
||||
RegScope rsi(this);
|
||||
compileExprAuto(stat->values.data[i], rsi);
|
||||
}
|
||||
|
||||
// almost done... let's assign everything left to right, noting that locals were either written-to directly, or will be written-to in a
|
||||
// separate pass to avoid conflicts
|
||||
for (const Assignment& var : vars)
|
||||
{
|
||||
LUAU_ASSERT(var.valueReg != kInvalidReg);
|
||||
|
||||
if (var.lvalue.kind != LValue::Kind_Local)
|
||||
{
|
||||
setDebugLine(var.lvalue.location);
|
||||
compileAssign(var.lvalue, var.valueReg);
|
||||
}
|
||||
}
|
||||
|
||||
// all regular local writes are done by the prior loops by computing result directly into target, so this just handles conflicts OR
|
||||
// local copies from temporary registers in multret context, since in that case we have to allocate consecutive temporaries
|
||||
for (const Assignment& var : vars)
|
||||
{
|
||||
if (var.lvalue.kind == LValue::Kind_Local && var.valueReg != var.lvalue.reg)
|
||||
bytecode.emitABC(LOP_MOVE, var.lvalue.reg, var.valueReg, 0);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
// compute expressions with side effects for lulz
|
||||
for (size_t i = stat->vars.size; i < stat->values.size; ++i)
|
||||
{
|
||||
// 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);
|
||||
RegScope rsi(this);
|
||||
compileExprAuto(stat->values.data[i], rsi);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < stat->vars.size; ++i)
|
||||
vars[i] = compileLValue(stat->vars.data[i], rs);
|
||||
// almost done... let's assign everything left to right, noting that locals were either written-to directly, or will be written-to in a
|
||||
// separate pass to avoid conflicts
|
||||
for (const Assignment& var : vars)
|
||||
{
|
||||
LUAU_ASSERT(var.valueReg != kInvalidReg);
|
||||
|
||||
// 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)
|
||||
if (var.lvalue.kind != LValue::Kind_Local)
|
||||
{
|
||||
setDebugLine(stat->vars.data[i]);
|
||||
compileAssign(vars[i], uint8_t(regs + i));
|
||||
setDebugLine(var.lvalue.location);
|
||||
compileAssign(var.lvalue, var.valueReg);
|
||||
}
|
||||
}
|
||||
|
||||
// all regular local writes are done by the prior loops by computing result directly into target, so this just handles conflicts OR
|
||||
// local copies from temporary registers in multret context, since in that case we have to allocate consecutive temporaries
|
||||
for (const Assignment& var : vars)
|
||||
{
|
||||
if (var.lvalue.kind == LValue::Kind_Local && var.valueReg != var.lvalue.reg)
|
||||
bytecode.emitABC(LOP_MOVE, var.lvalue.reg, var.valueReg, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void compileStatCompoundAssign(AstStatCompoundAssign* stat)
|
||||
|
@ -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);
|
||||
|
@ -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");
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
// The following userdata request might cause an overflow.
|
||||
lua_newuserdatadtor(L1, SIZE_MAX, [](void* d){});
|
||||
// The overflow might segfault in the following call.
|
||||
lua_getmetatable(L1, -1);
|
||||
return 0;
|
||||
}, nullptr);
|
||||
lua_pushcfunction(
|
||||
L,
|
||||
[](lua_State* L1) {
|
||||
// The following userdata request might cause an overflow.
|
||||
lua_newuserdatadtor(L1, SIZE_MAX, [](void* d) {});
|
||||
// The overflow might segfault in the following call.
|
||||
lua_getmetatable(L1, -1);
|
||||
return 0;
|
||||
},
|
||||
nullptr);
|
||||
|
||||
CHECK(lua_pcall(L, 0, 0, 0) == LUA_ERRRUN);
|
||||
CHECK(strcmp(lua_tostring(L, -1), "memory allocation error: block too big") == 0);
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -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)
|
||||
|
@ -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");
|
||||
|
@ -94,7 +94,7 @@ TEST_CASE("write_optional")
|
||||
write(emitter, std::optional<bool>{true});
|
||||
emitter.writeComma();
|
||||
write(emitter, std::nullopt);
|
||||
|
||||
|
||||
CHECK(emitter.str() == "true,null");
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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"(
|
||||
|
@ -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();
|
||||
|
@ -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");
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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"));
|
||||
}
|
||||
|
||||
|
@ -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"(
|
||||
|
@ -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)
|
||||
CHECK_EQ(toString(requireType("p")), "string");
|
||||
else
|
||||
CHECK_EQ(toString(requireType("p")), "string?");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK_EQ(toString(requireType("p")), "string");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "CheckMethodsOfNumber")
|
||||
|
@ -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();
|
||||
|
@ -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"}}})"));
|
||||
|
@ -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)
|
||||
|
@ -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)")
|
||||
|
@ -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
|
||||
|
@ -84,12 +84,16 @@ def main():
|
||||
|
||||
failList = loadFailList()
|
||||
|
||||
commandLine = [
|
||||
args.path,
|
||||
"--reporters=xml",
|
||||
"--fflags=true,DebugLuauDeferredConstraintResolution=true",
|
||||
]
|
||||
|
||||
print('>', ' '.join(commandLine), file=sys.stderr)
|
||||
|
||||
p = sp.Popen(
|
||||
[
|
||||
args.path,
|
||||
"--reporters=xml",
|
||||
"--fflags=true,DebugLuauDeferredConstraintResolution=true",
|
||||
],
|
||||
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(
|
||||
|
Loading…
Reference in New Issue
Block a user