mirror of
https://github.com/luau-lang/luau.git
synced 2024-12-12 13:00:38 +00:00
Sync to upstream/release/548 (#699)
- Fix rare type checking bugs with invalid generic types escaping the module scope - Fix type checking of variadic type packs in certain cases - Implement type normalization, which resolves a large set of various issues with unions/intersections in type checker - Improve parse errors for trailing commas in function calls and type lists - Reduce profiling skew when using --profile with very high frequencies - Improve performance of `lua_getinfo` (`debug.info`, `debug.traceback` and profiling overhead are now 20% faster/smaller) - Improve performance of polymorphic comparisons (1-2% lift on some benchmarks) - Improve performance of closure creation (1-2% lift on some benchmarks) - Improve string comparison performance (4% lift on string sorting)
This commit is contained in:
parent
cc26ef16df
commit
d5a2a1585e
65 changed files with 4001 additions and 481 deletions
|
@ -7,6 +7,7 @@
|
|||
#include "Luau/Constraint.h"
|
||||
#include "Luau/TypeVar.h"
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/Normalize.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
|
@ -44,6 +45,7 @@ struct ConstraintSolver
|
|||
TypeArena* arena;
|
||||
NotNull<SingletonTypes> singletonTypes;
|
||||
InternalErrorReporter iceReporter;
|
||||
NotNull<Normalizer> normalizer;
|
||||
// The entire set of constraints that the solver is trying to resolve.
|
||||
std::vector<NotNull<Constraint>> constraints;
|
||||
NotNull<Scope> rootScope;
|
||||
|
@ -74,9 +76,12 @@ struct ConstraintSolver
|
|||
|
||||
DcrLogger* logger;
|
||||
|
||||
explicit ConstraintSolver(TypeArena* arena, NotNull<SingletonTypes> singletonTypes, NotNull<Scope> rootScope, ModuleName moduleName,
|
||||
explicit ConstraintSolver(NotNull<Normalizer> normalizer, NotNull<Scope> rootScope, ModuleName moduleName,
|
||||
NotNull<ModuleResolver> moduleResolver, std::vector<RequireCycle> requireCycles, DcrLogger* logger);
|
||||
|
||||
// Randomize the order in which to dispatch constraints
|
||||
void randomize(unsigned seed);
|
||||
|
||||
/**
|
||||
* Attempts to dispatch all pending constraints and reach a type solution
|
||||
* that satisfies all of the constraints.
|
||||
|
@ -85,8 +90,9 @@ struct ConstraintSolver
|
|||
|
||||
bool done();
|
||||
|
||||
/** Attempt to dispatch a constraint. Returns true if it was successful.
|
||||
* If tryDispatch() returns false, the constraint remains in the unsolved set and will be retried later.
|
||||
/** Attempt to dispatch a constraint. Returns true if it was successful. If
|
||||
* tryDispatch() returns false, the constraint remains in the unsolved set
|
||||
* and will be retried later.
|
||||
*/
|
||||
bool tryDispatch(NotNull<const Constraint> c, bool force);
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ struct TypeMismatch
|
|||
TypeMismatch() = default;
|
||||
TypeMismatch(TypeId wantedType, TypeId givenType);
|
||||
TypeMismatch(TypeId wantedType, TypeId givenType, std::string reason);
|
||||
TypeMismatch(TypeId wantedType, TypeId givenType, std::string reason, TypeError error);
|
||||
TypeMismatch(TypeId wantedType, TypeId givenType, std::string reason, std::optional<TypeError> error);
|
||||
|
||||
TypeId wantedType = nullptr;
|
||||
TypeId givenType = nullptr;
|
||||
|
|
|
@ -83,8 +83,13 @@ struct FrontendOptions
|
|||
// is complete.
|
||||
bool retainFullTypeGraphs = false;
|
||||
|
||||
// Run typechecking only in mode required for autocomplete (strict mode in order to get more precise type information)
|
||||
// Run typechecking only in mode required for autocomplete (strict mode in
|
||||
// order to get more precise type information)
|
||||
bool forAutocomplete = false;
|
||||
|
||||
// If not empty, randomly shuffle the constraint set before attempting to
|
||||
// solve. Use this value to seed the random number generator.
|
||||
std::optional<unsigned> randomizeConstraintResolutionSeed;
|
||||
};
|
||||
|
||||
struct CheckResult
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Module.h"
|
||||
#include "Luau/NotNull.h"
|
||||
#include "Luau/TypeVar.h"
|
||||
#include "Luau/UnifierSharedState.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
|
@ -29,4 +29,231 @@ std::pair<TypePackId, bool> normalize(
|
|||
std::pair<TypePackId, bool> normalize(TypePackId ty, NotNull<Module> module, NotNull<SingletonTypes> singletonTypes, InternalErrorReporter& ice);
|
||||
std::pair<TypePackId, bool> normalize(TypePackId ty, const ModulePtr& module, NotNull<SingletonTypes> singletonTypes, InternalErrorReporter& ice);
|
||||
|
||||
class TypeIds
|
||||
{
|
||||
private:
|
||||
std::unordered_set<TypeId> types;
|
||||
std::vector<TypeId> order;
|
||||
std::size_t hash = 0;
|
||||
|
||||
public:
|
||||
using iterator = std::vector<TypeId>::iterator;
|
||||
using const_iterator = std::vector<TypeId>::const_iterator;
|
||||
|
||||
TypeIds(const TypeIds&) = delete;
|
||||
TypeIds(TypeIds&&) = default;
|
||||
TypeIds() = default;
|
||||
~TypeIds() = default;
|
||||
TypeIds& operator=(TypeIds&&) = default;
|
||||
|
||||
void insert(TypeId ty);
|
||||
/// Erase every element that does not also occur in tys
|
||||
void retain(const TypeIds& tys);
|
||||
void clear();
|
||||
|
||||
iterator begin();
|
||||
iterator end();
|
||||
const_iterator begin() const;
|
||||
const_iterator end() const;
|
||||
iterator erase(const_iterator it);
|
||||
|
||||
size_t size() const;
|
||||
bool empty() const;
|
||||
size_t count(TypeId ty) const;
|
||||
|
||||
template<class Iterator>
|
||||
void insert(Iterator begin, Iterator end)
|
||||
{
|
||||
for (Iterator it = begin; it != end; ++it)
|
||||
insert(*it);
|
||||
}
|
||||
|
||||
bool operator ==(const TypeIds& there) const;
|
||||
size_t getHash() const;
|
||||
};
|
||||
|
||||
} // namespace Luau
|
||||
|
||||
template<> struct std::hash<Luau::TypeIds>
|
||||
{
|
||||
std::size_t operator()(const Luau::TypeIds& tys) const
|
||||
{
|
||||
return tys.getHash();
|
||||
}
|
||||
};
|
||||
|
||||
template<> struct std::hash<const Luau::TypeIds*>
|
||||
{
|
||||
std::size_t operator()(const Luau::TypeIds* tys) const
|
||||
{
|
||||
return tys->getHash();
|
||||
}
|
||||
};
|
||||
|
||||
template<> struct std::equal_to<Luau::TypeIds>
|
||||
{
|
||||
bool operator()(const Luau::TypeIds& here, const Luau::TypeIds& there) const
|
||||
{
|
||||
return here == there;
|
||||
}
|
||||
};
|
||||
|
||||
template<> struct std::equal_to<const Luau::TypeIds*>
|
||||
{
|
||||
bool operator()(const Luau::TypeIds* here, const Luau::TypeIds* there) const
|
||||
{
|
||||
return *here == *there;
|
||||
}
|
||||
};
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
// A normalized string type is either `string` (represented by `nullopt`)
|
||||
// or a union of string singletons.
|
||||
using NormalizedStringType = std::optional<std::map<std::string, TypeId>>;
|
||||
|
||||
// A normalized function type is either `never` (represented by `nullopt`)
|
||||
// or an intersection of function types.
|
||||
// NOTE: type normalization can fail on function types with generics
|
||||
// (e.g. because we do not support unions and intersections of generic type packs),
|
||||
// so this type may contain `error`.
|
||||
using NormalizedFunctionType = std::optional<TypeIds>;
|
||||
|
||||
// A normalized generic/free type is a union, where each option is of the form (X & T) where
|
||||
// * X is either a free type or a generic
|
||||
// * T is a normalized type.
|
||||
struct NormalizedType;
|
||||
using NormalizedTyvars = std::unordered_map<TypeId, std::unique_ptr<NormalizedType>>;
|
||||
|
||||
// A normalized type is either any, unknown, or one of the form P | T | F | G where
|
||||
// * P is a union of primitive types (including singletons, classes and the error type)
|
||||
// * T is a union of table types
|
||||
// * F is a union of an intersection of function types
|
||||
// * G is a union of generic/free normalized types, intersected with a normalized type
|
||||
struct NormalizedType
|
||||
{
|
||||
// The top part of the type.
|
||||
// This type is either never, unknown, or any.
|
||||
// If this type is not never, all the other fields are null.
|
||||
TypeId tops;
|
||||
|
||||
// The boolean part of the type.
|
||||
// This type is either never, boolean type, or a boolean singleton.
|
||||
TypeId booleans;
|
||||
|
||||
// The class part of the type.
|
||||
// Each element of this set is a class, and none of the classes are subclasses of each other.
|
||||
TypeIds classes;
|
||||
|
||||
// The error part of the type.
|
||||
// This type is either never or the error type.
|
||||
TypeId errors;
|
||||
|
||||
// The nil part of the type.
|
||||
// This type is either never or nil.
|
||||
TypeId nils;
|
||||
|
||||
// The number part of the type.
|
||||
// This type is either never or number.
|
||||
TypeId numbers;
|
||||
|
||||
// The string part of the type.
|
||||
// This may be the `string` type, or a union of singletons.
|
||||
NormalizedStringType strings = std::map<std::string,TypeId>{};
|
||||
|
||||
// The thread part of the type.
|
||||
// This type is either never or thread.
|
||||
TypeId threads;
|
||||
|
||||
// The (meta)table part of the type.
|
||||
// Each element of this set is a (meta)table type.
|
||||
TypeIds tables;
|
||||
|
||||
// The function part of the type.
|
||||
NormalizedFunctionType functions;
|
||||
|
||||
// The generic/free part of the type.
|
||||
NormalizedTyvars tyvars;
|
||||
|
||||
NormalizedType(NotNull<SingletonTypes> singletonTypes);
|
||||
|
||||
NormalizedType(const NormalizedType&) = delete;
|
||||
NormalizedType(NormalizedType&&) = default;
|
||||
NormalizedType() = delete;
|
||||
~NormalizedType() = default;
|
||||
NormalizedType& operator=(NormalizedType&&) = default;
|
||||
NormalizedType& operator=(NormalizedType&) = delete;
|
||||
};
|
||||
|
||||
class Normalizer
|
||||
{
|
||||
std::unordered_map<TypeId, std::unique_ptr<NormalizedType>> cachedNormals;
|
||||
std::unordered_map<const TypeIds*, TypeId> cachedIntersections;
|
||||
std::unordered_map<const TypeIds*, TypeId> cachedUnions;
|
||||
std::unordered_map<const TypeIds*, std::unique_ptr<TypeIds>> cachedTypeIds;
|
||||
bool withinResourceLimits();
|
||||
|
||||
public:
|
||||
TypeArena* arena;
|
||||
NotNull<SingletonTypes> singletonTypes;
|
||||
NotNull<UnifierSharedState> sharedState;
|
||||
|
||||
Normalizer(TypeArena* arena, NotNull<SingletonTypes> singletonTypes, NotNull<UnifierSharedState> sharedState);
|
||||
Normalizer(const Normalizer&) = delete;
|
||||
Normalizer(Normalizer&&) = delete;
|
||||
Normalizer() = delete;
|
||||
~Normalizer() = default;
|
||||
Normalizer& operator=(Normalizer&&) = delete;
|
||||
Normalizer& operator=(Normalizer&) = delete;
|
||||
|
||||
// If this returns null, the typechecker should emit a "too complex" error
|
||||
const NormalizedType* normalize(TypeId ty);
|
||||
void clearNormal(NormalizedType& norm);
|
||||
|
||||
// ------- Cached TypeIds
|
||||
TypeId unionType(TypeId here, TypeId there);
|
||||
TypeId intersectionType(TypeId here, TypeId there);
|
||||
const TypeIds* cacheTypeIds(TypeIds tys);
|
||||
void clearCaches();
|
||||
|
||||
// ------- Normalizing unions
|
||||
void unionTysWithTy(TypeIds& here, TypeId there);
|
||||
TypeId unionOfTops(TypeId here, TypeId there);
|
||||
TypeId unionOfBools(TypeId here, TypeId there);
|
||||
void unionClassesWithClass(TypeIds& heres, TypeId there);
|
||||
void unionClasses(TypeIds& heres, const TypeIds& theres);
|
||||
void unionStrings(NormalizedStringType& here, const NormalizedStringType& there);
|
||||
std::optional<TypePackId> unionOfTypePacks(TypePackId here, TypePackId there);
|
||||
std::optional<TypeId> unionOfFunctions(TypeId here, TypeId there);
|
||||
std::optional<TypeId> unionSaturatedFunctions(TypeId here, TypeId there);
|
||||
void unionFunctionsWithFunction(NormalizedFunctionType& heress, TypeId there);
|
||||
void unionFunctions(NormalizedFunctionType& heress, const NormalizedFunctionType& theress);
|
||||
void unionTablesWithTable(TypeIds& heres, TypeId there);
|
||||
void unionTables(TypeIds& heres, const TypeIds& theres);
|
||||
bool unionNormals(NormalizedType& here, const NormalizedType& there, int ignoreSmallerTyvars = -1);
|
||||
bool unionNormalWithTy(NormalizedType& here, TypeId there, int ignoreSmallerTyvars = -1);
|
||||
|
||||
// ------- Normalizing intersections
|
||||
void intersectTysWithTy(TypeIds& here, TypeId there);
|
||||
TypeId intersectionOfTops(TypeId here, TypeId there);
|
||||
TypeId intersectionOfBools(TypeId here, TypeId there);
|
||||
void intersectClasses(TypeIds& heres, const TypeIds& theres);
|
||||
void intersectClassesWithClass(TypeIds& heres, TypeId there);
|
||||
void intersectStrings(NormalizedStringType& here, const NormalizedStringType& there);
|
||||
std::optional<TypePackId> intersectionOfTypePacks(TypePackId here, TypePackId there);
|
||||
std::optional<TypeId> intersectionOfTables(TypeId here, TypeId there);
|
||||
void intersectTablesWithTable(TypeIds& heres, TypeId there);
|
||||
void intersectTables(TypeIds& heres, const TypeIds& theres);
|
||||
std::optional<TypeId> intersectionOfFunctions(TypeId here, TypeId there);
|
||||
void intersectFunctionsWithFunction(NormalizedFunctionType& heress, TypeId there);
|
||||
void intersectFunctions(NormalizedFunctionType& heress, const NormalizedFunctionType& theress);
|
||||
bool intersectTyvarsWithTy(NormalizedTyvars& here, TypeId there);
|
||||
bool intersectNormals(NormalizedType& here, const NormalizedType& there, int ignoreSmallerTyvars = -1);
|
||||
bool intersectNormalWithTy(NormalizedType& here, TypeId there);
|
||||
|
||||
// -------- Convert back from a normalized type to a type
|
||||
TypeId typeFromNormal(const NormalizedType& norm);
|
||||
};
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -234,6 +234,8 @@ public:
|
|||
TypeId anyify(const ScopePtr& scope, TypeId ty, Location location);
|
||||
TypePackId anyify(const ScopePtr& scope, TypePackId ty, Location location);
|
||||
|
||||
TypePackId anyifyModuleReturnTypePackGenerics(TypePackId ty);
|
||||
|
||||
void reportError(const TypeError& error);
|
||||
void reportError(const Location& location, TypeErrorData error);
|
||||
void reportErrors(const ErrorVec& errors);
|
||||
|
@ -359,6 +361,7 @@ public:
|
|||
InternalErrorReporter* iceHandler;
|
||||
|
||||
UnifierSharedState unifierState;
|
||||
Normalizer normalizer;
|
||||
|
||||
std::vector<RequireCycle> requireCycles;
|
||||
|
||||
|
|
|
@ -96,7 +96,7 @@ struct Free
|
|||
bool forwardedTypeAlias = false;
|
||||
|
||||
private:
|
||||
static int nextIndex;
|
||||
static int DEPRECATED_nextIndex;
|
||||
};
|
||||
|
||||
template<typename Id>
|
||||
|
@ -127,7 +127,7 @@ struct Generic
|
|||
bool explicitName = false;
|
||||
|
||||
private:
|
||||
static int nextIndex;
|
||||
static int DEPRECATED_nextIndex;
|
||||
};
|
||||
|
||||
struct Error
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include "Luau/TxnLog.h"
|
||||
#include "Luau/TypeArena.h"
|
||||
#include "Luau/UnifierSharedState.h"
|
||||
#include "Normalize.h"
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
|
@ -52,6 +53,7 @@ struct Unifier
|
|||
{
|
||||
TypeArena* const types;
|
||||
NotNull<SingletonTypes> singletonTypes;
|
||||
NotNull<Normalizer> normalizer;
|
||||
Mode mode;
|
||||
|
||||
NotNull<Scope> scope; // const Scope maybe
|
||||
|
@ -60,13 +62,14 @@ struct Unifier
|
|||
Location location;
|
||||
Variance variance = Covariant;
|
||||
bool anyIsTop = false; // If true, we consider any to be a top type. If false, it is a familiar but weird mix of top and bottom all at once.
|
||||
bool normalize; // Normalize unions and intersections if necessary
|
||||
bool useScopes = false; // If true, we use the scope hierarchy rather than TypeLevels
|
||||
CountMismatch::Context ctx = CountMismatch::Arg;
|
||||
|
||||
UnifierSharedState& sharedState;
|
||||
|
||||
Unifier(TypeArena* types, NotNull<SingletonTypes> singletonTypes, Mode mode, NotNull<Scope> scope, const Location& location, Variance variance,
|
||||
UnifierSharedState& sharedState, TxnLog* parentLog = nullptr);
|
||||
Unifier(NotNull<Normalizer> normalizer, Mode mode, NotNull<Scope> scope, const Location& location, Variance variance,
|
||||
TxnLog* parentLog = nullptr);
|
||||
|
||||
// Test whether the two type vars unify. Never commits the result.
|
||||
ErrorVec canUnify(TypeId subTy, TypeId superTy);
|
||||
|
@ -84,6 +87,7 @@ private:
|
|||
void tryUnifyTypeWithUnion(TypeId subTy, TypeId superTy, const UnionTypeVar* uv, bool cacheEnabled, bool isFunctionCall);
|
||||
void tryUnifyTypeWithIntersection(TypeId subTy, TypeId superTy, const IntersectionTypeVar* uv);
|
||||
void tryUnifyIntersectionWithType(TypeId subTy, const IntersectionTypeVar* uv, TypeId superTy, bool cacheEnabled, bool isFunctionCall);
|
||||
void tryUnifyNormalizedTypes(TypeId subTy, TypeId superTy, const NormalizedType& subNorm, const NormalizedType& superNorm, std::string reason, std::optional<TypeError> error = std::nullopt);
|
||||
void tryUnifyPrimitives(TypeId subTy, TypeId superTy);
|
||||
void tryUnifySingletons(TypeId subTy, TypeId superTy);
|
||||
void tryUnifyFunctions(TypeId subTy, TypeId superTy, bool isFunctionCall = false);
|
||||
|
@ -92,6 +96,8 @@ private:
|
|||
void tryUnifyWithMetatable(TypeId subTy, TypeId superTy, bool reversed);
|
||||
void tryUnifyWithClass(TypeId subTy, TypeId superTy, bool reversed);
|
||||
|
||||
TypePackId tryApplyOverloadedFunction(TypeId function, const NormalizedFunctionType& overloads, TypePackId args);
|
||||
|
||||
TypeId widen(TypeId ty);
|
||||
TypePackId widen(TypePackId tp);
|
||||
|
||||
|
|
|
@ -12,8 +12,6 @@
|
|||
#include <unordered_set>
|
||||
#include <utility>
|
||||
|
||||
LUAU_FASTFLAG(LuauSelfCallAutocompleteFix3)
|
||||
|
||||
static const std::unordered_set<std::string> kStatementStartingKeywords = {
|
||||
"while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"};
|
||||
|
||||
|
@ -139,7 +137,8 @@ static bool checkTypeMatch(TypeId subTy, TypeId superTy, NotNull<Scope> scope, T
|
|||
{
|
||||
InternalErrorReporter iceReporter;
|
||||
UnifierSharedState unifierState(&iceReporter);
|
||||
Unifier unifier(typeArena, singletonTypes, Mode::Strict, scope, Location(), Variance::Covariant, unifierState);
|
||||
Normalizer normalizer{typeArena, singletonTypes, NotNull{&unifierState}};
|
||||
Unifier unifier(NotNull<Normalizer>{&normalizer}, Mode::Strict, scope, Location(), Variance::Covariant);
|
||||
|
||||
return unifier.canUnify(subTy, superTy).empty();
|
||||
}
|
||||
|
@ -151,18 +150,6 @@ static TypeCorrectKind checkTypeCorrectKind(
|
|||
|
||||
NotNull<Scope> moduleScope{module.getModuleScope().get()};
|
||||
|
||||
auto canUnify = [&typeArena, singletonTypes, moduleScope](TypeId subTy, TypeId superTy) {
|
||||
LUAU_ASSERT(!FFlag::LuauSelfCallAutocompleteFix3);
|
||||
|
||||
InternalErrorReporter iceReporter;
|
||||
UnifierSharedState unifierState(&iceReporter);
|
||||
Unifier unifier(typeArena, singletonTypes, Mode::Strict, moduleScope, Location(), Variance::Covariant, unifierState);
|
||||
|
||||
unifier.tryUnify(subTy, superTy);
|
||||
bool ok = unifier.errors.empty();
|
||||
return ok;
|
||||
};
|
||||
|
||||
auto typeAtPosition = findExpectedTypeAt(module, node, position);
|
||||
|
||||
if (!typeAtPosition)
|
||||
|
@ -170,30 +157,11 @@ static TypeCorrectKind checkTypeCorrectKind(
|
|||
|
||||
TypeId expectedType = follow(*typeAtPosition);
|
||||
|
||||
auto checkFunctionType = [typeArena, singletonTypes, moduleScope, &canUnify, &expectedType](const FunctionTypeVar* ftv) {
|
||||
if (FFlag::LuauSelfCallAutocompleteFix3)
|
||||
{
|
||||
if (std::optional<TypeId> firstRetTy = first(ftv->retTypes))
|
||||
return checkTypeMatch(*firstRetTy, expectedType, moduleScope, typeArena, singletonTypes);
|
||||
auto checkFunctionType = [typeArena, singletonTypes, moduleScope, &expectedType](const FunctionTypeVar* ftv) {
|
||||
if (std::optional<TypeId> firstRetTy = first(ftv->retTypes))
|
||||
return checkTypeMatch(*firstRetTy, expectedType, moduleScope, typeArena, singletonTypes);
|
||||
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto [retHead, retTail] = flatten(ftv->retTypes);
|
||||
|
||||
if (!retHead.empty() && canUnify(retHead.front(), expectedType))
|
||||
return true;
|
||||
|
||||
// We might only have a variadic tail pack, check if the element is compatible
|
||||
if (retTail)
|
||||
{
|
||||
if (const VariadicTypePack* vtp = get<VariadicTypePack>(follow(*retTail)); vtp && canUnify(vtp->ty, expectedType))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// We also want to suggest functions that return compatible result
|
||||
|
@ -212,11 +180,8 @@ static TypeCorrectKind checkTypeCorrectKind(
|
|||
}
|
||||
}
|
||||
|
||||
if (FFlag::LuauSelfCallAutocompleteFix3)
|
||||
return checkTypeMatch(ty, expectedType, NotNull{module.getModuleScope().get()}, typeArena, singletonTypes) ? TypeCorrectKind::Correct
|
||||
: TypeCorrectKind::None;
|
||||
else
|
||||
return canUnify(ty, expectedType) ? TypeCorrectKind::Correct : TypeCorrectKind::None;
|
||||
return checkTypeMatch(ty, expectedType, NotNull{module.getModuleScope().get()}, typeArena, singletonTypes) ? TypeCorrectKind::Correct
|
||||
: TypeCorrectKind::None;
|
||||
}
|
||||
|
||||
enum class PropIndexType
|
||||
|
@ -230,51 +195,14 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, NotNul
|
|||
PropIndexType indexType, const std::vector<AstNode*>& nodes, AutocompleteEntryMap& result, std::unordered_set<TypeId>& seen,
|
||||
std::optional<const ClassTypeVar*> containingClass = std::nullopt)
|
||||
{
|
||||
if (FFlag::LuauSelfCallAutocompleteFix3)
|
||||
rootTy = follow(rootTy);
|
||||
|
||||
rootTy = follow(rootTy);
|
||||
ty = follow(ty);
|
||||
|
||||
if (seen.count(ty))
|
||||
return;
|
||||
seen.insert(ty);
|
||||
|
||||
auto isWrongIndexer_DEPRECATED = [indexType, useStrictFunctionIndexers = !!get<ClassTypeVar>(ty)](Luau::TypeId type) {
|
||||
LUAU_ASSERT(!FFlag::LuauSelfCallAutocompleteFix3);
|
||||
|
||||
if (indexType == PropIndexType::Key)
|
||||
return false;
|
||||
|
||||
bool colonIndex = indexType == PropIndexType::Colon;
|
||||
|
||||
if (const FunctionTypeVar* ftv = get<FunctionTypeVar>(type))
|
||||
{
|
||||
return useStrictFunctionIndexers ? colonIndex != ftv->hasSelf : false;
|
||||
}
|
||||
else if (const IntersectionTypeVar* itv = get<IntersectionTypeVar>(type))
|
||||
{
|
||||
bool allHaveSelf = true;
|
||||
for (auto subType : itv->parts)
|
||||
{
|
||||
if (const FunctionTypeVar* ftv = get<FunctionTypeVar>(Luau::follow(subType)))
|
||||
{
|
||||
allHaveSelf &= ftv->hasSelf;
|
||||
}
|
||||
else
|
||||
{
|
||||
return colonIndex;
|
||||
}
|
||||
}
|
||||
return useStrictFunctionIndexers ? colonIndex != allHaveSelf : false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return colonIndex;
|
||||
}
|
||||
};
|
||||
auto isWrongIndexer = [typeArena, singletonTypes, &module, rootTy, indexType](Luau::TypeId type) {
|
||||
LUAU_ASSERT(FFlag::LuauSelfCallAutocompleteFix3);
|
||||
|
||||
if (indexType == PropIndexType::Key)
|
||||
return false;
|
||||
|
||||
|
@ -337,7 +265,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, NotNul
|
|||
AutocompleteEntryKind::Property,
|
||||
type,
|
||||
prop.deprecated,
|
||||
FFlag::LuauSelfCallAutocompleteFix3 ? isWrongIndexer(type) : isWrongIndexer_DEPRECATED(type),
|
||||
isWrongIndexer(type),
|
||||
typeCorrect,
|
||||
containingClass,
|
||||
&prop,
|
||||
|
@ -380,31 +308,8 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, NotNul
|
|||
{
|
||||
autocompleteProps(module, typeArena, singletonTypes, rootTy, mt->table, indexType, nodes, result, seen);
|
||||
|
||||
if (FFlag::LuauSelfCallAutocompleteFix3)
|
||||
{
|
||||
if (auto mtable = get<TableTypeVar>(mt->metatable))
|
||||
fillMetatableProps(mtable);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto mtable = get<TableTypeVar>(mt->metatable);
|
||||
if (!mtable)
|
||||
return;
|
||||
|
||||
auto indexIt = mtable->props.find("__index");
|
||||
if (indexIt != mtable->props.end())
|
||||
{
|
||||
TypeId followed = follow(indexIt->second.type);
|
||||
if (get<TableTypeVar>(followed) || get<MetatableTypeVar>(followed))
|
||||
autocompleteProps(module, typeArena, singletonTypes, rootTy, followed, indexType, nodes, result, seen);
|
||||
else if (auto indexFunction = get<FunctionTypeVar>(followed))
|
||||
{
|
||||
std::optional<TypeId> indexFunctionResult = first(indexFunction->retTypes);
|
||||
if (indexFunctionResult)
|
||||
autocompleteProps(module, typeArena, singletonTypes, rootTy, *indexFunctionResult, indexType, nodes, result, seen);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (auto mtable = get<TableTypeVar>(mt->metatable))
|
||||
fillMetatableProps(mtable);
|
||||
}
|
||||
else if (auto i = get<IntersectionTypeVar>(ty))
|
||||
{
|
||||
|
@ -446,9 +351,6 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, NotNul
|
|||
AutocompleteEntryMap inner;
|
||||
std::unordered_set<TypeId> innerSeen;
|
||||
|
||||
if (!FFlag::LuauSelfCallAutocompleteFix3)
|
||||
innerSeen = seen;
|
||||
|
||||
if (isNil(*iter))
|
||||
{
|
||||
++iter;
|
||||
|
@ -472,7 +374,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, NotNul
|
|||
++iter;
|
||||
}
|
||||
}
|
||||
else if (auto pt = get<PrimitiveTypeVar>(ty); pt && FFlag::LuauSelfCallAutocompleteFix3)
|
||||
else if (auto pt = get<PrimitiveTypeVar>(ty))
|
||||
{
|
||||
if (pt->metatable)
|
||||
{
|
||||
|
@ -480,7 +382,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, NotNul
|
|||
fillMetatableProps(mtable);
|
||||
}
|
||||
}
|
||||
else if (FFlag::LuauSelfCallAutocompleteFix3 && get<StringSingleton>(get<SingletonTypeVar>(ty)))
|
||||
else if (get<StringSingleton>(get<SingletonTypeVar>(ty)))
|
||||
{
|
||||
autocompleteProps(module, typeArena, singletonTypes, rootTy, singletonTypes->stringType, indexType, nodes, result, seen);
|
||||
}
|
||||
|
@ -1416,11 +1318,7 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
|
|||
TypeId ty = follow(*it);
|
||||
PropIndexType indexType = indexName->op == ':' ? PropIndexType::Colon : PropIndexType::Point;
|
||||
|
||||
if (!FFlag::LuauSelfCallAutocompleteFix3 && isString(ty))
|
||||
return {autocompleteProps(*module, &typeArena, singletonTypes, globalScope->bindings[AstName{"string"}].typeId, indexType, ancestry),
|
||||
ancestry, AutocompleteContext::Property};
|
||||
else
|
||||
return {autocompleteProps(*module, &typeArena, singletonTypes, ty, indexType, ancestry), ancestry, AutocompleteContext::Property};
|
||||
return {autocompleteProps(*module, &typeArena, singletonTypes, ty, indexType, ancestry), ancestry, AutocompleteContext::Property};
|
||||
}
|
||||
else if (auto typeReference = node->as<AstTypeReference>())
|
||||
{
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
#include "Luau/VisitTypeVar.h"
|
||||
#include "Luau/TypeUtils.h"
|
||||
|
||||
#include <random>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolver, false);
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false);
|
||||
LUAU_FASTFLAG(LuauFixNameMaps)
|
||||
|
@ -251,10 +253,11 @@ void dump(ConstraintSolver* cs, ToStringOptions& opts)
|
|||
}
|
||||
}
|
||||
|
||||
ConstraintSolver::ConstraintSolver(TypeArena* arena, NotNull<SingletonTypes> singletonTypes, NotNull<Scope> rootScope, ModuleName moduleName,
|
||||
ConstraintSolver::ConstraintSolver(NotNull<Normalizer> normalizer, NotNull<Scope> rootScope, ModuleName moduleName,
|
||||
NotNull<ModuleResolver> moduleResolver, std::vector<RequireCycle> requireCycles, DcrLogger* logger)
|
||||
: arena(arena)
|
||||
, singletonTypes(singletonTypes)
|
||||
: arena(normalizer->arena)
|
||||
, singletonTypes(normalizer->singletonTypes)
|
||||
, normalizer(normalizer)
|
||||
, constraints(collectConstraints(rootScope))
|
||||
, rootScope(rootScope)
|
||||
, currentModuleName(std::move(moduleName))
|
||||
|
@ -278,6 +281,12 @@ ConstraintSolver::ConstraintSolver(TypeArena* arena, NotNull<SingletonTypes> sin
|
|||
LUAU_ASSERT(logger);
|
||||
}
|
||||
|
||||
void ConstraintSolver::randomize(unsigned seed)
|
||||
{
|
||||
std::mt19937 g(seed);
|
||||
std::shuffle(begin(unsolvedConstraints), end(unsolvedConstraints), g);
|
||||
}
|
||||
|
||||
void ConstraintSolver::run()
|
||||
{
|
||||
if (done())
|
||||
|
@ -1355,8 +1364,7 @@ bool ConstraintSolver::isBlocked(NotNull<const Constraint> constraint)
|
|||
|
||||
void ConstraintSolver::unify(TypeId subType, TypeId superType, NotNull<Scope> scope)
|
||||
{
|
||||
UnifierSharedState sharedState{&iceReporter};
|
||||
Unifier u{arena, singletonTypes, Mode::Strict, scope, Location{}, Covariant, sharedState};
|
||||
Unifier u{normalizer, Mode::Strict, scope, Location{}, Covariant};
|
||||
u.useScopes = true;
|
||||
|
||||
u.tryUnify(subType, superType);
|
||||
|
@ -1379,7 +1387,7 @@ void ConstraintSolver::unify(TypeId subType, TypeId superType, NotNull<Scope> sc
|
|||
void ConstraintSolver::unify(TypePackId subPack, TypePackId superPack, NotNull<Scope> scope)
|
||||
{
|
||||
UnifierSharedState sharedState{&iceReporter};
|
||||
Unifier u{arena, singletonTypes, Mode::Strict, scope, Location{}, Covariant, sharedState};
|
||||
Unifier u{normalizer, Mode::Strict, scope, Location{}, Covariant};
|
||||
u.useScopes = true;
|
||||
|
||||
u.tryUnify(subPack, superPack);
|
||||
|
|
|
@ -511,11 +511,11 @@ TypeMismatch::TypeMismatch(TypeId wantedType, TypeId givenType, std::string reas
|
|||
{
|
||||
}
|
||||
|
||||
TypeMismatch::TypeMismatch(TypeId wantedType, TypeId givenType, std::string reason, TypeError error)
|
||||
TypeMismatch::TypeMismatch(TypeId wantedType, TypeId givenType, std::string reason, std::optional<TypeError> error)
|
||||
: wantedType(wantedType)
|
||||
, givenType(givenType)
|
||||
, reason(reason)
|
||||
, error(std::make_shared<TypeError>(std::move(error)))
|
||||
, error(error ? std::make_shared<TypeError>(std::move(*error)) : nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -860,12 +860,18 @@ ModulePtr Frontend::check(
|
|||
const NotNull<ModuleResolver> mr{forAutocomplete ? &moduleResolverForAutocomplete : &moduleResolver};
|
||||
const ScopePtr& globalScope{forAutocomplete ? typeCheckerForAutocomplete.globalScope : typeChecker.globalScope};
|
||||
|
||||
Normalizer normalizer{&result->internalTypes, singletonTypes, NotNull{&typeChecker.unifierState}};
|
||||
|
||||
ConstraintGraphBuilder cgb{
|
||||
sourceModule.name, result, &result->internalTypes, mr, singletonTypes, NotNull(&iceHandler), globalScope, logger.get()};
|
||||
cgb.visit(sourceModule.root);
|
||||
result->errors = std::move(cgb.errors);
|
||||
|
||||
ConstraintSolver cs{&result->internalTypes, singletonTypes, NotNull(cgb.rootScope), sourceModule.name, NotNull(&moduleResolver), requireCycles, logger.get()};
|
||||
ConstraintSolver cs{NotNull{&normalizer}, NotNull(cgb.rootScope), sourceModule.name, NotNull(&moduleResolver), requireCycles, logger.get()};
|
||||
|
||||
if (options.randomizeConstraintResolutionSeed)
|
||||
cs.randomize(*options.randomizeConstraintResolutionSeed);
|
||||
|
||||
cs.run();
|
||||
|
||||
for (TypeError& e : cs.errors)
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
#include <algorithm>
|
||||
|
||||
LUAU_FASTFLAG(LuauAnyifyModuleReturnGenerics)
|
||||
LUAU_FASTFLAG(LuauLowerBoundsCalculation);
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
LUAU_FASTFLAGVARIABLE(LuauForceExportSurfacesToBeNormal, false);
|
||||
|
@ -285,13 +286,16 @@ void Module::clonePublicInterface(NotNull<SingletonTypes> singletonTypes, Intern
|
|||
}
|
||||
}
|
||||
|
||||
for (TypeId ty : returnType)
|
||||
if (!FFlag::LuauAnyifyModuleReturnGenerics)
|
||||
{
|
||||
if (get<GenericTypeVar>(follow(ty)))
|
||||
for (TypeId ty : returnType)
|
||||
{
|
||||
auto t = asMutable(ty);
|
||||
t->ty = AnyTypeVar{};
|
||||
t->normal = true;
|
||||
if (get<GenericTypeVar>(follow(ty)))
|
||||
{
|
||||
auto t = asMutable(ty);
|
||||
t->ty = AnyTypeVar{};
|
||||
t->normal = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -280,7 +280,8 @@ struct TypeChecker2
|
|||
TypePackId actualRetType = reconstructPack(ret->list, arena);
|
||||
|
||||
UnifierSharedState sharedState{&ice};
|
||||
Unifier u{&arena, singletonTypes, Mode::Strict, stack.back(), ret->location, Covariant, sharedState};
|
||||
Normalizer normalizer{&arena, singletonTypes, NotNull{&sharedState}};
|
||||
Unifier u{NotNull{&normalizer}, Mode::Strict, stack.back(), ret->location, Covariant};
|
||||
u.anyIsTop = true;
|
||||
|
||||
u.tryUnify(actualRetType, expectedRetType);
|
||||
|
@ -1206,7 +1207,8 @@ struct TypeChecker2
|
|||
ErrorVec tryUnify(NotNull<Scope> scope, const Location& location, TID subTy, TID superTy)
|
||||
{
|
||||
UnifierSharedState sharedState{&ice};
|
||||
Unifier u{&module->internalTypes, singletonTypes, Mode::Strict, scope, location, Covariant, sharedState};
|
||||
Normalizer normalizer{&module->internalTypes, singletonTypes, NotNull{&sharedState}};
|
||||
Unifier u{NotNull{&normalizer}, Mode::Strict, scope, location, Covariant};
|
||||
u.anyIsTop = true;
|
||||
u.tryUnify(subTy, superTy);
|
||||
|
||||
|
|
|
@ -32,19 +32,21 @@ LUAU_FASTINTVARIABLE(LuauCheckRecursionLimit, 300)
|
|||
LUAU_FASTINTVARIABLE(LuauVisitRecursionLimit, 500)
|
||||
LUAU_FASTFLAG(LuauKnowsTheDataModel3)
|
||||
LUAU_FASTFLAG(LuauAutocompleteDynamicLimits)
|
||||
LUAU_FASTFLAG(LuauTypeNormalization2)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFunctionArgMismatchDetails, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauInplaceDemoteSkipAllBound, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauLowerBoundsCalculation, false)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSelfCallAutocompleteFix3, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauReturnAnyInsteadOfICE, false) // Eventually removed as false.
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauSharedSelf, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAnyifyModuleReturnGenerics, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUnknownAndNeverType, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCallUnifyPackTails, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCheckGenericHOFTypes, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauBinaryNeedsExpectedTypesToo, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFixVarargExprHeadType, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNeverTypesAndOperatorsInference, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauReturnsFromCallsitesAreNotWidened, false)
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCompleteVisitor, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUnionOfTypesFollow, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauReportShadowedTypeAlias, false)
|
||||
|
@ -255,6 +257,7 @@ TypeChecker::TypeChecker(ModuleResolver* resolver, NotNull<SingletonTypes> singl
|
|||
, singletonTypes(singletonTypes)
|
||||
, iceHandler(iceHandler)
|
||||
, unifierState(iceHandler)
|
||||
, normalizer(nullptr, singletonTypes, NotNull{&unifierState})
|
||||
, nilType(singletonTypes->nilType)
|
||||
, numberType(singletonTypes->numberType)
|
||||
, stringType(singletonTypes->stringType)
|
||||
|
@ -301,12 +304,13 @@ ModulePtr TypeChecker::checkWithoutRecursionCheck(const SourceModule& module, Mo
|
|||
LUAU_TIMETRACE_SCOPE("TypeChecker::check", "TypeChecker");
|
||||
LUAU_TIMETRACE_ARGUMENT("module", module.name.c_str());
|
||||
|
||||
currentModule.reset(new Module());
|
||||
currentModule.reset(new Module);
|
||||
currentModule->type = module.type;
|
||||
currentModule->allocator = module.allocator;
|
||||
currentModule->names = module.names;
|
||||
|
||||
iceHandler->moduleName = module.name;
|
||||
normalizer.arena = ¤tModule->internalTypes;
|
||||
|
||||
if (FFlag::LuauAutocompleteDynamicLimits)
|
||||
{
|
||||
|
@ -351,15 +355,23 @@ ModulePtr TypeChecker::checkWithoutRecursionCheck(const SourceModule& module, Mo
|
|||
if (get<FreeTypePack>(follow(moduleScope->returnType)))
|
||||
moduleScope->returnType = addTypePack(TypePack{{}, std::nullopt});
|
||||
else
|
||||
{
|
||||
moduleScope->returnType = anyify(moduleScope, moduleScope->returnType, Location{});
|
||||
}
|
||||
|
||||
if (FFlag::LuauAnyifyModuleReturnGenerics)
|
||||
moduleScope->returnType = anyifyModuleReturnTypePackGenerics(moduleScope->returnType);
|
||||
|
||||
for (auto& [_, typeFun] : moduleScope->exportedTypeBindings)
|
||||
typeFun.type = anyify(moduleScope, typeFun.type, Location{});
|
||||
|
||||
prepareErrorsForDisplay(currentModule->errors);
|
||||
|
||||
if (FFlag::LuauTypeNormalization2)
|
||||
{
|
||||
// Clear the normalizer caches, since they contain types from the internal type surface
|
||||
normalizer.clearCaches();
|
||||
normalizer.arena = nullptr;
|
||||
}
|
||||
|
||||
currentModule->clonePublicInterface(singletonTypes, *iceHandler);
|
||||
|
||||
// Clear unifier cache since it's keyed off internal types that get deallocated
|
||||
|
@ -474,7 +486,7 @@ struct InplaceDemoter : TypeVarOnceVisitor
|
|||
TypeArena* arena;
|
||||
|
||||
InplaceDemoter(TypeLevel level, TypeArena* arena)
|
||||
: TypeVarOnceVisitor(/* skipBoundTypes= */ FFlag::LuauInplaceDemoteSkipAllBound)
|
||||
: TypeVarOnceVisitor(/* skipBoundTypes= */ true)
|
||||
, newLevel(level)
|
||||
, arena(arena)
|
||||
{
|
||||
|
@ -494,12 +506,6 @@ struct InplaceDemoter : TypeVarOnceVisitor
|
|||
return false;
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const BoundTypeVar& btyRef) override
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::LuauInplaceDemoteSkipAllBound);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool visit(TypeId ty) override
|
||||
{
|
||||
if (ty->owningArena != arena)
|
||||
|
@ -1029,8 +1035,11 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatAssign& assign)
|
|||
|
||||
if (right)
|
||||
{
|
||||
if (!maybeGeneric(left) && isGeneric(right))
|
||||
right = instantiate(scope, right, loc);
|
||||
if (!FFlag::LuauInstantiateInSubtyping)
|
||||
{
|
||||
if (!maybeGeneric(left) && isGeneric(right))
|
||||
right = instantiate(scope, right, loc);
|
||||
}
|
||||
|
||||
// Setting a table entry to nil doesn't mean nil is the type of the indexer, it is just deleting the entry
|
||||
const TableTypeVar* destTableTypeReceivingNil = nullptr;
|
||||
|
@ -1104,7 +1113,9 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatLocal& local)
|
|||
variableTypes.push_back(ty);
|
||||
expectedTypes.push_back(ty);
|
||||
|
||||
instantiateGenerics.push_back(annotation != nullptr && !maybeGeneric(ty));
|
||||
// with FFlag::LuauInstantiateInSubtyping enabled, we shouldn't need to produce instantiateGenerics at all.
|
||||
if (!FFlag::LuauInstantiateInSubtyping)
|
||||
instantiateGenerics.push_back(annotation != nullptr && !maybeGeneric(ty));
|
||||
}
|
||||
|
||||
if (local.values.size > 0)
|
||||
|
@ -1729,9 +1740,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatDeclareClass& declar
|
|||
{
|
||||
ftv->argNames.insert(ftv->argNames.begin(), FunctionArgument{"self", {}});
|
||||
ftv->argTypes = addTypePack(TypePack{{classTy}, ftv->argTypes});
|
||||
|
||||
if (FFlag::LuauSelfCallAutocompleteFix3)
|
||||
ftv->hasSelf = true;
|
||||
ftv->hasSelf = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1905,8 +1914,18 @@ WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExp
|
|||
|
||||
if (get<TypePack>(varargPack))
|
||||
{
|
||||
std::vector<TypeId> types = flatten(varargPack).first;
|
||||
return {!types.empty() ? types[0] : nilType};
|
||||
if (FFlag::LuauFixVarargExprHeadType)
|
||||
{
|
||||
if (std::optional<TypeId> ty = first(varargPack))
|
||||
return {*ty};
|
||||
|
||||
return {nilType};
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<TypeId> types = flatten(varargPack).first;
|
||||
return {!types.empty() ? types[0] : nilType};
|
||||
}
|
||||
}
|
||||
else if (get<FreeTypePack>(varargPack))
|
||||
{
|
||||
|
@ -3967,7 +3986,10 @@ void TypeChecker::checkArgumentList(const ScopePtr& scope, const AstExpr& funNam
|
|||
}
|
||||
else
|
||||
{
|
||||
unifyWithInstantiationIfNeeded(*argIter, *paramIter, scope, state);
|
||||
if (FFlag::LuauInstantiateInSubtyping)
|
||||
state.tryUnify(*argIter, *paramIter, /*isFunctionCall*/ false);
|
||||
else
|
||||
unifyWithInstantiationIfNeeded(*argIter, *paramIter, scope, state);
|
||||
++argIter;
|
||||
++paramIter;
|
||||
}
|
||||
|
@ -4523,8 +4545,11 @@ WithPredicate<TypePackId> TypeChecker::checkExprList(const ScopePtr& scope, cons
|
|||
|
||||
TypeId actualType = substituteFreeForNil && expr->is<AstExprConstantNil>() ? freshType(scope) : type;
|
||||
|
||||
if (instantiateGenerics.size() > i && instantiateGenerics[i])
|
||||
actualType = instantiate(scope, actualType, expr->location);
|
||||
if (!FFlag::LuauInstantiateInSubtyping)
|
||||
{
|
||||
if (instantiateGenerics.size() > i && instantiateGenerics[i])
|
||||
actualType = instantiate(scope, actualType, expr->location);
|
||||
}
|
||||
|
||||
if (expectedType)
|
||||
{
|
||||
|
@ -4686,6 +4711,8 @@ bool TypeChecker::unifyWithInstantiationIfNeeded(TypeId subTy, TypeId superTy, c
|
|||
|
||||
void TypeChecker::unifyWithInstantiationIfNeeded(TypeId subTy, TypeId superTy, const ScopePtr& scope, Unifier& state)
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::LuauInstantiateInSubtyping);
|
||||
|
||||
if (!maybeGeneric(subTy))
|
||||
// Quick check to see if we definitely can't instantiate
|
||||
state.tryUnify(subTy, superTy, /*isFunctionCall*/ false);
|
||||
|
@ -4828,6 +4855,33 @@ TypePackId TypeChecker::anyify(const ScopePtr& scope, TypePackId ty, Location lo
|
|||
}
|
||||
}
|
||||
|
||||
TypePackId TypeChecker::anyifyModuleReturnTypePackGenerics(TypePackId tp)
|
||||
{
|
||||
tp = follow(tp);
|
||||
|
||||
if (const VariadicTypePack* vtp = get<VariadicTypePack>(tp))
|
||||
return get<GenericTypeVar>(vtp->ty) ? anyTypePack : tp;
|
||||
|
||||
if (!get<TypePack>(follow(tp)))
|
||||
return tp;
|
||||
|
||||
std::vector<TypeId> resultTypes;
|
||||
std::optional<TypePackId> resultTail;
|
||||
|
||||
TypePackIterator it = begin(tp);
|
||||
|
||||
for (TypePackIterator e = end(tp); it != e; ++it)
|
||||
{
|
||||
TypeId ty = follow(*it);
|
||||
resultTypes.push_back(get<GenericTypeVar>(ty) ? anyType : ty);
|
||||
}
|
||||
|
||||
if (std::optional<TypePackId> tail = it.tail())
|
||||
resultTail = anyifyModuleReturnTypePackGenerics(*tail);
|
||||
|
||||
return addTypePack(resultTypes, resultTail);
|
||||
}
|
||||
|
||||
void TypeChecker::reportError(const TypeError& error)
|
||||
{
|
||||
if (currentModule->mode == Mode::NoCheck)
|
||||
|
@ -4955,8 +5009,7 @@ void TypeChecker::merge(RefinementMap& l, const RefinementMap& r)
|
|||
|
||||
Unifier TypeChecker::mkUnifier(const ScopePtr& scope, const Location& location)
|
||||
{
|
||||
return Unifier{
|
||||
¤tModule->internalTypes, singletonTypes, currentModule->mode, NotNull{scope.get()}, location, Variance::Covariant, unifierState};
|
||||
return Unifier{NotNull{&normalizer}, currentModule->mode, NotNull{scope.get()}, location, Variance::Covariant};
|
||||
}
|
||||
|
||||
TypeId TypeChecker::freshType(const ScopePtr& scope)
|
||||
|
|
|
@ -27,6 +27,7 @@ LUAU_FASTFLAG(LuauUnknownAndNeverType)
|
|||
LUAU_FASTFLAGVARIABLE(LuauMaybeGenericIntersectionTypes, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauStringFormatArgumentErrorFix, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNoMoreGlobalSingletonTypes, false)
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -339,6 +340,8 @@ bool isSubset(const UnionTypeVar& super, const UnionTypeVar& sub)
|
|||
// then instantiate U if `isGeneric(U)` is true, and `maybeGeneric(T)` is false.
|
||||
bool isGeneric(TypeId ty)
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::LuauInstantiateInSubtyping);
|
||||
|
||||
ty = follow(ty);
|
||||
if (auto ftv = get<FunctionTypeVar>(ty))
|
||||
return ftv->generics.size() > 0 || ftv->genericPacks.size() > 0;
|
||||
|
@ -350,6 +353,8 @@ bool isGeneric(TypeId ty)
|
|||
|
||||
bool maybeGeneric(TypeId ty)
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::LuauInstantiateInSubtyping);
|
||||
|
||||
if (FFlag::LuauMaybeGenericIntersectionTypes)
|
||||
{
|
||||
ty = follow(ty);
|
||||
|
|
|
@ -1,60 +1,64 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/Unifiable.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauTypeNormalization2);
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
namespace Unifiable
|
||||
{
|
||||
|
||||
static int nextIndex = 0;
|
||||
|
||||
Free::Free(TypeLevel level)
|
||||
: index(++nextIndex)
|
||||
: index(FFlag::LuauTypeNormalization2 ? ++nextIndex : ++DEPRECATED_nextIndex)
|
||||
, level(level)
|
||||
{
|
||||
}
|
||||
|
||||
Free::Free(Scope* scope)
|
||||
: index(++nextIndex)
|
||||
: index(FFlag::LuauTypeNormalization2 ? ++nextIndex : ++DEPRECATED_nextIndex)
|
||||
, scope(scope)
|
||||
{
|
||||
}
|
||||
|
||||
Free::Free(Scope* scope, TypeLevel level)
|
||||
: index(++nextIndex)
|
||||
: index(FFlag::LuauTypeNormalization2 ? ++nextIndex : ++DEPRECATED_nextIndex)
|
||||
, level(level)
|
||||
, scope(scope)
|
||||
{
|
||||
}
|
||||
|
||||
int Free::nextIndex = 0;
|
||||
int Free::DEPRECATED_nextIndex = 0;
|
||||
|
||||
Generic::Generic()
|
||||
: index(++nextIndex)
|
||||
: index(FFlag::LuauTypeNormalization2 ? ++nextIndex : ++DEPRECATED_nextIndex)
|
||||
, name("g" + std::to_string(index))
|
||||
{
|
||||
}
|
||||
|
||||
Generic::Generic(TypeLevel level)
|
||||
: index(++nextIndex)
|
||||
: index(FFlag::LuauTypeNormalization2 ? ++nextIndex : ++DEPRECATED_nextIndex)
|
||||
, level(level)
|
||||
, name("g" + std::to_string(index))
|
||||
{
|
||||
}
|
||||
|
||||
Generic::Generic(const Name& name)
|
||||
: index(++nextIndex)
|
||||
: index(FFlag::LuauTypeNormalization2 ? ++nextIndex : ++DEPRECATED_nextIndex)
|
||||
, name(name)
|
||||
, explicitName(true)
|
||||
{
|
||||
}
|
||||
|
||||
Generic::Generic(Scope* scope)
|
||||
: index(++nextIndex)
|
||||
: index(FFlag::LuauTypeNormalization2 ? ++nextIndex : ++DEPRECATED_nextIndex)
|
||||
, scope(scope)
|
||||
{
|
||||
}
|
||||
|
||||
Generic::Generic(TypeLevel level, const Name& name)
|
||||
: index(++nextIndex)
|
||||
: index(FFlag::LuauTypeNormalization2 ? ++nextIndex : ++DEPRECATED_nextIndex)
|
||||
, level(level)
|
||||
, name(name)
|
||||
, explicitName(true)
|
||||
|
@ -62,14 +66,14 @@ Generic::Generic(TypeLevel level, const Name& name)
|
|||
}
|
||||
|
||||
Generic::Generic(Scope* scope, const Name& name)
|
||||
: index(++nextIndex)
|
||||
: index(FFlag::LuauTypeNormalization2 ? ++nextIndex : ++DEPRECATED_nextIndex)
|
||||
, scope(scope)
|
||||
, name(name)
|
||||
, explicitName(true)
|
||||
{
|
||||
}
|
||||
|
||||
int Generic::nextIndex = 0;
|
||||
int Generic::DEPRECATED_nextIndex = 0;
|
||||
|
||||
Error::Error()
|
||||
: index(++nextIndex)
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include "Luau/Unifier.h"
|
||||
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/Instantiation.h"
|
||||
#include "Luau/RecursionCounter.h"
|
||||
#include "Luau/Scope.h"
|
||||
#include "Luau/TypePack.h"
|
||||
|
@ -20,7 +21,9 @@ LUAU_FASTINTVARIABLE(LuauTypeInferLowerBoundsIterationLimit, 2000);
|
|||
LUAU_FASTFLAG(LuauLowerBoundsCalculation);
|
||||
LUAU_FASTFLAG(LuauErrorRecoveryType);
|
||||
LUAU_FASTFLAG(LuauUnknownAndNeverType)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSubtypeNormalizer, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauScalarShapeSubtyping, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauInstantiateInSubtyping, false)
|
||||
LUAU_FASTFLAG(LuauClassTypeVarsInSubstitution)
|
||||
LUAU_FASTFLAG(LuauCallUnifyPackTails)
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
|
@ -343,17 +346,19 @@ static bool subsumes(bool useScopes, TY_A* left, TY_B* right)
|
|||
return left->level.subsumes(right->level);
|
||||
}
|
||||
|
||||
Unifier::Unifier(TypeArena* types, NotNull<SingletonTypes> singletonTypes, Mode mode, NotNull<Scope> scope, const Location& location,
|
||||
Variance variance, UnifierSharedState& sharedState, TxnLog* parentLog)
|
||||
: types(types)
|
||||
, singletonTypes(singletonTypes)
|
||||
Unifier::Unifier(NotNull<Normalizer> normalizer, Mode mode, NotNull<Scope> scope, const Location& location,
|
||||
Variance variance, TxnLog* parentLog)
|
||||
: types(normalizer->arena)
|
||||
, singletonTypes(normalizer->singletonTypes)
|
||||
, normalizer(normalizer)
|
||||
, mode(mode)
|
||||
, scope(scope)
|
||||
, log(parentLog)
|
||||
, location(location)
|
||||
, variance(variance)
|
||||
, sharedState(sharedState)
|
||||
, sharedState(*normalizer->sharedState)
|
||||
{
|
||||
normalize = FFlag::LuauSubtypeNormalizer;
|
||||
LUAU_ASSERT(sharedState.iceHandler);
|
||||
}
|
||||
|
||||
|
@ -524,7 +529,7 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
|
|||
{
|
||||
tryUnifyUnionWithType(subTy, subUnion, superTy);
|
||||
}
|
||||
else if (const UnionTypeVar* uv = log.getMutable<UnionTypeVar>(superTy))
|
||||
else if (const UnionTypeVar* uv = (FFlag::LuauSubtypeNormalizer? nullptr: log.getMutable<UnionTypeVar>(superTy)))
|
||||
{
|
||||
tryUnifyTypeWithUnion(subTy, superTy, uv, cacheEnabled, isFunctionCall);
|
||||
}
|
||||
|
@ -532,6 +537,10 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
|
|||
{
|
||||
tryUnifyTypeWithIntersection(subTy, superTy, uv);
|
||||
}
|
||||
else if (const UnionTypeVar* uv = log.getMutable<UnionTypeVar>(superTy))
|
||||
{
|
||||
tryUnifyTypeWithUnion(subTy, superTy, uv, cacheEnabled, isFunctionCall);
|
||||
}
|
||||
else if (const IntersectionTypeVar* uv = log.getMutable<IntersectionTypeVar>(subTy))
|
||||
{
|
||||
tryUnifyIntersectionWithType(subTy, uv, superTy, cacheEnabled, isFunctionCall);
|
||||
|
@ -585,7 +594,7 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
|
|||
|
||||
void Unifier::tryUnifyUnionWithType(TypeId subTy, const UnionTypeVar* subUnion, TypeId superTy)
|
||||
{
|
||||
// A | B <: T if A <: T and B <: T
|
||||
// A | B <: T if and only if A <: T and B <: T
|
||||
bool failed = false;
|
||||
std::optional<TypeError> unificationTooComplex;
|
||||
std::optional<TypeError> firstFailedOption;
|
||||
|
@ -715,6 +724,7 @@ void Unifier::tryUnifyTypeWithUnion(TypeId subTy, TypeId superTy, const UnionTyp
|
|||
{
|
||||
TypeId type = uv->options[(i + startIndex) % uv->options.size()];
|
||||
Unifier innerState = makeChildUnifier();
|
||||
innerState.normalize = false;
|
||||
innerState.tryUnify_(subTy, type, isFunctionCall);
|
||||
|
||||
if (innerState.errors.empty())
|
||||
|
@ -741,6 +751,20 @@ void Unifier::tryUnifyTypeWithUnion(TypeId subTy, TypeId superTy, const UnionTyp
|
|||
{
|
||||
reportError(*unificationTooComplex);
|
||||
}
|
||||
else if (!found && normalize)
|
||||
{
|
||||
// It is possible that T <: A | B even though T </: A and T </:B
|
||||
// for example boolean <: true | false.
|
||||
// We deal with this by type normalization.
|
||||
const NormalizedType* subNorm = normalizer->normalize(subTy);
|
||||
const NormalizedType* superNorm = normalizer->normalize(superTy);
|
||||
if (!subNorm || !superNorm)
|
||||
reportError(TypeError{location, UnificationTooComplex{}});
|
||||
else if ((failedOptionCount == 1 || foundHeuristic) && failedOption)
|
||||
tryUnifyNormalizedTypes(subTy, superTy, *subNorm, *superNorm, "None of the union options are compatible. For example:", *failedOption);
|
||||
else
|
||||
tryUnifyNormalizedTypes(subTy, superTy, *subNorm, *superNorm, "none of the union options are compatible");
|
||||
}
|
||||
else if (!found)
|
||||
{
|
||||
if ((failedOptionCount == 1 || foundHeuristic) && failedOption)
|
||||
|
@ -755,7 +779,7 @@ void Unifier::tryUnifyTypeWithIntersection(TypeId subTy, TypeId superTy, const I
|
|||
std::optional<TypeError> unificationTooComplex;
|
||||
std::optional<TypeError> firstFailedOption;
|
||||
|
||||
// T <: A & B if T <: A and T <: B
|
||||
// T <: A & B if and only if T <: A and T <: B
|
||||
for (TypeId type : uv->parts)
|
||||
{
|
||||
Unifier innerState = makeChildUnifier();
|
||||
|
@ -806,6 +830,7 @@ void Unifier::tryUnifyIntersectionWithType(TypeId subTy, const IntersectionTypeV
|
|||
{
|
||||
TypeId type = uv->parts[(i + startIndex) % uv->parts.size()];
|
||||
Unifier innerState = makeChildUnifier();
|
||||
innerState.normalize = false;
|
||||
innerState.tryUnify_(type, superTy, isFunctionCall);
|
||||
|
||||
if (innerState.errors.empty())
|
||||
|
@ -822,12 +847,207 @@ void Unifier::tryUnifyIntersectionWithType(TypeId subTy, const IntersectionTypeV
|
|||
|
||||
if (unificationTooComplex)
|
||||
reportError(*unificationTooComplex);
|
||||
else if (!found && normalize)
|
||||
{
|
||||
// It is possible that A & B <: T even though A </: T and B </: T
|
||||
// for example string? & number? <: nil.
|
||||
// We deal with this by type normalization.
|
||||
const NormalizedType* subNorm = normalizer->normalize(subTy);
|
||||
const NormalizedType* superNorm = normalizer->normalize(superTy);
|
||||
if (subNorm && superNorm)
|
||||
tryUnifyNormalizedTypes(subTy, superTy, *subNorm, *superNorm, "none of the intersection parts are compatible");
|
||||
else
|
||||
reportError(TypeError{location, UnificationTooComplex{}});
|
||||
}
|
||||
else if (!found)
|
||||
{
|
||||
reportError(TypeError{location, TypeMismatch{superTy, subTy, "none of the intersection parts are compatible"}});
|
||||
}
|
||||
}
|
||||
|
||||
void Unifier::tryUnifyNormalizedTypes(TypeId subTy, TypeId superTy, const NormalizedType& subNorm, const NormalizedType& superNorm, std::string reason, std::optional<TypeError> error)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauSubtypeNormalizer);
|
||||
|
||||
if (get<UnknownTypeVar>(superNorm.tops) || get<AnyTypeVar>(superNorm.tops) || get<AnyTypeVar>(subNorm.tops))
|
||||
return;
|
||||
else if (get<UnknownTypeVar>(subNorm.tops))
|
||||
return reportError(TypeError{location, TypeMismatch{superTy, subTy, reason, error}});
|
||||
|
||||
if (get<ErrorTypeVar>(subNorm.errors))
|
||||
if (!get<ErrorTypeVar>(superNorm.errors))
|
||||
return reportError(TypeError{location, TypeMismatch{superTy, subTy, reason, error}});
|
||||
|
||||
if (get<PrimitiveTypeVar>(subNorm.booleans))
|
||||
{
|
||||
if (!get<PrimitiveTypeVar>(superNorm.booleans))
|
||||
return reportError(TypeError{location, TypeMismatch{superTy, subTy, reason, error}});
|
||||
}
|
||||
else if (const SingletonTypeVar* stv = get<SingletonTypeVar>(subNorm.booleans))
|
||||
{
|
||||
if (!get<PrimitiveTypeVar>(superNorm.booleans) && stv != get<SingletonTypeVar>(superNorm.booleans))
|
||||
return reportError(TypeError{location, TypeMismatch{superTy, subTy, reason, error}});
|
||||
}
|
||||
|
||||
if (get<PrimitiveTypeVar>(subNorm.nils))
|
||||
if (!get<PrimitiveTypeVar>(superNorm.nils))
|
||||
return reportError(TypeError{location, TypeMismatch{superTy, subTy, reason, error}});
|
||||
|
||||
if (get<PrimitiveTypeVar>(subNorm.numbers))
|
||||
if (!get<PrimitiveTypeVar>(superNorm.numbers))
|
||||
return reportError(TypeError{location, TypeMismatch{superTy, subTy, reason, error}});
|
||||
|
||||
if (subNorm.strings && superNorm.strings)
|
||||
{
|
||||
for (auto [name, ty] : *subNorm.strings)
|
||||
if (!superNorm.strings->count(name))
|
||||
return reportError(TypeError{location, TypeMismatch{superTy, subTy, reason, error}});
|
||||
}
|
||||
else if (!subNorm.strings && superNorm.strings)
|
||||
return reportError(TypeError{location, TypeMismatch{superTy, subTy, reason, error}});
|
||||
|
||||
if (get<PrimitiveTypeVar>(subNorm.threads))
|
||||
if (!get<PrimitiveTypeVar>(superNorm.errors))
|
||||
return reportError(TypeError{location, TypeMismatch{superTy, subTy, reason, error}});
|
||||
|
||||
for (TypeId subClass : subNorm.classes)
|
||||
{
|
||||
bool found = false;
|
||||
const ClassTypeVar* subCtv = get<ClassTypeVar>(subClass);
|
||||
for (TypeId superClass : superNorm.classes)
|
||||
{
|
||||
const ClassTypeVar* superCtv = get<ClassTypeVar>(superClass);
|
||||
if (isSubclass(subCtv, superCtv))
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
return reportError(TypeError{location, TypeMismatch{superTy, subTy, reason, error}});
|
||||
}
|
||||
|
||||
for (TypeId subTable : subNorm.tables)
|
||||
{
|
||||
bool found = false;
|
||||
for (TypeId superTable : superNorm.tables)
|
||||
{
|
||||
Unifier innerState = makeChildUnifier();
|
||||
if (get<MetatableTypeVar>(superTable))
|
||||
innerState.tryUnifyWithMetatable(subTable, superTable, /* reversed */ false);
|
||||
else if (get<MetatableTypeVar>(subTable))
|
||||
innerState.tryUnifyWithMetatable(superTable, subTable, /* reversed */ true);
|
||||
else
|
||||
innerState.tryUnifyTables(subTable, superTable);
|
||||
if (innerState.errors.empty())
|
||||
{
|
||||
found = true;
|
||||
log.concat(std::move(innerState.log));
|
||||
break;
|
||||
}
|
||||
else if (auto e = hasUnificationTooComplex(innerState.errors))
|
||||
return reportError(*e);
|
||||
}
|
||||
if (!found)
|
||||
return reportError(TypeError{location, TypeMismatch{superTy, subTy, reason, error}});
|
||||
}
|
||||
|
||||
if (subNorm.functions)
|
||||
{
|
||||
if (!superNorm.functions)
|
||||
return reportError(TypeError{location, TypeMismatch{superTy, subTy, reason, error}});
|
||||
if (superNorm.functions->empty())
|
||||
return;
|
||||
for (TypeId superFun : *superNorm.functions)
|
||||
{
|
||||
Unifier innerState = makeChildUnifier();
|
||||
const FunctionTypeVar* superFtv = get<FunctionTypeVar>(superFun);
|
||||
if (!superFtv)
|
||||
return reportError(TypeError{location, TypeMismatch{superTy, subTy, reason, error}});
|
||||
TypePackId tgt = innerState.tryApplyOverloadedFunction(subTy, subNorm.functions, superFtv->argTypes);
|
||||
innerState.tryUnify_(tgt, superFtv->retTypes);
|
||||
if (innerState.errors.empty())
|
||||
log.concat(std::move(innerState.log));
|
||||
else if (auto e = hasUnificationTooComplex(innerState.errors))
|
||||
return reportError(*e);
|
||||
else
|
||||
return reportError(TypeError{location, TypeMismatch{superTy, subTy, reason, error}});
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& [tyvar, subIntersect] : subNorm.tyvars)
|
||||
{
|
||||
auto found = superNorm.tyvars.find(tyvar);
|
||||
if (found == superNorm.tyvars.end())
|
||||
tryUnifyNormalizedTypes(subTy, superTy, *subIntersect, superNorm, reason, error);
|
||||
else
|
||||
tryUnifyNormalizedTypes(subTy, superTy, *subIntersect, *found->second, reason, error);
|
||||
if (!errors.empty())
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
TypePackId Unifier::tryApplyOverloadedFunction(TypeId function, const NormalizedFunctionType& overloads, TypePackId args)
|
||||
{
|
||||
if (!overloads || overloads->empty())
|
||||
{
|
||||
reportError(TypeError{location, CannotCallNonFunction{function}});
|
||||
return singletonTypes->errorRecoveryTypePack();
|
||||
}
|
||||
|
||||
std::optional<TypePackId> result;
|
||||
const FunctionTypeVar* firstFun = nullptr;
|
||||
for (TypeId overload : *overloads)
|
||||
{
|
||||
if (const FunctionTypeVar* ftv = get<FunctionTypeVar>(overload))
|
||||
{
|
||||
// TODO: instantiate generics?
|
||||
if (ftv->generics.empty() && ftv->genericPacks.empty())
|
||||
{
|
||||
if (!firstFun)
|
||||
firstFun = ftv;
|
||||
Unifier innerState = makeChildUnifier();
|
||||
innerState.tryUnify_(args, ftv->argTypes);
|
||||
if (innerState.errors.empty())
|
||||
{
|
||||
log.concat(std::move(innerState.log));
|
||||
if (result)
|
||||
{
|
||||
// Annoyingly, since we don't support intersection of generic type packs,
|
||||
// the intersection may fail. We rather arbitrarily use the first matching overload
|
||||
// in that case.
|
||||
if (std::optional<TypePackId> intersect = normalizer->intersectionOfTypePacks(*result, ftv->retTypes))
|
||||
result = intersect;
|
||||
}
|
||||
else
|
||||
result = ftv->retTypes;
|
||||
}
|
||||
else if (auto e = hasUnificationTooComplex(innerState.errors))
|
||||
{
|
||||
reportError(*e);
|
||||
return singletonTypes->errorRecoveryTypePack(args);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (result)
|
||||
return *result;
|
||||
else if (firstFun)
|
||||
{
|
||||
// TODO: better error reporting?
|
||||
// The logic for error reporting overload resolution
|
||||
// is currently over in TypeInfer.cpp, should we move it?
|
||||
reportError(TypeError{location, GenericError{"No matching overload."}});
|
||||
return singletonTypes->errorRecoveryTypePack(firstFun->retTypes);
|
||||
}
|
||||
else
|
||||
{
|
||||
reportError(TypeError{location, CannotCallNonFunction{function}});
|
||||
return singletonTypes->errorRecoveryTypePack();
|
||||
}
|
||||
}
|
||||
|
||||
bool Unifier::canCacheResult(TypeId subTy, TypeId superTy)
|
||||
{
|
||||
bool* superTyInfo = sharedState.skipCacheForType.find(superTy);
|
||||
|
@ -1253,14 +1473,38 @@ void Unifier::tryUnifyFunctions(TypeId subTy, TypeId superTy, bool isFunctionCal
|
|||
ice("passed non-function types to unifyFunction");
|
||||
|
||||
size_t numGenerics = superFunction->generics.size();
|
||||
if (numGenerics != subFunction->generics.size())
|
||||
size_t numGenericPacks = superFunction->genericPacks.size();
|
||||
|
||||
bool shouldInstantiate = (numGenerics == 0 && subFunction->generics.size() > 0) || (numGenericPacks == 0 && subFunction->genericPacks.size() > 0);
|
||||
|
||||
if (FFlag::LuauInstantiateInSubtyping && variance == Covariant && shouldInstantiate)
|
||||
{
|
||||
Instantiation instantiation{&log, types, scope->level, scope};
|
||||
|
||||
std::optional<TypeId> instantiated = instantiation.substitute(subTy);
|
||||
if (instantiated.has_value())
|
||||
{
|
||||
subFunction = log.getMutable<FunctionTypeVar>(*instantiated);
|
||||
|
||||
if (!subFunction)
|
||||
ice("instantiation made a function type into a non-function type in unifyFunction");
|
||||
|
||||
numGenerics = std::min(superFunction->generics.size(), subFunction->generics.size());
|
||||
numGenericPacks = std::min(superFunction->genericPacks.size(), subFunction->genericPacks.size());
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
reportError(TypeError{location, UnificationTooComplex{}});
|
||||
}
|
||||
}
|
||||
else if (numGenerics != subFunction->generics.size())
|
||||
{
|
||||
numGenerics = std::min(superFunction->generics.size(), subFunction->generics.size());
|
||||
|
||||
reportError(TypeError{location, TypeMismatch{superTy, subTy, "different number of generic type parameters"}});
|
||||
}
|
||||
|
||||
size_t numGenericPacks = superFunction->genericPacks.size();
|
||||
if (numGenericPacks != subFunction->genericPacks.size())
|
||||
{
|
||||
numGenericPacks = std::min(superFunction->genericPacks.size(), subFunction->genericPacks.size());
|
||||
|
@ -1376,6 +1620,27 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
|
|||
std::vector<std::string> missingProperties;
|
||||
std::vector<std::string> extraProperties;
|
||||
|
||||
if (FFlag::LuauInstantiateInSubtyping)
|
||||
{
|
||||
if (variance == Covariant && subTable->state == TableState::Generic && superTable->state != TableState::Generic)
|
||||
{
|
||||
Instantiation instantiation{&log, types, subTable->level, scope};
|
||||
|
||||
std::optional<TypeId> instantiated = instantiation.substitute(subTy);
|
||||
if (instantiated.has_value())
|
||||
{
|
||||
subTable = log.getMutable<TableTypeVar>(*instantiated);
|
||||
|
||||
if (!subTable)
|
||||
ice("instantiation made a table type into a non-table type in tryUnifyTables");
|
||||
}
|
||||
else
|
||||
{
|
||||
reportError(TypeError{location, UnificationTooComplex{}});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Optimization: First test that the property sets are compatible without doing any recursive unification
|
||||
if (!subTable->indexer && subTable->state != TableState::Free)
|
||||
{
|
||||
|
@ -2344,8 +2609,9 @@ bool Unifier::occursCheck(DenseHashSet<TypePackId>& seen, TypePackId needle, Typ
|
|||
|
||||
Unifier Unifier::makeChildUnifier()
|
||||
{
|
||||
Unifier u = Unifier{types, singletonTypes, mode, scope, location, variance, sharedState, &log};
|
||||
Unifier u = Unifier{normalizer, mode, scope, location, variance, &log};
|
||||
u.anyIsTop = anyIsTop;
|
||||
u.normalize = normalize;
|
||||
return u;
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,8 @@ LUAU_DYNAMIC_FASTFLAGVARIABLE(LuaReportParseIntegerIssues, false)
|
|||
LUAU_FASTFLAGVARIABLE(LuauInterpolatedStringBaseSupport, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTypeAnnotationLocationChange, false)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauCommaParenWarnings, false)
|
||||
|
||||
bool lua_telemetry_parsed_out_of_range_bin_integer = false;
|
||||
bool lua_telemetry_parsed_out_of_range_hex_integer = false;
|
||||
bool lua_telemetry_parsed_double_prefix_hex_integer = false;
|
||||
|
@ -1062,6 +1064,12 @@ void Parser::parseExprList(TempVector<AstExpr*>& result)
|
|||
{
|
||||
nextLexeme();
|
||||
|
||||
if (FFlag::LuauCommaParenWarnings && lexer.current().type == ')')
|
||||
{
|
||||
report(lexer.current().location, "Expected expression after ',' but got ')' instead");
|
||||
break;
|
||||
}
|
||||
|
||||
result.push_back(parseExpr());
|
||||
}
|
||||
}
|
||||
|
@ -1148,7 +1156,14 @@ AstTypePack* Parser::parseTypeList(TempVector<AstType*>& result, TempVector<std:
|
|||
result.push_back(parseTypeAnnotation());
|
||||
if (lexer.current().type != ',')
|
||||
break;
|
||||
|
||||
nextLexeme();
|
||||
|
||||
if (FFlag::LuauCommaParenWarnings && lexer.current().type == ')')
|
||||
{
|
||||
report(lexer.current().location, "Expected type after ',' but got ')' instead");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
|
@ -2514,7 +2529,15 @@ std::pair<AstArray<AstGenericType>, AstArray<AstGenericTypePack>> Parser::parseG
|
|||
}
|
||||
|
||||
if (lexer.current().type == ',')
|
||||
{
|
||||
nextLexeme();
|
||||
|
||||
if (FFlag::LuauCommaParenWarnings && lexer.current().type == '>')
|
||||
{
|
||||
report(lexer.current().location, "Expected type after ',' but got '>' instead");
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -82,11 +82,13 @@ static void profilerLoop()
|
|||
|
||||
if (now - last >= 1.0 / double(gProfiler.frequency))
|
||||
{
|
||||
gProfiler.ticks += uint64_t((now - last) * 1e6);
|
||||
int64_t ticks = int64_t((now - last) * 1e6);
|
||||
|
||||
gProfiler.ticks += ticks;
|
||||
gProfiler.samples++;
|
||||
gProfiler.callbacks->interrupt = profilerTrigger;
|
||||
|
||||
last = now;
|
||||
last += ticks * 1e-6;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -152,6 +152,7 @@ private:
|
|||
void placeModRegMem(OperandX64 rhs, uint8_t regop);
|
||||
void placeRex(RegisterX64 op);
|
||||
void placeRex(OperandX64 op);
|
||||
void placeRexNoW(OperandX64 op);
|
||||
void placeRex(RegisterX64 lhs, OperandX64 rhs);
|
||||
void placeVex(OperandX64 dst, OperandX64 src1, OperandX64 src2, bool setW, uint8_t mode, uint8_t prefix);
|
||||
void placeImm8Or32(int32_t imm);
|
||||
|
|
|
@ -24,13 +24,15 @@ struct CodeAllocator
|
|||
void* context = nullptr;
|
||||
|
||||
// Called when new block is created to create and setup the unwinding information for all the code in the block
|
||||
// If data is placed inside the block itself (some platforms require this), we also return 'unwindDataSizeInBlock'
|
||||
void* (*createBlockUnwindInfo)(void* context, uint8_t* block, size_t blockSize, size_t& unwindDataSizeInBlock) = nullptr;
|
||||
// 'startOffset' reserves space for data at the beginning of the page
|
||||
void* (*createBlockUnwindInfo)(void* context, uint8_t* block, size_t blockSize, size_t& startOffset) = nullptr;
|
||||
|
||||
// Called to destroy unwinding information returned by 'createBlockUnwindInfo'
|
||||
void (*destroyBlockUnwindInfo)(void* context, void* unwindData) = nullptr;
|
||||
|
||||
static const size_t kMaxUnwindDataSize = 128;
|
||||
// Unwind information can be placed inside the block with some implementation-specific reservations at the beginning
|
||||
// But to simplify block space checks, we limit the max size of all that data
|
||||
static const size_t kMaxReservedDataSize = 256;
|
||||
|
||||
bool allocateNewBlock(size_t& unwindInfoSize);
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ namespace CodeGen
|
|||
{
|
||||
|
||||
// context must be an UnwindBuilder
|
||||
void* createBlockUnwindInfo(void* context, uint8_t* block, size_t blockSize, size_t& unwindDataSizeInBlock);
|
||||
void* createBlockUnwindInfo(void* context, uint8_t* block, size_t blockSize, size_t& startOffset);
|
||||
void destroyBlockUnwindInfo(void* context, void* unwindData);
|
||||
|
||||
} // namespace CodeGen
|
||||
|
|
|
@ -14,7 +14,10 @@ namespace CodeGen
|
|||
class UnwindBuilder
|
||||
{
|
||||
public:
|
||||
virtual ~UnwindBuilder() {}
|
||||
virtual ~UnwindBuilder() = default;
|
||||
|
||||
virtual void setBeginOffset(size_t beginOffset) = 0;
|
||||
virtual size_t getBeginOffset() const = 0;
|
||||
|
||||
virtual void start() = 0;
|
||||
|
||||
|
|
|
@ -12,6 +12,9 @@ namespace CodeGen
|
|||
class UnwindBuilderDwarf2 : public UnwindBuilder
|
||||
{
|
||||
public:
|
||||
void setBeginOffset(size_t beginOffset) override;
|
||||
size_t getBeginOffset() const override;
|
||||
|
||||
void start() override;
|
||||
|
||||
void spill(int espOffset, RegisterX64 reg) override;
|
||||
|
@ -26,6 +29,8 @@ public:
|
|||
void finalize(char* target, void* funcAddress, size_t funcSize) const override;
|
||||
|
||||
private:
|
||||
size_t beginOffset = 0;
|
||||
|
||||
static const unsigned kRawDataLimit = 128;
|
||||
uint8_t rawData[kRawDataLimit];
|
||||
uint8_t* pos = rawData;
|
||||
|
|
|
@ -22,6 +22,9 @@ struct UnwindCodeWin
|
|||
class UnwindBuilderWin : public UnwindBuilder
|
||||
{
|
||||
public:
|
||||
void setBeginOffset(size_t beginOffset) override;
|
||||
size_t getBeginOffset() const override;
|
||||
|
||||
void start() override;
|
||||
|
||||
void spill(int espOffset, RegisterX64 reg) override;
|
||||
|
@ -36,6 +39,8 @@ public:
|
|||
void finalize(char* target, void* funcAddress, size_t funcSize) const override;
|
||||
|
||||
private:
|
||||
size_t beginOffset = 0;
|
||||
|
||||
// Windows unwind codes are written in reverse, so we have to collect them all first
|
||||
std::vector<UnwindCodeWin> unwindCodes;
|
||||
|
||||
|
|
|
@ -354,10 +354,15 @@ void AssemblyBuilderX64::jmp(Label& label)
|
|||
|
||||
void AssemblyBuilderX64::jmp(OperandX64 op)
|
||||
{
|
||||
LUAU_ASSERT((op.cat == CategoryX64::reg ? op.base.size : op.memSize) == SizeX64::qword);
|
||||
|
||||
if (logText)
|
||||
log("jmp", op);
|
||||
|
||||
placeRex(op);
|
||||
// Indirect absolute calls always work in 64 bit width mode, so REX.W is optional
|
||||
// While we could keep an optional prefix, in Windows x64 ABI it signals a tail call return statement to the unwinder
|
||||
placeRexNoW(op);
|
||||
|
||||
place(0xff);
|
||||
placeModRegMem(op, 4);
|
||||
commit();
|
||||
|
@ -376,10 +381,14 @@ void AssemblyBuilderX64::call(Label& label)
|
|||
|
||||
void AssemblyBuilderX64::call(OperandX64 op)
|
||||
{
|
||||
LUAU_ASSERT((op.cat == CategoryX64::reg ? op.base.size : op.memSize) == SizeX64::qword);
|
||||
|
||||
if (logText)
|
||||
log("call", op);
|
||||
|
||||
placeRex(op);
|
||||
// Indirect absolute calls always work in 64 bit width mode, so REX.W is optional
|
||||
placeRexNoW(op);
|
||||
|
||||
place(0xff);
|
||||
placeModRegMem(op, 2);
|
||||
commit();
|
||||
|
@ -838,6 +847,21 @@ void AssemblyBuilderX64::placeRex(OperandX64 op)
|
|||
place(code | 0x40);
|
||||
}
|
||||
|
||||
void AssemblyBuilderX64::placeRexNoW(OperandX64 op)
|
||||
{
|
||||
uint8_t code = 0;
|
||||
|
||||
if (op.cat == CategoryX64::reg)
|
||||
code = REX_B(op.base);
|
||||
else if (op.cat == CategoryX64::mem)
|
||||
code = REX_X(op.index) | REX_B(op.base);
|
||||
else
|
||||
LUAU_ASSERT(!"No encoding for left operand of this category");
|
||||
|
||||
if (code != 0)
|
||||
place(code | 0x40);
|
||||
}
|
||||
|
||||
void AssemblyBuilderX64::placeRex(RegisterX64 lhs, OperandX64 rhs)
|
||||
{
|
||||
uint8_t code = REX_W(lhs.size == SizeX64::qword);
|
||||
|
|
|
@ -91,7 +91,7 @@ CodeAllocator::CodeAllocator(size_t blockSize, size_t maxTotalSize)
|
|||
: blockSize(blockSize)
|
||||
, maxTotalSize(maxTotalSize)
|
||||
{
|
||||
LUAU_ASSERT(blockSize > kMaxUnwindDataSize);
|
||||
LUAU_ASSERT(blockSize > kMaxReservedDataSize);
|
||||
LUAU_ASSERT(maxTotalSize >= blockSize);
|
||||
}
|
||||
|
||||
|
@ -116,15 +116,15 @@ bool CodeAllocator::allocate(
|
|||
size_t totalSize = alignedDataSize + codeSize;
|
||||
|
||||
// Function has to fit into a single block with unwinding information
|
||||
if (totalSize > blockSize - kMaxUnwindDataSize)
|
||||
if (totalSize > blockSize - kMaxReservedDataSize)
|
||||
return false;
|
||||
|
||||
size_t unwindInfoSize = 0;
|
||||
size_t startOffset = 0;
|
||||
|
||||
// We might need a new block
|
||||
if (totalSize > size_t(blockEnd - blockPos))
|
||||
{
|
||||
if (!allocateNewBlock(unwindInfoSize))
|
||||
if (!allocateNewBlock(startOffset))
|
||||
return false;
|
||||
|
||||
LUAU_ASSERT(totalSize <= size_t(blockEnd - blockPos));
|
||||
|
@ -132,20 +132,20 @@ bool CodeAllocator::allocate(
|
|||
|
||||
LUAU_ASSERT((uintptr_t(blockPos) & (kPageSize - 1)) == 0); // Allocation starts on page boundary
|
||||
|
||||
size_t dataOffset = unwindInfoSize + alignedDataSize - dataSize;
|
||||
size_t codeOffset = unwindInfoSize + alignedDataSize;
|
||||
size_t dataOffset = startOffset + alignedDataSize - dataSize;
|
||||
size_t codeOffset = startOffset + alignedDataSize;
|
||||
|
||||
if (dataSize)
|
||||
memcpy(blockPos + dataOffset, data, dataSize);
|
||||
if (codeSize)
|
||||
memcpy(blockPos + codeOffset, code, codeSize);
|
||||
|
||||
size_t pageAlignedSize = alignToPageSize(unwindInfoSize + totalSize);
|
||||
size_t pageAlignedSize = alignToPageSize(startOffset + totalSize);
|
||||
|
||||
makePagesExecutable(blockPos, pageAlignedSize);
|
||||
flushInstructionCache(blockPos + codeOffset, codeSize);
|
||||
|
||||
result = blockPos + unwindInfoSize;
|
||||
result = blockPos + startOffset;
|
||||
resultSize = totalSize;
|
||||
resultCodeStart = blockPos + codeOffset;
|
||||
|
||||
|
@ -190,7 +190,7 @@ bool CodeAllocator::allocateNewBlock(size_t& unwindInfoSize)
|
|||
// 'Round up' to preserve 16 byte alignment of the following data and code
|
||||
unwindInfoSize = (unwindInfoSize + 15) & ~15;
|
||||
|
||||
LUAU_ASSERT(unwindInfoSize <= kMaxUnwindDataSize);
|
||||
LUAU_ASSERT(unwindInfoSize <= kMaxReservedDataSize);
|
||||
|
||||
if (!unwindInfo)
|
||||
return false;
|
||||
|
|
|
@ -51,7 +51,7 @@ namespace Luau
|
|||
namespace CodeGen
|
||||
{
|
||||
|
||||
void* createBlockUnwindInfo(void* context, uint8_t* block, size_t blockSize, size_t& unwindDataSizeInBlock)
|
||||
void* createBlockUnwindInfo(void* context, uint8_t* block, size_t blockSize, size_t& beginOffset)
|
||||
{
|
||||
#if defined(_WIN32) && defined(_M_X64)
|
||||
UnwindBuilder* unwind = (UnwindBuilder*)context;
|
||||
|
@ -75,7 +75,7 @@ void* createBlockUnwindInfo(void* context, uint8_t* block, size_t blockSize, siz
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
unwindDataSizeInBlock = unwindSize;
|
||||
beginOffset = unwindSize + unwind->getBeginOffset();
|
||||
return block;
|
||||
#elif !defined(_WIN32)
|
||||
UnwindBuilder* unwind = (UnwindBuilder*)context;
|
||||
|
@ -94,7 +94,7 @@ void* createBlockUnwindInfo(void* context, uint8_t* block, size_t blockSize, siz
|
|||
__register_frame(unwindData);
|
||||
#endif
|
||||
|
||||
unwindDataSizeInBlock = unwindSize;
|
||||
beginOffset = unwindSize + unwind->getBeginOffset();
|
||||
return block;
|
||||
#endif
|
||||
|
||||
|
|
|
@ -129,6 +129,16 @@ namespace Luau
|
|||
namespace CodeGen
|
||||
{
|
||||
|
||||
void UnwindBuilderDwarf2::setBeginOffset(size_t beginOffset)
|
||||
{
|
||||
this->beginOffset = beginOffset;
|
||||
}
|
||||
|
||||
size_t UnwindBuilderDwarf2::getBeginOffset() const
|
||||
{
|
||||
return beginOffset;
|
||||
}
|
||||
|
||||
void UnwindBuilderDwarf2::start()
|
||||
{
|
||||
uint8_t* cieLength = pos;
|
||||
|
|
|
@ -32,6 +32,16 @@ struct UnwindInfoWin
|
|||
uint8_t frameregoff : 4;
|
||||
};
|
||||
|
||||
void UnwindBuilderWin::setBeginOffset(size_t beginOffset)
|
||||
{
|
||||
this->beginOffset = beginOffset;
|
||||
}
|
||||
|
||||
size_t UnwindBuilderWin::getBeginOffset() const
|
||||
{
|
||||
return beginOffset;
|
||||
}
|
||||
|
||||
void UnwindBuilderWin::start()
|
||||
{
|
||||
stackOffset = 8; // Return address was pushed by calling the function
|
||||
|
|
|
@ -13,6 +13,7 @@ inline bool isFlagExperimental(const char* flag)
|
|||
static const char* kList[] = {
|
||||
"LuauLowerBoundsCalculation",
|
||||
"LuauInterpolatedStringBaseSupport",
|
||||
"LuauInstantiateInSubtyping", // requires some fixes to lua-apps code
|
||||
// makes sure we always have at least one entry
|
||||
nullptr,
|
||||
};
|
||||
|
|
17
Makefile
17
Makefile
|
@ -4,6 +4,7 @@ MAKEFLAGS+=-r -j8
|
|||
COMMA=,
|
||||
|
||||
config=debug
|
||||
protobuf=system
|
||||
|
||||
BUILD=build/$(config)
|
||||
|
||||
|
@ -95,12 +96,22 @@ ifeq ($(config),fuzz)
|
|||
CXX=clang++ # our fuzzing infra relies on llvm fuzzer
|
||||
CXXFLAGS+=-fsanitize=address,fuzzer -Ibuild/libprotobuf-mutator -O2
|
||||
LDFLAGS+=-fsanitize=address,fuzzer
|
||||
LPROTOBUF=-lprotobuf
|
||||
DPROTOBUF=-D CMAKE_BUILD_TYPE=Release -D LIB_PROTO_MUTATOR_TESTING=OFF
|
||||
EPROTOC=protoc
|
||||
endif
|
||||
|
||||
ifeq ($(config),profile)
|
||||
CXXFLAGS+=-O2 -DNDEBUG -gdwarf-4 -DCALLGRIND=1
|
||||
endif
|
||||
|
||||
ifeq ($(protobuf),download)
|
||||
CXXFLAGS+=-Ibuild/libprotobuf-mutator/external.protobuf/include
|
||||
LPROTOBUF=build/libprotobuf-mutator/external.protobuf/lib/libprotobuf.a
|
||||
DPROTOBUF+=-D LIB_PROTO_MUTATOR_DOWNLOAD_PROTOBUF=ON
|
||||
EPROTOC=../build/libprotobuf-mutator/external.protobuf/bin/protoc
|
||||
endif
|
||||
|
||||
# target-specific flags
|
||||
$(AST_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include
|
||||
$(COMPILER_OBJECTS): CXXFLAGS+=-std=c++17 -ICompiler/include -ICommon/include -IAst/include
|
||||
|
@ -115,7 +126,7 @@ $(FUZZ_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/
|
|||
|
||||
$(TESTS_TARGET): LDFLAGS+=-lpthread
|
||||
$(REPL_CLI_TARGET): LDFLAGS+=-lpthread
|
||||
fuzz-proto fuzz-prototest: LDFLAGS+=build/libprotobuf-mutator/src/libfuzzer/libprotobuf-mutator-libfuzzer.a build/libprotobuf-mutator/src/libprotobuf-mutator.a -lprotobuf
|
||||
fuzz-proto fuzz-prototest: LDFLAGS+=build/libprotobuf-mutator/src/libfuzzer/libprotobuf-mutator-libfuzzer.a build/libprotobuf-mutator/src/libprotobuf-mutator.a $(LPROTOBUF)
|
||||
|
||||
# pseudo targets
|
||||
.PHONY: all test clean coverage format luau-size aliases
|
||||
|
@ -199,7 +210,7 @@ $(BUILD)/%.c.o: %.c
|
|||
|
||||
# protobuf fuzzer setup
|
||||
fuzz/luau.pb.cpp: fuzz/luau.proto build/libprotobuf-mutator
|
||||
cd fuzz && protoc luau.proto --cpp_out=.
|
||||
cd fuzz && $(EPROTOC) luau.proto --cpp_out=.
|
||||
mv fuzz/luau.pb.cc fuzz/luau.pb.cpp
|
||||
|
||||
$(BUILD)/fuzz/proto.cpp.o: fuzz/luau.pb.cpp
|
||||
|
@ -207,7 +218,7 @@ $(BUILD)/fuzz/protoprint.cpp.o: fuzz/luau.pb.cpp
|
|||
|
||||
build/libprotobuf-mutator:
|
||||
git clone https://github.com/google/libprotobuf-mutator build/libprotobuf-mutator
|
||||
CXX= cmake -S build/libprotobuf-mutator -B build/libprotobuf-mutator -D CMAKE_BUILD_TYPE=Release -D LIB_PROTO_MUTATOR_TESTING=OFF
|
||||
CXX= cmake -S build/libprotobuf-mutator -B build/libprotobuf-mutator $(DPROTOBUF)
|
||||
make -C build/libprotobuf-mutator -j8
|
||||
|
||||
# picks up include dependencies for all object files
|
||||
|
|
|
@ -401,13 +401,15 @@ struct lua_Debug
|
|||
const char* name; // (n)
|
||||
const char* what; // (s) `Lua', `C', `main', `tail'
|
||||
const char* source; // (s)
|
||||
const char* short_src; // (s)
|
||||
int linedefined; // (s)
|
||||
int currentline; // (l)
|
||||
unsigned char nupvals; // (u) number of upvalues
|
||||
unsigned char nparams; // (a) number of parameters
|
||||
char isvararg; // (a)
|
||||
char short_src[LUA_IDSIZE]; // (s)
|
||||
void* userdata; // only valid in luau_callhook
|
||||
|
||||
char ssbuf[LUA_IDSIZE];
|
||||
};
|
||||
|
||||
// }======================================================================
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauFasterGetInfo, false)
|
||||
|
||||
static const char* getfuncname(Closure* f);
|
||||
|
||||
static int currentpc(lua_State* L, CallInfo* ci)
|
||||
|
@ -89,9 +91,9 @@ const char* lua_setlocal(lua_State* L, int level, int n)
|
|||
return name;
|
||||
}
|
||||
|
||||
static int auxgetinfo(lua_State* L, const char* what, lua_Debug* ar, Closure* f, CallInfo* ci)
|
||||
static Closure* auxgetinfo(lua_State* L, const char* what, lua_Debug* ar, Closure* f, CallInfo* ci)
|
||||
{
|
||||
int status = 1;
|
||||
Closure* cl = NULL;
|
||||
for (; *what; what++)
|
||||
{
|
||||
switch (*what)
|
||||
|
@ -103,14 +105,23 @@ static int auxgetinfo(lua_State* L, const char* what, lua_Debug* ar, Closure* f,
|
|||
ar->source = "=[C]";
|
||||
ar->what = "C";
|
||||
ar->linedefined = -1;
|
||||
if (FFlag::LuauFasterGetInfo)
|
||||
ar->short_src = "[C]";
|
||||
}
|
||||
else
|
||||
{
|
||||
ar->source = getstr(f->l.p->source);
|
||||
TString* source = f->l.p->source;
|
||||
ar->source = getstr(source);
|
||||
ar->what = "Lua";
|
||||
ar->linedefined = f->l.p->linedefined;
|
||||
if (FFlag::LuauFasterGetInfo)
|
||||
ar->short_src = luaO_chunkid(ar->ssbuf, sizeof(ar->ssbuf), getstr(source), source->len);
|
||||
}
|
||||
if (!FFlag::LuauFasterGetInfo)
|
||||
{
|
||||
luaO_chunkid(ar->ssbuf, LUA_IDSIZE, ar->source, 0);
|
||||
ar->short_src = ar->ssbuf;
|
||||
}
|
||||
luaO_chunkid(ar->short_src, ar->source, LUA_IDSIZE);
|
||||
break;
|
||||
}
|
||||
case 'l':
|
||||
|
@ -150,10 +161,15 @@ static int auxgetinfo(lua_State* L, const char* what, lua_Debug* ar, Closure* f,
|
|||
ar->name = ci ? getfuncname(ci_func(ci)) : getfuncname(f);
|
||||
break;
|
||||
}
|
||||
case 'f':
|
||||
{
|
||||
cl = f;
|
||||
break;
|
||||
}
|
||||
default:;
|
||||
}
|
||||
}
|
||||
return status;
|
||||
return cl;
|
||||
}
|
||||
|
||||
int lua_stackdepth(lua_State* L)
|
||||
|
@ -163,7 +179,6 @@ int lua_stackdepth(lua_State* L)
|
|||
|
||||
int lua_getinfo(lua_State* L, int level, const char* what, lua_Debug* ar)
|
||||
{
|
||||
int status = 0;
|
||||
Closure* f = NULL;
|
||||
CallInfo* ci = NULL;
|
||||
if (level < 0)
|
||||
|
@ -180,15 +195,28 @@ int lua_getinfo(lua_State* L, int level, const char* what, lua_Debug* ar)
|
|||
}
|
||||
if (f)
|
||||
{
|
||||
status = auxgetinfo(L, what, ar, f, ci);
|
||||
if (strchr(what, 'f'))
|
||||
if (FFlag::LuauFasterGetInfo)
|
||||
{
|
||||
luaC_threadbarrier(L);
|
||||
setclvalue(L, L->top, f);
|
||||
incr_top(L);
|
||||
// auxgetinfo fills ar and optionally requests to put closure on stack
|
||||
if (Closure* fcl = auxgetinfo(L, what, ar, f, ci))
|
||||
{
|
||||
luaC_threadbarrier(L);
|
||||
setclvalue(L, L->top, fcl);
|
||||
incr_top(L);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
auxgetinfo(L, what, ar, f, ci);
|
||||
if (strchr(what, 'f'))
|
||||
{
|
||||
luaC_threadbarrier(L);
|
||||
setclvalue(L, L->top, f);
|
||||
incr_top(L);
|
||||
}
|
||||
}
|
||||
}
|
||||
return status;
|
||||
return f ? 1 : 0;
|
||||
}
|
||||
|
||||
static const char* getfuncname(Closure* cl)
|
||||
|
@ -284,10 +312,11 @@ static void pusherror(lua_State* L, const char* msg)
|
|||
CallInfo* ci = L->ci;
|
||||
if (isLua(ci))
|
||||
{
|
||||
char buff[LUA_IDSIZE]; // add file:line information
|
||||
luaO_chunkid(buff, getstr(getluaproto(ci)->source), LUA_IDSIZE);
|
||||
TString* source = getluaproto(ci)->source;
|
||||
char chunkbuf[LUA_IDSIZE]; // add file:line information
|
||||
const char* chunkid = luaO_chunkid(chunkbuf, sizeof(chunkbuf), getstr(source), source->len);
|
||||
int line = currentline(L, ci);
|
||||
luaO_pushfstring(L, "%s:%d: %s", buff, line, msg);
|
||||
luaO_pushfstring(L, "%s:%d: %s", chunkid, line, msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -13,8 +13,6 @@
|
|||
|
||||
#include <string.h>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauBetterThreadMark, false)
|
||||
|
||||
/*
|
||||
* Luau uses an incremental non-generational non-moving mark&sweep garbage collector.
|
||||
*
|
||||
|
@ -473,54 +471,25 @@ static size_t propagatemark(global_State* g)
|
|||
|
||||
bool active = th->isactive || th == th->global->mainthread;
|
||||
|
||||
if (FFlag::LuauBetterThreadMark)
|
||||
traversestack(g, th);
|
||||
|
||||
// active threads will need to be rescanned later to mark new stack writes so we mark them gray again
|
||||
if (active)
|
||||
{
|
||||
traversestack(g, th);
|
||||
th->gclist = g->grayagain;
|
||||
g->grayagain = o;
|
||||
|
||||
// active threads will need to be rescanned later to mark new stack writes so we mark them gray again
|
||||
if (active)
|
||||
{
|
||||
th->gclist = g->grayagain;
|
||||
g->grayagain = o;
|
||||
|
||||
black2gray(o);
|
||||
}
|
||||
|
||||
// the stack needs to be cleared after the last modification of the thread state before sweep begins
|
||||
// if the thread is inactive, we might not see the thread in this cycle so we must clear it now
|
||||
if (!active || g->gcstate == GCSatomic)
|
||||
clearstack(th);
|
||||
|
||||
// we could shrink stack at any time but we opt to do it during initial mark to do that just once per cycle
|
||||
if (g->gcstate == GCSpropagate)
|
||||
shrinkstack(th);
|
||||
black2gray(o);
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Refactor this logic!
|
||||
if (!active && g->gcstate == GCSpropagate)
|
||||
{
|
||||
traversestack(g, th);
|
||||
clearstack(th);
|
||||
}
|
||||
else
|
||||
{
|
||||
th->gclist = g->grayagain;
|
||||
g->grayagain = o;
|
||||
|
||||
black2gray(o);
|
||||
// the stack needs to be cleared after the last modification of the thread state before sweep begins
|
||||
// if the thread is inactive, we might not see the thread in this cycle so we must clear it now
|
||||
if (!active || g->gcstate == GCSatomic)
|
||||
clearstack(th);
|
||||
|
||||
traversestack(g, th);
|
||||
|
||||
// final traversal?
|
||||
if (g->gcstate == GCSatomic)
|
||||
clearstack(th);
|
||||
}
|
||||
|
||||
// we could shrink stack at any time but we opt to skip it during atomic since it's redundant to do that more than once per cycle
|
||||
if (g->gcstate != GCSatomic)
|
||||
shrinkstack(th);
|
||||
}
|
||||
// we could shrink stack at any time but we opt to do it during initial mark to do that just once per cycle
|
||||
if (g->gcstate == GCSpropagate)
|
||||
shrinkstack(th);
|
||||
|
||||
return sizeof(lua_State) + sizeof(TValue) * th->stacksize + sizeof(CallInfo) * th->size_ci;
|
||||
}
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
|
||||
|
||||
|
||||
LUAU_FASTFLAG(LuauFasterGetInfo)
|
||||
|
||||
const TValue luaO_nilobject_ = {{NULL}, {0}, LUA_TNIL};
|
||||
|
||||
int luaO_log2(unsigned int x)
|
||||
|
@ -117,44 +119,68 @@ const char* luaO_pushfstring(lua_State* L, const char* fmt, ...)
|
|||
return msg;
|
||||
}
|
||||
|
||||
void luaO_chunkid(char* out, const char* source, size_t bufflen)
|
||||
const char* luaO_chunkid(char* buf, size_t buflen, const char* source, size_t srclen)
|
||||
{
|
||||
if (*source == '=')
|
||||
{
|
||||
source++; // skip the `='
|
||||
size_t srclen = strlen(source);
|
||||
size_t dstlen = srclen < bufflen ? srclen : bufflen - 1;
|
||||
memcpy(out, source, dstlen);
|
||||
out[dstlen] = '\0';
|
||||
if (FFlag::LuauFasterGetInfo)
|
||||
{
|
||||
if (srclen <= buflen)
|
||||
return source + 1;
|
||||
// truncate the part after =
|
||||
memcpy(buf, source + 1, buflen - 1);
|
||||
buf[buflen - 1] = '\0';
|
||||
}
|
||||
else
|
||||
{
|
||||
source++; // skip the `='
|
||||
size_t len = strlen(source);
|
||||
size_t dstlen = len < buflen ? len : buflen - 1;
|
||||
memcpy(buf, source, dstlen);
|
||||
buf[dstlen] = '\0';
|
||||
}
|
||||
}
|
||||
else if (*source == '@')
|
||||
{
|
||||
size_t l;
|
||||
source++; // skip the `@'
|
||||
bufflen -= sizeof("...");
|
||||
l = strlen(source);
|
||||
strcpy(out, "");
|
||||
if (l > bufflen)
|
||||
if (FFlag::LuauFasterGetInfo)
|
||||
{
|
||||
source += (l - bufflen); // get last part of file name
|
||||
strcat(out, "...");
|
||||
}
|
||||
strcat(out, source);
|
||||
}
|
||||
else
|
||||
{ // out = [string "string"]
|
||||
size_t len = strcspn(source, "\n\r"); // stop at first newline
|
||||
bufflen -= sizeof("[string \"...\"]");
|
||||
if (len > bufflen)
|
||||
len = bufflen;
|
||||
strcpy(out, "[string \"");
|
||||
if (source[len] != '\0')
|
||||
{ // must truncate?
|
||||
strncat(out, source, len);
|
||||
strcat(out, "...");
|
||||
if (srclen <= buflen)
|
||||
return source + 1;
|
||||
// truncate the part after @
|
||||
memcpy(buf, "...", 3);
|
||||
memcpy(buf + 3, source + srclen - (buflen - 4), buflen - 4);
|
||||
buf[buflen - 1] = '\0';
|
||||
}
|
||||
else
|
||||
strcat(out, source);
|
||||
strcat(out, "\"]");
|
||||
{
|
||||
size_t l;
|
||||
source++; // skip the `@'
|
||||
buflen -= sizeof("...");
|
||||
l = strlen(source);
|
||||
strcpy(buf, "");
|
||||
if (l > buflen)
|
||||
{
|
||||
source += (l - buflen); // get last part of file name
|
||||
strcat(buf, "...");
|
||||
}
|
||||
strcat(buf, source);
|
||||
}
|
||||
}
|
||||
else
|
||||
{ // buf = [string "string"]
|
||||
size_t len = strcspn(source, "\n\r"); // stop at first newline
|
||||
buflen -= sizeof("[string \"...\"]");
|
||||
if (len > buflen)
|
||||
len = buflen;
|
||||
strcpy(buf, "[string \"");
|
||||
if (source[len] != '\0')
|
||||
{ // must truncate?
|
||||
strncat(buf, source, len);
|
||||
strcat(buf, "...");
|
||||
}
|
||||
else
|
||||
strcat(buf, source);
|
||||
strcat(buf, "\"]");
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
|
|
@ -460,4 +460,4 @@ LUAI_FUNC int luaO_rawequalKey(const TKey* t1, const TValue* t2);
|
|||
LUAI_FUNC int luaO_str2d(const char* s, double* result);
|
||||
LUAI_FUNC const char* luaO_pushvfstring(lua_State* L, const char* fmt, va_list argp);
|
||||
LUAI_FUNC const char* luaO_pushfstring(lua_State* L, const char* fmt, ...);
|
||||
LUAI_FUNC void luaO_chunkid(char* out, const char* source, size_t len);
|
||||
LUAI_FUNC const char* luaO_chunkid(char* buf, size_t buflen, const char* source, size_t srclen);
|
||||
|
|
|
@ -781,6 +781,7 @@ reentry:
|
|||
|
||||
default:
|
||||
LUAU_ASSERT(!"Unknown upvalue capture type");
|
||||
LUAU_UNREACHABLE(); // improves switch() codegen by eliding opcode bounds checks
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1184,7 +1185,9 @@ reentry:
|
|||
// slow path after switch()
|
||||
break;
|
||||
|
||||
default:;
|
||||
default:
|
||||
LUAU_ASSERT(!"Unknown value type");
|
||||
LUAU_UNREACHABLE(); // improves switch() codegen by eliding opcode bounds checks
|
||||
}
|
||||
|
||||
// slow-path: tables with metatables and userdata values
|
||||
|
@ -1296,7 +1299,9 @@ reentry:
|
|||
// slow path after switch()
|
||||
break;
|
||||
|
||||
default:;
|
||||
default:
|
||||
LUAU_ASSERT(!"Unknown value type");
|
||||
LUAU_UNREACHABLE(); // improves switch() codegen by eliding opcode bounds checks
|
||||
}
|
||||
|
||||
// slow-path: tables with metatables and userdata values
|
||||
|
|
|
@ -148,16 +148,16 @@ int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size
|
|||
// 0 means the rest of the bytecode is the error message
|
||||
if (version == 0)
|
||||
{
|
||||
char chunkid[LUA_IDSIZE];
|
||||
luaO_chunkid(chunkid, chunkname, LUA_IDSIZE);
|
||||
char chunkbuf[LUA_IDSIZE];
|
||||
const char* chunkid = luaO_chunkid(chunkbuf, sizeof(chunkbuf), chunkname, strlen(chunkname));
|
||||
lua_pushfstring(L, "%s%.*s", chunkid, int(size - offset), data + offset);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (version < LBC_VERSION_MIN || version > LBC_VERSION_MAX)
|
||||
{
|
||||
char chunkid[LUA_IDSIZE];
|
||||
luaO_chunkid(chunkid, chunkname, LUA_IDSIZE);
|
||||
char chunkbuf[LUA_IDSIZE];
|
||||
const char* chunkid = luaO_chunkid(chunkbuf, sizeof(chunkbuf), chunkname, strlen(chunkname));
|
||||
lua_pushfstring(L, "%s: bytecode version mismatch (expected [%d..%d], got %d)", chunkid, LBC_VERSION_MIN, LBC_VERSION_MAX, version);
|
||||
return 1;
|
||||
}
|
||||
|
|
|
@ -220,8 +220,13 @@ int luaV_strcmp(const TString* ls, const TString* rs)
|
|||
return 0;
|
||||
|
||||
const char* l = getstr(ls);
|
||||
size_t ll = ls->len;
|
||||
const char* r = getstr(rs);
|
||||
|
||||
// always safe to read one character because even empty strings are nul terminated
|
||||
if (*l != *r)
|
||||
return uint8_t(*l) - uint8_t(*r);
|
||||
|
||||
size_t ll = ls->len;
|
||||
size_t lr = rs->len;
|
||||
size_t lmin = ll < lr ? ll : lr;
|
||||
|
||||
|
|
|
@ -240,12 +240,12 @@ TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "FormsOfLea")
|
|||
|
||||
TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "FormsOfAbsoluteJumps")
|
||||
{
|
||||
SINGLE_COMPARE(jmp(rax), 0x48, 0xff, 0xe0);
|
||||
SINGLE_COMPARE(jmp(r14), 0x49, 0xff, 0xe6);
|
||||
SINGLE_COMPARE(jmp(qword[r14 + rdx * 4]), 0x49, 0xff, 0x24, 0x96);
|
||||
SINGLE_COMPARE(call(rax), 0x48, 0xff, 0xd0);
|
||||
SINGLE_COMPARE(call(r14), 0x49, 0xff, 0xd6);
|
||||
SINGLE_COMPARE(call(qword[r14 + rdx * 4]), 0x49, 0xff, 0x14, 0x96);
|
||||
SINGLE_COMPARE(jmp(rax), 0xff, 0xe0);
|
||||
SINGLE_COMPARE(jmp(r14), 0x41, 0xff, 0xe6);
|
||||
SINGLE_COMPARE(jmp(qword[r14 + rdx * 4]), 0x41, 0xff, 0x24, 0x96);
|
||||
SINGLE_COMPARE(call(rax), 0xff, 0xd0);
|
||||
SINGLE_COMPARE(call(r14), 0x41, 0xff, 0xd6);
|
||||
SINGLE_COMPARE(call(qword[r14 + rdx * 4]), 0x41, 0xff, 0x14, 0x96);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "FormsOfImul")
|
||||
|
|
|
@ -2955,8 +2955,6 @@ local abc = b@1
|
|||
|
||||
TEST_CASE_FIXTURE(ACFixture, "no_incompatible_self_calls_on_class")
|
||||
{
|
||||
ScopedFastFlag selfCallAutocompleteFix3{"LuauSelfCallAutocompleteFix3", true};
|
||||
|
||||
loadDefinition(R"(
|
||||
declare class Foo
|
||||
function one(self): number
|
||||
|
@ -2995,8 +2993,6 @@ t.@1
|
|||
|
||||
TEST_CASE_FIXTURE(ACFixture, "do_compatible_self_calls")
|
||||
{
|
||||
ScopedFastFlag selfCallAutocompleteFix3{"LuauSelfCallAutocompleteFix3", true};
|
||||
|
||||
check(R"(
|
||||
local t = {}
|
||||
function t:m() end
|
||||
|
@ -3011,8 +3007,6 @@ t:@1
|
|||
|
||||
TEST_CASE_FIXTURE(ACFixture, "no_incompatible_self_calls")
|
||||
{
|
||||
ScopedFastFlag selfCallAutocompleteFix3{"LuauSelfCallAutocompleteFix3", true};
|
||||
|
||||
check(R"(
|
||||
local t = {}
|
||||
function t.m() end
|
||||
|
@ -3027,8 +3021,6 @@ t:@1
|
|||
|
||||
TEST_CASE_FIXTURE(ACFixture, "no_incompatible_self_calls_2")
|
||||
{
|
||||
ScopedFastFlag selfCallAutocompleteFix3{"LuauSelfCallAutocompleteFix3", true};
|
||||
|
||||
check(R"(
|
||||
local f: (() -> number) & ((number) -> number) = function(x: number?) return 2 end
|
||||
local t = {}
|
||||
|
@ -3059,8 +3051,6 @@ t:@1
|
|||
|
||||
TEST_CASE_FIXTURE(ACFixture, "no_wrong_compatible_self_calls_with_generics")
|
||||
{
|
||||
ScopedFastFlag selfCallAutocompleteFix3{"LuauSelfCallAutocompleteFix3", true};
|
||||
|
||||
check(R"(
|
||||
local t = {}
|
||||
function t.m<T>(a: T) end
|
||||
|
@ -3076,8 +3066,6 @@ t:@1
|
|||
|
||||
TEST_CASE_FIXTURE(ACFixture, "string_prim_self_calls_are_fine")
|
||||
{
|
||||
ScopedFastFlag selfCallAutocompleteFix3{"LuauSelfCallAutocompleteFix3", true};
|
||||
|
||||
check(R"(
|
||||
local s = "hello"
|
||||
s:@1
|
||||
|
@ -3095,8 +3083,6 @@ s:@1
|
|||
|
||||
TEST_CASE_FIXTURE(ACFixture, "string_prim_non_self_calls_are_avoided")
|
||||
{
|
||||
ScopedFastFlag selfCallAutocompleteFix3{"LuauSelfCallAutocompleteFix3", true};
|
||||
|
||||
check(R"(
|
||||
local s = "hello"
|
||||
s.@1
|
||||
|
@ -3112,8 +3098,6 @@ s.@1
|
|||
|
||||
TEST_CASE_FIXTURE(ACBuiltinsFixture, "library_non_self_calls_are_fine")
|
||||
{
|
||||
ScopedFastFlag selfCallAutocompleteFix3{"LuauSelfCallAutocompleteFix3", true};
|
||||
|
||||
check(R"(
|
||||
string.@1
|
||||
)");
|
||||
|
@ -3143,8 +3127,6 @@ table.@1
|
|||
|
||||
TEST_CASE_FIXTURE(ACBuiltinsFixture, "library_self_calls_are_invalid")
|
||||
{
|
||||
ScopedFastFlag selfCallAutocompleteFix3{"LuauSelfCallAutocompleteFix3", true};
|
||||
|
||||
check(R"(
|
||||
string:@1
|
||||
)");
|
||||
|
|
|
@ -96,12 +96,12 @@ TEST_CASE("CodeAllocationWithUnwindCallbacks")
|
|||
data.resize(8);
|
||||
|
||||
allocator.context = &info;
|
||||
allocator.createBlockUnwindInfo = [](void* context, uint8_t* block, size_t blockSize, size_t& unwindDataSizeInBlock) -> void* {
|
||||
allocator.createBlockUnwindInfo = [](void* context, uint8_t* block, size_t blockSize, size_t& beginOffset) -> void* {
|
||||
Info& info = *(Info*)context;
|
||||
|
||||
CHECK(info.unwind.size() == 8);
|
||||
memcpy(block, info.unwind.data(), info.unwind.size());
|
||||
unwindDataSizeInBlock = 8;
|
||||
beginOffset = 8;
|
||||
|
||||
info.block = block;
|
||||
|
||||
|
@ -194,10 +194,12 @@ TEST_CASE("Dwarf2UnwindCodesX64")
|
|||
// Windows x64 ABI
|
||||
constexpr RegisterX64 rArg1 = rcx;
|
||||
constexpr RegisterX64 rArg2 = rdx;
|
||||
constexpr RegisterX64 rArg3 = r8;
|
||||
#else
|
||||
// System V AMD64 ABI
|
||||
constexpr RegisterX64 rArg1 = rdi;
|
||||
constexpr RegisterX64 rArg2 = rsi;
|
||||
constexpr RegisterX64 rArg3 = rdx;
|
||||
#endif
|
||||
|
||||
constexpr RegisterX64 rNonVol1 = r12;
|
||||
|
@ -313,6 +315,119 @@ TEST_CASE("GeneratedCodeExecutionWithThrow")
|
|||
}
|
||||
}
|
||||
|
||||
TEST_CASE("GeneratedCodeExecutionWithThrowOutsideTheGate")
|
||||
{
|
||||
AssemblyBuilderX64 build(/* logText= */ false);
|
||||
|
||||
#if defined(_WIN32)
|
||||
std::unique_ptr<UnwindBuilder> unwind = std::make_unique<UnwindBuilderWin>();
|
||||
#else
|
||||
std::unique_ptr<UnwindBuilder> unwind = std::make_unique<UnwindBuilderDwarf2>();
|
||||
#endif
|
||||
|
||||
unwind->start();
|
||||
|
||||
// Prologue (some of these registers don't have to be saved, but we want to have a big prologue)
|
||||
build.push(r10);
|
||||
unwind->save(r10);
|
||||
build.push(r11);
|
||||
unwind->save(r11);
|
||||
build.push(r12);
|
||||
unwind->save(r12);
|
||||
build.push(r13);
|
||||
unwind->save(r13);
|
||||
build.push(r14);
|
||||
unwind->save(r14);
|
||||
build.push(r15);
|
||||
unwind->save(r15);
|
||||
build.push(rbp);
|
||||
unwind->save(rbp);
|
||||
|
||||
int stackSize = 64;
|
||||
int localsSize = 16;
|
||||
|
||||
build.sub(rsp, stackSize + localsSize);
|
||||
unwind->allocStack(stackSize + localsSize);
|
||||
|
||||
build.lea(rbp, qword[rsp + stackSize]);
|
||||
unwind->setupFrameReg(rbp, stackSize);
|
||||
|
||||
unwind->finish();
|
||||
|
||||
size_t prologueSize = build.setLabel().location;
|
||||
|
||||
// Body
|
||||
build.mov(rax, rArg1);
|
||||
build.mov(rArg1, 25);
|
||||
build.jmp(rax);
|
||||
|
||||
Label returnOffset = build.setLabel();
|
||||
|
||||
// Epilogue
|
||||
build.lea(rsp, qword[rbp + localsSize]);
|
||||
build.pop(rbp);
|
||||
build.pop(r15);
|
||||
build.pop(r14);
|
||||
build.pop(r13);
|
||||
build.pop(r12);
|
||||
build.pop(r11);
|
||||
build.pop(r10);
|
||||
build.ret();
|
||||
|
||||
build.finalize();
|
||||
|
||||
size_t blockSize = 4096; // Force allocate to create a new block each time
|
||||
size_t maxTotalSize = 1024 * 1024;
|
||||
CodeAllocator allocator(blockSize, maxTotalSize);
|
||||
|
||||
allocator.context = unwind.get();
|
||||
allocator.createBlockUnwindInfo = createBlockUnwindInfo;
|
||||
allocator.destroyBlockUnwindInfo = destroyBlockUnwindInfo;
|
||||
|
||||
uint8_t* nativeData1;
|
||||
size_t sizeNativeData1;
|
||||
uint8_t* nativeEntry1;
|
||||
REQUIRE(
|
||||
allocator.allocate(build.data.data(), build.data.size(), build.code.data(), build.code.size(), nativeData1, sizeNativeData1, nativeEntry1));
|
||||
REQUIRE(nativeEntry1);
|
||||
|
||||
// Now we set the offset at the begining so that functions in new blocks will not overlay the locations
|
||||
// specified by the unwind information of the entry function
|
||||
unwind->setBeginOffset(prologueSize);
|
||||
|
||||
using FunctionType = int64_t(void*, void (*)(int64_t), void*);
|
||||
FunctionType* f = (FunctionType*)nativeEntry1;
|
||||
|
||||
uint8_t* nativeExit = nativeEntry1 + returnOffset.location;
|
||||
|
||||
AssemblyBuilderX64 build2(/* logText= */ false);
|
||||
|
||||
build2.mov(r12, rArg3);
|
||||
build2.call(rArg2);
|
||||
build2.jmp(r12);
|
||||
|
||||
build2.finalize();
|
||||
|
||||
uint8_t* nativeData2;
|
||||
size_t sizeNativeData2;
|
||||
uint8_t* nativeEntry2;
|
||||
REQUIRE(allocator.allocate(
|
||||
build2.data.data(), build2.data.size(), build2.code.data(), build2.code.size(), nativeData2, sizeNativeData2, nativeEntry2));
|
||||
REQUIRE(nativeEntry2);
|
||||
|
||||
// To simplify debugging, CHECK_THROWS_WITH_AS is not used here
|
||||
try
|
||||
{
|
||||
f(nativeEntry2, throwing, nativeExit);
|
||||
}
|
||||
catch (const std::runtime_error& error)
|
||||
{
|
||||
CHECK(strcmp(error.what(), "testing") == 0);
|
||||
}
|
||||
|
||||
REQUIRE(nativeEntry2);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -28,8 +28,11 @@ TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "hello")
|
|||
cgb.visit(block);
|
||||
NotNull<Scope> rootScope{cgb.rootScope};
|
||||
|
||||
InternalErrorReporter iceHandler;
|
||||
UnifierSharedState sharedState{&iceHandler};
|
||||
Normalizer normalizer{&arena, singletonTypes, NotNull{&sharedState}};
|
||||
NullModuleResolver resolver;
|
||||
ConstraintSolver cs{&arena, singletonTypes, rootScope, "MainModule", NotNull(&resolver), {}, &logger};
|
||||
ConstraintSolver cs{NotNull{&normalizer}, rootScope, "MainModule", NotNull(&resolver), {}, &logger};
|
||||
|
||||
cs.run();
|
||||
|
||||
|
@ -49,9 +52,11 @@ TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "generic_function")
|
|||
cgb.visit(block);
|
||||
NotNull<Scope> rootScope{cgb.rootScope};
|
||||
|
||||
InternalErrorReporter iceHandler;
|
||||
UnifierSharedState sharedState{&iceHandler};
|
||||
Normalizer normalizer{&arena, singletonTypes, NotNull{&sharedState}};
|
||||
NullModuleResolver resolver;
|
||||
ConstraintSolver cs{&arena, singletonTypes, rootScope, "MainModule", NotNull(&resolver), {}, &logger};
|
||||
|
||||
ConstraintSolver cs{NotNull{&normalizer}, rootScope, "MainModule", NotNull(&resolver), {}, &logger};
|
||||
cs.run();
|
||||
|
||||
TypeId idType = requireBinding(rootScope, "id");
|
||||
|
@ -79,7 +84,10 @@ TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "proper_let_generalization")
|
|||
ToStringOptions opts;
|
||||
|
||||
NullModuleResolver resolver;
|
||||
ConstraintSolver cs{&arena, singletonTypes, rootScope, "MainModule", NotNull(&resolver), {}, &logger};
|
||||
InternalErrorReporter iceHandler;
|
||||
UnifierSharedState sharedState{&iceHandler};
|
||||
Normalizer normalizer{&arena, singletonTypes, NotNull{&sharedState}};
|
||||
ConstraintSolver cs{NotNull{&normalizer}, rootScope, "MainModule", NotNull(&resolver), {}, &logger};
|
||||
|
||||
cs.run();
|
||||
|
||||
|
|
|
@ -22,6 +22,8 @@ LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
|||
LUAU_FASTFLAG(LuauUnknownAndNeverType)
|
||||
LUAU_FASTFLAG(LuauReportShadowedTypeAlias)
|
||||
|
||||
extern std::optional<unsigned> randomSeed; // tests/main.cpp
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
@ -90,7 +92,7 @@ std::optional<std::string> TestFileResolver::getEnvironmentForModule(const Modul
|
|||
|
||||
Fixture::Fixture(bool freeze, bool prepareAutocomplete)
|
||||
: sff_DebugLuauFreezeArena("DebugLuauFreezeArena", freeze)
|
||||
, frontend(&fileResolver, &configResolver, {/* retainFullTypeGraphs= */ true})
|
||||
, frontend(&fileResolver, &configResolver, {/* retainFullTypeGraphs= */ true, /* forAutocomplete */ false, /* randomConstraintResolutionSeed */ randomSeed})
|
||||
, typeChecker(frontend.typeChecker)
|
||||
, singletonTypes(frontend.singletonTypes)
|
||||
{
|
||||
|
|
|
@ -21,14 +21,14 @@ end
|
|||
return math.max(fib(5), 1)
|
||||
)");
|
||||
|
||||
CHECK_EQ(result.warnings.size(), 0);
|
||||
REQUIRE(0 == result.warnings.size());
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "UnknownGlobal")
|
||||
{
|
||||
LintResult result = lint("--!nocheck\nreturn foo");
|
||||
|
||||
REQUIRE_EQ(result.warnings.size(), 1);
|
||||
REQUIRE(1 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].text, "Unknown global 'foo'");
|
||||
}
|
||||
|
||||
|
@ -39,7 +39,7 @@ TEST_CASE_FIXTURE(Fixture, "DeprecatedGlobal")
|
|||
|
||||
LintResult result = lintTyped("Wait(5)");
|
||||
|
||||
REQUIRE_EQ(result.warnings.size(), 1);
|
||||
REQUIRE(1 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].text, "Global 'Wait' is deprecated, use 'wait' instead");
|
||||
}
|
||||
|
||||
|
@ -53,7 +53,7 @@ TEST_CASE_FIXTURE(Fixture, "DeprecatedGlobalNoReplacement")
|
|||
|
||||
LintResult result = lintTyped("Version()");
|
||||
|
||||
REQUIRE_EQ(result.warnings.size(), 1);
|
||||
REQUIRE(1 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].text, "Global 'Version' is deprecated");
|
||||
}
|
||||
|
||||
|
@ -64,7 +64,7 @@ local _ = 5
|
|||
return _
|
||||
)");
|
||||
|
||||
CHECK_EQ(result.warnings.size(), 1);
|
||||
REQUIRE(1 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].text, "Placeholder value '_' is read here; consider using a named variable");
|
||||
}
|
||||
|
||||
|
@ -75,7 +75,7 @@ _ = 5
|
|||
print(_)
|
||||
)");
|
||||
|
||||
CHECK_EQ(result.warnings.size(), 1);
|
||||
REQUIRE(1 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].text, "Placeholder value '_' is read here; consider using a named variable");
|
||||
}
|
||||
|
||||
|
@ -86,7 +86,7 @@ local _ = 5
|
|||
_ = 6
|
||||
)");
|
||||
|
||||
CHECK_EQ(result.warnings.size(), 0);
|
||||
REQUIRE(0 == result.warnings.size());
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "BuiltinGlobalWrite")
|
||||
|
@ -100,7 +100,7 @@ end
|
|||
assert(5)
|
||||
)");
|
||||
|
||||
CHECK_EQ(result.warnings.size(), 2);
|
||||
REQUIRE(2 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].text, "Built-in global 'math' is overwritten here; consider using a local or changing the name");
|
||||
CHECK_EQ(result.warnings[1].text, "Built-in global 'assert' is overwritten here; consider using a local or changing the name");
|
||||
}
|
||||
|
@ -111,7 +111,7 @@ TEST_CASE_FIXTURE(Fixture, "MultilineBlock")
|
|||
if true then print(1) print(2) print(3) end
|
||||
)");
|
||||
|
||||
CHECK_EQ(result.warnings.size(), 1);
|
||||
REQUIRE(1 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].text, "A new statement is on the same line; add semi-colon on previous statement to silence");
|
||||
}
|
||||
|
||||
|
@ -121,7 +121,7 @@ TEST_CASE_FIXTURE(Fixture, "MultilineBlockSemicolonsWhitelisted")
|
|||
print(1); print(2); print(3)
|
||||
)");
|
||||
|
||||
CHECK(result.warnings.empty());
|
||||
REQUIRE(0 == result.warnings.size());
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "MultilineBlockMissedSemicolon")
|
||||
|
@ -130,7 +130,7 @@ TEST_CASE_FIXTURE(Fixture, "MultilineBlockMissedSemicolon")
|
|||
print(1); print(2) print(3)
|
||||
)");
|
||||
|
||||
CHECK_EQ(result.warnings.size(), 1);
|
||||
REQUIRE(1 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].text, "A new statement is on the same line; add semi-colon on previous statement to silence");
|
||||
}
|
||||
|
||||
|
@ -142,7 +142,7 @@ local _x do
|
|||
end
|
||||
)");
|
||||
|
||||
CHECK_EQ(result.warnings.size(), 0);
|
||||
REQUIRE(0 == result.warnings.size());
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "ConfusingIndentation")
|
||||
|
@ -152,7 +152,7 @@ print(math.max(1,
|
|||
2))
|
||||
)");
|
||||
|
||||
CHECK_EQ(result.warnings.size(), 1);
|
||||
REQUIRE(1 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].text, "Statement spans multiple lines; use indentation to silence");
|
||||
}
|
||||
|
||||
|
@ -167,7 +167,7 @@ end
|
|||
return bar()
|
||||
)");
|
||||
|
||||
CHECK_EQ(result.warnings.size(), 1);
|
||||
REQUIRE(1 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].text, "Global 'foo' is only used in the enclosing function 'bar'; consider changing it to local");
|
||||
}
|
||||
|
||||
|
@ -188,7 +188,7 @@ end
|
|||
return bar() + baz()
|
||||
)");
|
||||
|
||||
REQUIRE_EQ(result.warnings.size(), 1);
|
||||
REQUIRE(1 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].text, "Global 'foo' is never read before being written. Consider changing it to local");
|
||||
}
|
||||
|
||||
|
@ -213,7 +213,7 @@ end
|
|||
return bar() + baz() + read()
|
||||
)");
|
||||
|
||||
CHECK_EQ(result.warnings.size(), 0);
|
||||
REQUIRE(0 == result.warnings.size());
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "GlobalAsLocalWithConditional")
|
||||
|
@ -233,7 +233,7 @@ end
|
|||
return bar() + baz()
|
||||
)");
|
||||
|
||||
CHECK_EQ(result.warnings.size(), 0);
|
||||
REQUIRE(0 == result.warnings.size());
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "GlobalAsLocal3WithConditionalRead")
|
||||
|
@ -257,7 +257,7 @@ end
|
|||
return bar() + baz() + read()
|
||||
)");
|
||||
|
||||
CHECK_EQ(result.warnings.size(), 0);
|
||||
REQUIRE(0 == result.warnings.size());
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "GlobalAsLocalInnerRead")
|
||||
|
@ -275,7 +275,7 @@ function baz() bar = 0 end
|
|||
return foo() + baz()
|
||||
)");
|
||||
|
||||
CHECK_EQ(result.warnings.size(), 0);
|
||||
REQUIRE(0 == result.warnings.size());
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "GlobalAsLocalMulti")
|
||||
|
@ -304,7 +304,7 @@ fnA() -- prints "true", "nil"
|
|||
fnB() -- prints "false", "nil"
|
||||
)");
|
||||
|
||||
CHECK_EQ(result.warnings.size(), 1);
|
||||
REQUIRE(1 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].text,
|
||||
"Global 'moreInternalLogic' is only used in the enclosing function defined at line 2; consider changing it to local");
|
||||
}
|
||||
|
@ -319,7 +319,7 @@ local arg = 5
|
|||
print(arg)
|
||||
)");
|
||||
|
||||
CHECK_EQ(result.warnings.size(), 1);
|
||||
REQUIRE(1 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].text, "Variable 'arg' shadows previous declaration at line 2");
|
||||
}
|
||||
|
||||
|
@ -337,7 +337,7 @@ end
|
|||
return bar()
|
||||
)");
|
||||
|
||||
CHECK_EQ(result.warnings.size(), 1);
|
||||
REQUIRE(1 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].text, "Variable 'global' shadows a global variable used at line 3");
|
||||
}
|
||||
|
||||
|
@ -352,7 +352,7 @@ end
|
|||
return bar()
|
||||
)");
|
||||
|
||||
CHECK_EQ(result.warnings.size(), 1);
|
||||
REQUIRE(1 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].text, "Variable 'a' shadows previous declaration at line 2");
|
||||
}
|
||||
|
||||
|
@ -372,7 +372,7 @@ end
|
|||
return bar()
|
||||
)");
|
||||
|
||||
CHECK_EQ(result.warnings.size(), 2);
|
||||
REQUIRE(2 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].text, "Variable 'arg' is never used; prefix with '_' to silence");
|
||||
CHECK_EQ(result.warnings[1].text, "Variable 'blarg' is never used; prefix with '_' to silence");
|
||||
}
|
||||
|
@ -387,7 +387,7 @@ local Roact = require(game.Packages.Roact)
|
|||
local _Roact = require(game.Packages.Roact)
|
||||
)");
|
||||
|
||||
CHECK_EQ(result.warnings.size(), 1);
|
||||
REQUIRE(1 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].text, "Import 'Roact' is never used; prefix with '_' to silence");
|
||||
}
|
||||
|
||||
|
@ -412,7 +412,7 @@ end
|
|||
return foo()
|
||||
)");
|
||||
|
||||
CHECK_EQ(result.warnings.size(), 2);
|
||||
REQUIRE(2 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].text, "Function 'bar' is never used; prefix with '_' to silence");
|
||||
CHECK_EQ(result.warnings[1].text, "Function 'qux' is never used; prefix with '_' to silence");
|
||||
}
|
||||
|
@ -427,7 +427,7 @@ end
|
|||
print("hi!")
|
||||
)");
|
||||
|
||||
CHECK_EQ(result.warnings.size(), 1);
|
||||
REQUIRE(1 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].location.begin.line, 5);
|
||||
CHECK_EQ(result.warnings[0].text, "Unreachable code (previous statement always returns)");
|
||||
}
|
||||
|
@ -443,7 +443,7 @@ end
|
|||
print("hi!")
|
||||
)");
|
||||
|
||||
CHECK_EQ(result.warnings.size(), 1);
|
||||
REQUIRE(1 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].location.begin.line, 3);
|
||||
CHECK_EQ(result.warnings[0].text, "Unreachable code (previous statement always breaks)");
|
||||
}
|
||||
|
@ -459,7 +459,7 @@ end
|
|||
print("hi!")
|
||||
)");
|
||||
|
||||
CHECK_EQ(result.warnings.size(), 1);
|
||||
REQUIRE(1 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].location.begin.line, 3);
|
||||
CHECK_EQ(result.warnings[0].text, "Unreachable code (previous statement always continues)");
|
||||
}
|
||||
|
@ -495,7 +495,7 @@ end
|
|||
return { foo1, foo2, foo3 }
|
||||
)");
|
||||
|
||||
CHECK_EQ(result.warnings.size(), 1);
|
||||
REQUIRE(1 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].location.begin.line, 7);
|
||||
CHECK_EQ(result.warnings[0].text, "Unreachable code (previous statement always returns)");
|
||||
}
|
||||
|
@ -515,7 +515,7 @@ end
|
|||
return foo1
|
||||
)");
|
||||
|
||||
CHECK_EQ(result.warnings.size(), 0);
|
||||
REQUIRE(0 == result.warnings.size());
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "UnreachableCodeAssertFalseReturnSilent")
|
||||
|
@ -532,7 +532,7 @@ end
|
|||
return foo1
|
||||
)");
|
||||
|
||||
CHECK_EQ(result.warnings.size(), 0);
|
||||
REQUIRE(0 == result.warnings.size());
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "UnreachableCodeErrorReturnNonSilentBranchy")
|
||||
|
@ -550,7 +550,7 @@ end
|
|||
return foo1
|
||||
)");
|
||||
|
||||
CHECK_EQ(result.warnings.size(), 1);
|
||||
REQUIRE(1 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].location.begin.line, 7);
|
||||
CHECK_EQ(result.warnings[0].text, "Unreachable code (previous statement always errors)");
|
||||
}
|
||||
|
@ -571,7 +571,7 @@ end
|
|||
return foo1
|
||||
)");
|
||||
|
||||
CHECK_EQ(result.warnings.size(), 1);
|
||||
REQUIRE(1 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].location.begin.line, 8);
|
||||
CHECK_EQ(result.warnings[0].text, "Unreachable code (previous statement always errors)");
|
||||
}
|
||||
|
@ -589,7 +589,7 @@ end
|
|||
return foo1
|
||||
)");
|
||||
|
||||
CHECK_EQ(result.warnings.size(), 0);
|
||||
REQUIRE(0 == result.warnings.size());
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "UnreachableCodeLoopRepeat")
|
||||
|
@ -605,8 +605,8 @@ end
|
|||
return foo1
|
||||
)");
|
||||
|
||||
CHECK_EQ(result.warnings.size(),
|
||||
0); // this is technically a bug, since the repeat body always returns; fixing this bug is a bit more involved than I'd like
|
||||
// this is technically a bug, since the repeat body always returns; fixing this bug is a bit more involved than I'd like
|
||||
REQUIRE(0 == result.warnings.size());
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "UnknownType")
|
||||
|
@ -633,7 +633,7 @@ local _o02 = type(game) == "vector"
|
|||
local _o03 = typeof(game) == "Part"
|
||||
)");
|
||||
|
||||
REQUIRE_EQ(result.warnings.size(), 3);
|
||||
REQUIRE(3 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].location.begin.line, 2);
|
||||
CHECK_EQ(result.warnings[0].text, "Unknown type 'Part' (expected primitive type)");
|
||||
CHECK_EQ(result.warnings[1].location.begin.line, 3);
|
||||
|
@ -654,7 +654,7 @@ for i=#t,1,-1 do
|
|||
end
|
||||
)");
|
||||
|
||||
CHECK_EQ(result.warnings.size(), 1);
|
||||
REQUIRE(1 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].location.begin.line, 3);
|
||||
CHECK_EQ(result.warnings[0].text, "For loop should iterate backwards; did you forget to specify -1 as step?");
|
||||
}
|
||||
|
@ -669,7 +669,7 @@ for i=8,1,-1 do
|
|||
end
|
||||
)");
|
||||
|
||||
CHECK_EQ(result.warnings.size(), 1);
|
||||
REQUIRE(1 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].location.begin.line, 1);
|
||||
CHECK_EQ(result.warnings[0].text, "For loop should iterate backwards; did you forget to specify -1 as step?");
|
||||
}
|
||||
|
@ -684,7 +684,7 @@ for i=1.3,7.5,1 do
|
|||
end
|
||||
)");
|
||||
|
||||
CHECK_EQ(result.warnings.size(), 1);
|
||||
REQUIRE(1 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].location.begin.line, 1);
|
||||
CHECK_EQ(result.warnings[0].text, "For loop ends at 7.3 instead of 7.5; did you forget to specify step?");
|
||||
}
|
||||
|
@ -702,7 +702,7 @@ for i=#t,0 do
|
|||
end
|
||||
)");
|
||||
|
||||
CHECK_EQ(result.warnings.size(), 2);
|
||||
REQUIRE(2 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].location.begin.line, 1);
|
||||
CHECK_EQ(result.warnings[0].text, "For loop starts at 0, but arrays start at 1");
|
||||
CHECK_EQ(result.warnings[1].location.begin.line, 7);
|
||||
|
@ -730,7 +730,7 @@ local _a,_b,_c = pcall(), nil
|
|||
end
|
||||
)");
|
||||
|
||||
CHECK_EQ(result.warnings.size(), 2);
|
||||
REQUIRE(2 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].location.begin.line, 5);
|
||||
CHECK_EQ(result.warnings[0].text, "Assigning 2 values to 3 variables initializes extra variables with nil; add 'nil' to value list to silence");
|
||||
CHECK_EQ(result.warnings[1].location.begin.line, 11);
|
||||
|
@ -795,7 +795,7 @@ end
|
|||
return f1,f2,f3,f4,f5,f6,f7
|
||||
)");
|
||||
|
||||
CHECK_EQ(result.warnings.size(), 3);
|
||||
REQUIRE(3 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].location.begin.line, 4);
|
||||
CHECK_EQ(result.warnings[0].text,
|
||||
"Function 'f1' can implicitly return no values even though there's an explicit return at line 4; add explicit return to silence");
|
||||
|
@ -851,7 +851,7 @@ end
|
|||
return f1,f2,f3,f4
|
||||
)");
|
||||
|
||||
CHECK_EQ(result.warnings.size(), 2);
|
||||
REQUIRE(2 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].location.begin.line, 25);
|
||||
CHECK_EQ(result.warnings[0].text,
|
||||
"Function 'f3' can implicitly return no values even though there's an explicit return at line 21; add explicit return to silence");
|
||||
|
@ -874,7 +874,7 @@ type InputData = {
|
|||
}
|
||||
)");
|
||||
|
||||
CHECK_EQ(result.warnings.size(), 0);
|
||||
REQUIRE(0 == result.warnings.size());
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "BreakFromInfiniteLoopMakesStatementReachable")
|
||||
|
@ -893,7 +893,7 @@ until true
|
|||
return 1
|
||||
)");
|
||||
|
||||
CHECK_EQ(result.warnings.size(), 0);
|
||||
REQUIRE(0 == result.warnings.size());
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "IgnoreLintAll")
|
||||
|
@ -903,7 +903,7 @@ TEST_CASE_FIXTURE(Fixture, "IgnoreLintAll")
|
|||
return foo
|
||||
)");
|
||||
|
||||
CHECK_EQ(result.warnings.size(), 0);
|
||||
REQUIRE(0 == result.warnings.size());
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "IgnoreLintSpecific")
|
||||
|
@ -914,7 +914,7 @@ local x = 1
|
|||
return foo
|
||||
)");
|
||||
|
||||
CHECK_EQ(result.warnings.size(), 1);
|
||||
REQUIRE(1 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].text, "Variable 'x' is never used; prefix with '_' to silence");
|
||||
}
|
||||
|
||||
|
@ -933,7 +933,7 @@ local _ = ("%"):format()
|
|||
string.format("hello %+10d %.02f %%", 4, 5)
|
||||
)");
|
||||
|
||||
CHECK_EQ(result.warnings.size(), 4);
|
||||
REQUIRE(4 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].text, "Invalid format string: unfinished format specifier");
|
||||
CHECK_EQ(result.warnings[1].text, "Invalid format string: invalid format specifier: must be a string format specifier or %");
|
||||
CHECK_EQ(result.warnings[2].text, "Invalid format string: invalid format specifier: must be a string format specifier or %");
|
||||
|
@ -973,7 +973,7 @@ string.packsize("c99999999999999999999")
|
|||
string.packsize("=!1bbbI3c42")
|
||||
)");
|
||||
|
||||
CHECK_EQ(result.warnings.size(), 11);
|
||||
REQUIRE(11 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].text, "Invalid pack format: unexpected character; must be a pack specifier or space");
|
||||
CHECK_EQ(result.warnings[1].text, "Invalid pack format: unexpected character; must be a pack specifier or space");
|
||||
CHECK_EQ(result.warnings[2].text, "Invalid pack format: unexpected character; must be a pack specifier or space");
|
||||
|
@ -1017,7 +1017,7 @@ local _ = s:match("%q")
|
|||
string.match(s, "[A-Z]+(%d)%1")
|
||||
)");
|
||||
|
||||
CHECK_EQ(result.warnings.size(), 14);
|
||||
REQUIRE(14 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].text, "Invalid match pattern: invalid character class, must refer to a defined class or its inverse");
|
||||
CHECK_EQ(result.warnings[1].text, "Invalid match pattern: invalid character class, must refer to a defined class or its inverse");
|
||||
CHECK_EQ(result.warnings[2].text, "Invalid match pattern: invalid character class, must refer to a defined class or its inverse");
|
||||
|
@ -1049,7 +1049,7 @@ string.match(s, "((a)%1)")
|
|||
string.match(s, "((a)%3)")
|
||||
)~");
|
||||
|
||||
CHECK_EQ(result.warnings.size(), 2);
|
||||
REQUIRE(2 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].text, "Invalid match pattern: invalid capture reference, must refer to a closed capture");
|
||||
CHECK_EQ(result.warnings[0].location.begin.line, 7);
|
||||
CHECK_EQ(result.warnings[1].text, "Invalid match pattern: invalid capture reference, must refer to a valid capture");
|
||||
|
@ -1087,7 +1087,7 @@ string.match(s, "[]|'[]")
|
|||
string.match(s, "[^]|'[]")
|
||||
)~");
|
||||
|
||||
CHECK_EQ(result.warnings.size(), 7);
|
||||
REQUIRE(7 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].text, "Invalid match pattern: expected ] at the end of the string to close a set");
|
||||
CHECK_EQ(result.warnings[1].text, "Invalid match pattern: expected ] at the end of the string to close a set");
|
||||
CHECK_EQ(result.warnings[2].text, "Invalid match pattern: character range can't include character sets");
|
||||
|
@ -1118,7 +1118,7 @@ string.find("foo");
|
|||
("foo"):find()
|
||||
)");
|
||||
|
||||
CHECK_EQ(result.warnings.size(), 2);
|
||||
REQUIRE(2 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].text, "Invalid match pattern: invalid character class, must refer to a defined class or its inverse");
|
||||
CHECK_EQ(result.warnings[0].location.begin.line, 4);
|
||||
CHECK_EQ(result.warnings[1].text, "Invalid match pattern: invalid character class, must refer to a defined class or its inverse");
|
||||
|
@ -1141,7 +1141,7 @@ string.gsub(s, '[A-Z]+(%d)', "%0%1")
|
|||
string.gsub(s, 'foo', "%0")
|
||||
)");
|
||||
|
||||
CHECK_EQ(result.warnings.size(), 4);
|
||||
REQUIRE(4 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].text, "Invalid match replacement: unfinished replacement");
|
||||
CHECK_EQ(result.warnings[1].text, "Invalid match replacement: unexpected replacement character; must be a digit or %");
|
||||
CHECK_EQ(result.warnings[2].text, "Invalid match replacement: invalid capture index, must refer to pattern capture");
|
||||
|
@ -1162,7 +1162,7 @@ os.date("it's %c now")
|
|||
os.date("!*t")
|
||||
)");
|
||||
|
||||
CHECK_EQ(result.warnings.size(), 4);
|
||||
REQUIRE(4 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].text, "Invalid date format: unfinished replacement");
|
||||
CHECK_EQ(result.warnings[1].text, "Invalid date format: unexpected replacement character; must be a date format specifier or %");
|
||||
CHECK_EQ(result.warnings[2].text, "Invalid date format: unexpected replacement character; must be a date format specifier or %");
|
||||
|
@ -1181,7 +1181,7 @@ s:match("[]")
|
|||
nons:match("[]")
|
||||
)~");
|
||||
|
||||
REQUIRE_EQ(result.warnings.size(), 2);
|
||||
REQUIRE(2 == result.warnings.size());
|
||||
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");
|
||||
|
@ -1231,7 +1231,7 @@ _ = {
|
|||
}
|
||||
)");
|
||||
|
||||
CHECK_EQ(result.warnings.size(), 6);
|
||||
REQUIRE(6 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].text, "Table field 'first' is a duplicate; previously defined at line 3");
|
||||
CHECK_EQ(result.warnings[1].text, "Table field 'first' is a duplicate; previously defined at line 9");
|
||||
CHECK_EQ(result.warnings[2].text, "Table index 1 is a duplicate; previously defined as a list entry");
|
||||
|
@ -1248,7 +1248,7 @@ TEST_CASE_FIXTURE(Fixture, "ImportOnlyUsedInTypeAnnotation")
|
|||
local x: Foo.Y = 1
|
||||
)");
|
||||
|
||||
REQUIRE_EQ(result.warnings.size(), 1);
|
||||
REQUIRE(1 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].text, "Variable 'x' is never used; prefix with '_' to silence");
|
||||
}
|
||||
|
||||
|
@ -1259,7 +1259,7 @@ TEST_CASE_FIXTURE(Fixture, "DisableUnknownGlobalWithTypeChecking")
|
|||
unknownGlobal()
|
||||
)");
|
||||
|
||||
REQUIRE_EQ(result.warnings.size(), 0);
|
||||
REQUIRE(0 == result.warnings.size());
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "no_spurious_warning_after_a_function_type_alias")
|
||||
|
@ -1271,7 +1271,7 @@ TEST_CASE_FIXTURE(Fixture, "no_spurious_warning_after_a_function_type_alias")
|
|||
return exports
|
||||
)");
|
||||
|
||||
CHECK_EQ(result.warnings.size(), 0);
|
||||
REQUIRE(0 == result.warnings.size());
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "use_all_parent_scopes_for_globals")
|
||||
|
@ -1294,7 +1294,7 @@ TEST_CASE_FIXTURE(Fixture, "use_all_parent_scopes_for_globals")
|
|||
|
||||
LintResult result = frontend.lint("A");
|
||||
|
||||
CHECK_EQ(result.warnings.size(), 0);
|
||||
REQUIRE(0 == result.warnings.size());
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "DeadLocalsUsed")
|
||||
|
@ -1320,7 +1320,7 @@ do
|
|||
end
|
||||
)");
|
||||
|
||||
CHECK_EQ(result.warnings.size(), 3);
|
||||
REQUIRE(3 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].text, "Variable 'x' defined at line 4 is never initialized or assigned; initialize with 'nil' to silence");
|
||||
CHECK_EQ(result.warnings[1].text, "Assigning 2 values to 3 variables initializes extra variables with nil; add 'nil' to value list to silence");
|
||||
CHECK_EQ(result.warnings[2].text, "Variable 'c' defined at line 12 is never initialized or assigned; initialize with 'nil' to silence");
|
||||
|
@ -1333,7 +1333,7 @@ local foo
|
|||
function foo() end
|
||||
)");
|
||||
|
||||
CHECK_EQ(result.warnings.size(), 0);
|
||||
REQUIRE(0 == result.warnings.size());
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "DuplicateGlobalFunction")
|
||||
|
@ -1408,7 +1408,7 @@ TEST_CASE_FIXTURE(Fixture, "DontTriggerTheWarningIfTheFunctionsAreInDifferentSco
|
|||
return c
|
||||
)");
|
||||
|
||||
CHECK(result.warnings.empty());
|
||||
REQUIRE(0 == result.warnings.size());
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "LintHygieneUAF")
|
||||
|
@ -1444,7 +1444,7 @@ TEST_CASE_FIXTURE(Fixture, "LintHygieneUAF")
|
|||
local h: Hooty.Pt
|
||||
)");
|
||||
|
||||
CHECK_EQ(result.warnings.size(), 12);
|
||||
REQUIRE(12 == result.warnings.size());
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "DeprecatedApi")
|
||||
|
@ -1478,7 +1478,7 @@ return function (i: Instance)
|
|||
end
|
||||
)");
|
||||
|
||||
REQUIRE_EQ(result.warnings.size(), 3);
|
||||
REQUIRE(3 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].text, "Member 'Instance.Wait' is deprecated");
|
||||
CHECK_EQ(result.warnings[1].text, "Member 'toHSV' is deprecated, use 'Color3:ToHSV' instead");
|
||||
CHECK_EQ(result.warnings[2].text, "Member 'Instance.DataCost' is deprecated");
|
||||
|
@ -1511,7 +1511,7 @@ table.create(42, {})
|
|||
table.create(42, {} :: {})
|
||||
)");
|
||||
|
||||
REQUIRE_EQ(result.warnings.size(), 10);
|
||||
REQUIRE(10 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].text, "table.insert will insert the value before the last element, which is likely a bug; consider removing the "
|
||||
"second argument or wrap it in parentheses to silence");
|
||||
CHECK_EQ(result.warnings[1].text, "table.insert will append the value to the table; consider removing the second argument for efficiency");
|
||||
|
@ -1556,7 +1556,7 @@ _ = true and true or false -- no warning since this is is a common pattern used
|
|||
_ = if true then 1 elseif true then 2 else 3
|
||||
)");
|
||||
|
||||
REQUIRE_EQ(result.warnings.size(), 8);
|
||||
REQUIRE(8 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].text, "Condition has already been checked on line 2");
|
||||
CHECK_EQ(result.warnings[0].location.begin.line + 1, 4);
|
||||
CHECK_EQ(result.warnings[1].text, "Condition has already been checked on column 5");
|
||||
|
@ -1580,7 +1580,7 @@ elseif correct({a = 1, b = 2 * (-2), c = opaque.path['with']("calls", false)}) t
|
|||
end
|
||||
)");
|
||||
|
||||
REQUIRE_EQ(result.warnings.size(), 1);
|
||||
REQUIRE(1 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].text, "Condition has already been checked on line 4");
|
||||
CHECK_EQ(result.warnings[0].location.begin.line + 1, 5);
|
||||
}
|
||||
|
@ -1601,7 +1601,7 @@ end
|
|||
return foo, moo, a1, a2
|
||||
)");
|
||||
|
||||
REQUIRE_EQ(result.warnings.size(), 4);
|
||||
REQUIRE(4 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].text, "Function parameter 'a1' already defined on column 14");
|
||||
CHECK_EQ(result.warnings[1].text, "Variable 'a1' is never used; prefix with '_' to silence");
|
||||
CHECK_EQ(result.warnings[2].text, "Variable 'a1' already defined on column 7");
|
||||
|
@ -1618,7 +1618,7 @@ _ = math.random() < 0.5 and 0 or 42
|
|||
_ = (math.random() < 0.5 and false) or 42 -- currently ignored
|
||||
)");
|
||||
|
||||
REQUIRE_EQ(result.warnings.size(), 2);
|
||||
REQUIRE(2 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].text, "The and-or expression always evaluates to the second alternative because the first alternative is false; "
|
||||
"consider using if-then-else expression instead");
|
||||
CHECK_EQ(result.warnings[1].text, "The and-or expression always evaluates to the second alternative because the first alternative is nil; "
|
||||
|
@ -1640,7 +1640,7 @@ do end
|
|||
--!nolint
|
||||
)");
|
||||
|
||||
REQUIRE_EQ(result.warnings.size(), 6);
|
||||
REQUIRE(6 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].text, "Unknown comment directive 'struct'; did you mean 'strict'?");
|
||||
CHECK_EQ(result.warnings[1].text, "Unknown comment directive 'nolintGlobal'");
|
||||
CHECK_EQ(result.warnings[2].text, "nolint directive refers to unknown lint rule 'Global'");
|
||||
|
@ -1656,7 +1656,7 @@ TEST_CASE_FIXTURE(Fixture, "WrongCommentMuteSelf")
|
|||
--!struct
|
||||
)");
|
||||
|
||||
REQUIRE_EQ(result.warnings.size(), 0); // --!nolint disables WrongComment lint :)
|
||||
REQUIRE(0 == result.warnings.size()); // --!nolint disables WrongComment lint :)
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "DuplicateConditionsIfStatAndExpr")
|
||||
|
@ -1668,7 +1668,7 @@ elseif if 0 then 5 else 4 then
|
|||
end
|
||||
)");
|
||||
|
||||
REQUIRE_EQ(result.warnings.size(), 1);
|
||||
REQUIRE(1 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].text, "Condition has already been checked on line 2");
|
||||
}
|
||||
|
||||
|
@ -1681,13 +1681,13 @@ TEST_CASE_FIXTURE(Fixture, "WrongCommentOptimize")
|
|||
--!optimize 2
|
||||
)");
|
||||
|
||||
REQUIRE_EQ(result.warnings.size(), 3);
|
||||
REQUIRE(3 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].text, "optimize directive requires an optimization level");
|
||||
CHECK_EQ(result.warnings[1].text, "optimize directive uses unknown optimization level 'me', 0..2 expected");
|
||||
CHECK_EQ(result.warnings[2].text, "optimize directive uses unknown optimization level '100500', 0..2 expected");
|
||||
|
||||
result = lint("--!optimize ");
|
||||
REQUIRE_EQ(result.warnings.size(), 1);
|
||||
REQUIRE(1 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].text, "optimize directive requires an optimization level");
|
||||
}
|
||||
|
||||
|
@ -1700,7 +1700,7 @@ TEST_CASE_FIXTURE(Fixture, "TestStringInterpolation")
|
|||
local _ = `unknown {foo}`
|
||||
)");
|
||||
|
||||
REQUIRE_EQ(result.warnings.size(), 1);
|
||||
REQUIRE(1 == result.warnings.size());
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "IntegerParsing")
|
||||
|
@ -1710,7 +1710,7 @@ local _ = 0b10000000000000000000000000000000000000000000000000000000000000000
|
|||
local _ = 0x10000000000000000
|
||||
)");
|
||||
|
||||
REQUIRE_EQ(result.warnings.size(), 2);
|
||||
REQUIRE(2 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].text, "Binary number literal exceeded available precision and has been truncated to 2^64");
|
||||
CHECK_EQ(result.warnings[1].text, "Hexadecimal number literal exceeded available precision and has been truncated to 2^64");
|
||||
}
|
||||
|
@ -1725,7 +1725,7 @@ local _ = 0x0x123
|
|||
local _ = 0x0xffffffffffffffffffffffffffffffffff
|
||||
)");
|
||||
|
||||
REQUIRE_EQ(result.warnings.size(), 2);
|
||||
REQUIRE(2 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].text,
|
||||
"Hexadecimal number literal has a double prefix, which will fail to parse in the future; remove the extra 0x to fix");
|
||||
CHECK_EQ(result.warnings[1].text,
|
||||
|
@ -1756,7 +1756,7 @@ local _ = (a <= b) == 0
|
|||
local _ = a <= (b == 0)
|
||||
)");
|
||||
|
||||
REQUIRE_EQ(result.warnings.size(), 5);
|
||||
REQUIRE(5 == result.warnings.size());
|
||||
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");
|
||||
|
|
|
@ -356,6 +356,11 @@ TEST_CASE_FIXTURE(NormalizeFixture, "table_with_any_prop")
|
|||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "intersection")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
|
||||
check(R"(
|
||||
local a: number & string
|
||||
local b: number
|
||||
|
@ -374,8 +379,9 @@ TEST_CASE_FIXTURE(NormalizeFixture, "intersection")
|
|||
CHECK(!isSubtype(c, a));
|
||||
CHECK(isSubtype(a, c));
|
||||
|
||||
CHECK(!isSubtype(d, a));
|
||||
CHECK(!isSubtype(a, d));
|
||||
// These types are both equivalent to never
|
||||
CHECK(isSubtype(d, a));
|
||||
CHECK(isSubtype(a, d));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "union_and_intersection")
|
||||
|
|
|
@ -2722,4 +2722,59 @@ TEST_CASE_FIXTURE(Fixture, "error_message_for_using_function_as_type_annotation"
|
|||
result.errors[0].getMessage());
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "get_a_nice_error_when_there_is_an_extra_comma_at_the_end_of_a_function_argument_list")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauCommaParenWarnings", true};
|
||||
|
||||
ParseResult result = tryParse(R"(
|
||||
foo(a, b, c,)
|
||||
)");
|
||||
|
||||
REQUIRE(1 == result.errors.size());
|
||||
|
||||
CHECK(Location({1, 20}, {1, 21}) == result.errors[0].getLocation());
|
||||
CHECK("Expected expression after ',' but got ')' instead" == result.errors[0].getMessage());
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "get_a_nice_error_when_there_is_an_extra_comma_at_the_end_of_a_function_parameter_list")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauCommaParenWarnings", true};
|
||||
|
||||
ParseResult result = tryParse(R"(
|
||||
export type VisitFn = (
|
||||
any,
|
||||
Array<TAnyNode | Array<TAnyNode>>, -- extra comma here
|
||||
) -> any
|
||||
)");
|
||||
|
||||
REQUIRE(1 == result.errors.size());
|
||||
|
||||
CHECK(Location({4, 8}, {4, 9}) == result.errors[0].getLocation());
|
||||
CHECK("Expected type after ',' but got ')' instead" == result.errors[0].getMessage());
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "get_a_nice_error_when_there_is_an_extra_comma_at_the_end_of_a_generic_parameter_list")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauCommaParenWarnings", true};
|
||||
|
||||
ParseResult result = tryParse(R"(
|
||||
export type VisitFn = <A, B,>(a: A, b: B) -> ()
|
||||
)");
|
||||
|
||||
REQUIRE(1 == result.errors.size());
|
||||
|
||||
CHECK(Location({1, 36}, {1, 37}) == result.errors[0].getLocation());
|
||||
CHECK("Expected type after ',' but got '>' instead" == result.errors[0].getMessage());
|
||||
|
||||
REQUIRE(1 == result.root->body.size);
|
||||
|
||||
AstStatTypeAlias* t = result.root->body.data[0]->as<AstStatTypeAlias>();
|
||||
REQUIRE(t != nullptr);
|
||||
|
||||
AstTypeFunction* f = t->type->as<AstTypeFunction>();
|
||||
REQUIRE(f != nullptr);
|
||||
|
||||
CHECK(2 == f->generics.size);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -480,4 +480,38 @@ local a: ChildClass = i
|
|||
CHECK_EQ("Type 'ChildClass' from 'Test' could not be converted into 'ChildClass' from 'MainModule'", toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ClassFixture, "intersections_of_unions_of_classes")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local x : (BaseClass | Vector2) & (ChildClass | AnotherChild)
|
||||
local y : (ChildClass | AnotherChild)
|
||||
x = y
|
||||
y = x
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ClassFixture, "unions_of_intersections_of_classes")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local x : (BaseClass & ChildClass) | (BaseClass & AnotherChild) | (BaseClass & Vector2)
|
||||
local y : (ChildClass | AnotherChild)
|
||||
x = y
|
||||
y = x
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "Luau/AstQuery.h"
|
||||
#include "Luau/BuiltinDefinitions.h"
|
||||
#include "Luau/Error.h"
|
||||
#include "Luau/Scope.h"
|
||||
#include "Luau/TypeInfer.h"
|
||||
#include "Luau/TypeVar.h"
|
||||
|
@ -14,6 +15,7 @@
|
|||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauLowerBoundsCalculation);
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping);
|
||||
LUAU_FASTFLAG(LuauSpecialTypesAsterisked);
|
||||
|
||||
TEST_SUITE_BEGIN("TypeInferFunctions");
|
||||
|
@ -1087,10 +1089,20 @@ f(function(a, b, c, ...) return a + b end)
|
|||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
|
||||
CHECK_EQ(R"(Type '(number, number, a) -> number' could not be converted into '(number, number) -> number'
|
||||
if (FFlag::LuauInstantiateInSubtyping)
|
||||
{
|
||||
CHECK_EQ(R"(Type '<a>(number, number, a) -> number' could not be converted into '(number, number) -> number'
|
||||
caused by:
|
||||
Argument count mismatch. Function expects 3 arguments, but only 2 are specified)",
|
||||
toString(result.errors[0]));
|
||||
toString(result.errors[0]));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ(R"(Type '(number, number, a) -> number' could not be converted into '(number, number) -> number'
|
||||
caused by:
|
||||
Argument count mismatch. Function expects 3 arguments, but only 2 are specified)",
|
||||
toString(result.errors[0]));
|
||||
}
|
||||
|
||||
// Infer from variadic packs into elements
|
||||
result = check(R"(
|
||||
|
@ -1189,10 +1201,20 @@ f(function(a, b, c, ...) return a + b end)
|
|||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
|
||||
CHECK_EQ(R"(Type '(number, number, a) -> number' could not be converted into '(number, number) -> number'
|
||||
if (FFlag::LuauInstantiateInSubtyping)
|
||||
{
|
||||
CHECK_EQ(R"(Type '<a>(number, number, a) -> number' could not be converted into '(number, number) -> number'
|
||||
caused by:
|
||||
Argument count mismatch. Function expects 3 arguments, but only 2 are specified)",
|
||||
toString(result.errors[0]));
|
||||
toString(result.errors[0]));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ(R"(Type '(number, number, a) -> number' could not be converted into '(number, number) -> number'
|
||||
caused by:
|
||||
Argument count mismatch. Function expects 3 arguments, but only 2 are specified)",
|
||||
toString(result.errors[0]));
|
||||
}
|
||||
|
||||
// Infer from variadic packs into elements
|
||||
result = check(R"(
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include "doctest.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauCheckGenericHOFTypes)
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
LUAU_FASTFLAG(LuauSpecialTypesAsterisked)
|
||||
|
||||
using namespace Luau;
|
||||
|
@ -960,7 +961,11 @@ TEST_CASE_FIXTURE(Fixture, "instantiate_generic_function_in_assignments")
|
|||
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
|
||||
REQUIRE(tm);
|
||||
CHECK_EQ("((number) -> number, string) -> number", toString(tm->wantedType));
|
||||
CHECK_EQ("((number) -> number, number) -> number", toString(tm->givenType));
|
||||
if (FFlag::LuauInstantiateInSubtyping)
|
||||
CHECK_EQ("<a, b...>((a) -> (b...), a) -> (b...)", toString(tm->givenType));
|
||||
else
|
||||
CHECK_EQ("((number) -> number, number) -> number", toString(tm->givenType));
|
||||
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "instantiate_generic_function_in_assignments2")
|
||||
|
@ -980,7 +985,10 @@ TEST_CASE_FIXTURE(Fixture, "instantiate_generic_function_in_assignments2")
|
|||
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
|
||||
REQUIRE(tm);
|
||||
CHECK_EQ("(string, string) -> number", toString(tm->wantedType));
|
||||
CHECK_EQ("((string) -> number, string) -> number", toString(*tm->givenType));
|
||||
if (FFlag::LuauInstantiateInSubtyping)
|
||||
CHECK_EQ("<a, b...>((a) -> (b...), a) -> (b...)", toString(tm->givenType));
|
||||
else
|
||||
CHECK_EQ("((string) -> number, string) -> number", toString(*tm->givenType));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "self_recursive_instantiated_param")
|
||||
|
@ -1110,6 +1118,15 @@ local c = sumrec(function(x, y, f) return f(x, y) end) -- type binders are not i
|
|||
{
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
else if (FFlag::LuauInstantiateInSubtyping)
|
||||
{
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
CHECK_EQ(
|
||||
R"(Type '<a, b, c...>(a, b, (a, b) -> (c...)) -> (c...)' could not be converted into '<a>(a, a, (a, a) -> a) -> a'
|
||||
caused by:
|
||||
Argument #1 type is not compatible. Generic subtype escaping scope)",
|
||||
toString(result.errors[0]));
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
|
@ -1219,4 +1236,48 @@ TEST_CASE_FIXTURE(Fixture, "do_not_always_instantiate_generic_intersection_types
|
|||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "hof_subtype_instantiation_regression")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
|
||||
local function defaultSort<T>(a: T, b: T)
|
||||
return true
|
||||
end
|
||||
type A = any
|
||||
return function<T>(array: {T}): {T}
|
||||
table.sort(array, defaultSort)
|
||||
return array
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "higher_rank_polymorphism_should_not_accept_instantiated_arguments")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{"LuauInstantiateInSubtyping", true},
|
||||
{"LuauCheckGenericHOFTypes", true}, // necessary because of interactions with the test
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
|
||||
local function instantiate(f: <a>(a) -> a): (number) -> number
|
||||
return f
|
||||
end
|
||||
|
||||
instantiate(function(x: string) return "foo" end)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
auto tm1 = get<TypeMismatch>(result.errors[0]);
|
||||
REQUIRE(tm1);
|
||||
|
||||
CHECK_EQ("<a>(a) -> a", toString(tm1->wantedType));
|
||||
CHECK_EQ("<a>(string) -> string", toString(tm1->givenType));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -446,4 +446,459 @@ TEST_CASE_FIXTURE(Fixture, "no_stack_overflow_from_flattenintersection")
|
|||
LUAU_REQUIRE_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "intersect_bool_and_false")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local x : (boolean & false)
|
||||
local y : false = x -- OK
|
||||
local z : true = x -- Not OK
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type 'boolean & false' could not be converted into 'true'; none of the intersection parts are compatible");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "intersect_false_and_bool_and_false")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local x : false & (boolean & false)
|
||||
local y : false = x -- OK
|
||||
local z : true = x -- Not OK
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
// TODO: odd stringification of `false & (boolean & false)`.)
|
||||
CHECK_EQ(toString(result.errors[0]), "Type 'boolean & false & false' could not be converted into 'true'; none of the intersection parts are compatible");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "intersect_saturate_overloaded_functions")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local x : ((number?) -> number?) & ((string?) -> string?)
|
||||
local y : (nil) -> nil = x -- OK
|
||||
local z : (number) -> number = x -- Not OK
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '((number?) -> number?) & ((string?) -> string?)' could not be converted into '(number) -> number'; none of the intersection parts are compatible");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "union_saturate_overloaded_functions")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local x : ((number) -> number) & ((string) -> string)
|
||||
local y : ((number | string) -> (number | string)) = x -- OK
|
||||
local z : ((number | boolean) -> (number | boolean)) = x -- Not OK
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '((number) -> number) & ((string) -> string)' could not be converted into '(boolean | number) -> boolean | number'; none of the intersection parts are compatible");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "intersection_of_tables")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local x : { p : number?, q : string? } & { p : number?, q : number?, r : number? }
|
||||
local y : { p : number?, q : nil, r : number? } = x -- OK
|
||||
local z : { p : nil } = x -- Not OK
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '{| p: number?, q: number?, r: number? |} & {| p: number?, q: string? |}' could not be converted into '{| p: nil |}'; none of the intersection parts are compatible");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_top_properties")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local x : { p : number?, q : any } & { p : unknown, q : string? }
|
||||
local y : { p : number?, q : string? } = x -- OK
|
||||
local z : { p : string?, q : number? } = x -- Not OK
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '{| p: number?, q: any |} & {| p: unknown, q: string? |}' could not be converted into '{| p: string?, q: number? |}'; none of the intersection parts are compatible");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_never_properties")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local x : { p : number?, q : never } & { p : never, q : string? }
|
||||
local y : { p : never, q : never } = x -- OK
|
||||
local z : never = x -- OK
|
||||
)");
|
||||
|
||||
// TODO: this should not produce type errors, since never <: { p : never }
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '{| p: never, q: string? |} & {| p: number?, q: never |}' could not be converted into 'never'; none of the intersection parts are compatible");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "overloaded_functions_returning_intersections")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local x : ((number?) -> ({ p : number } & { q : number })) & ((string?) -> ({ p : number } & { r : number }))
|
||||
local y : (nil) -> { p : number, q : number, r : number} = x -- OK
|
||||
local z : (number?) -> { p : number, q : number, r : number} = x -- Not OK
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '((number?) -> {| p: number |} & {| q: number |}) & ((string?) -> {| p: number |} & {| r: number |})' could not be converted into '(number?) -> {| p: number, q: number, r: number |}'; none of the intersection parts are compatible");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function f<a>()
|
||||
local x : ((number?) -> (a | number)) & ((string?) -> (a | string))
|
||||
local y : (nil) -> a = x -- OK
|
||||
local z : (number?) -> a = x -- Not OK
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '((number?) -> a | number) & ((string?) -> a | string)' could not be converted into '(number?) -> a'; none of the intersection parts are compatible");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generics")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function f<a,b,c>()
|
||||
local x : ((a?) -> (a | b)) & ((c?) -> (b | c))
|
||||
local y : (nil) -> ((a & c) | b) = x -- OK
|
||||
local z : (a?) -> ((a & c) | b) = x -- Not OK
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '((a?) -> a | b) & ((c?) -> b | c)' could not be converted into '(a?) -> (a & c) | b'; none of the intersection parts are compatible");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic_packs")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function f<a...,b...>()
|
||||
local x : ((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...))
|
||||
local y : ((nil, a...) -> (nil, b...)) = x -- OK
|
||||
local z : ((nil, b...) -> (nil, a...)) = x -- Not OK
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...))' could not be converted into '(nil, b...) -> (nil, a...)'; none of the intersection parts are compatible");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_result")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function f<a...,b...>()
|
||||
local x : ((number) -> number) & ((nil) -> unknown)
|
||||
local y : (number?) -> unknown = x -- OK
|
||||
local z : (number?) -> number? = x -- Not OK
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '((nil) -> unknown) & ((number) -> number)' could not be converted into '(number?) -> number?'; none of the intersection parts are compatible");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_arguments")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function f<a...,b...>()
|
||||
local x : ((number) -> number?) & ((unknown) -> string?)
|
||||
local y : (number) -> nil = x -- OK
|
||||
local z : (number?) -> nil = x -- Not OK
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '((number) -> number?) & ((unknown) -> string?)' could not be converted into '(number?) -> nil'; none of the intersection parts are compatible");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_result")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function f<a...,b...>()
|
||||
local x : ((number) -> number) & ((nil) -> never)
|
||||
local y : (number?) -> number = x -- OK
|
||||
local z : (number?) -> never = x -- Not OK
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '((nil) -> never) & ((number) -> number)' could not be converted into '(number?) -> never'; none of the intersection parts are compatible");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_arguments")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function f<a...,b...>()
|
||||
local x : ((number) -> number?) & ((never) -> string?)
|
||||
local y : (never) -> nil = x -- OK
|
||||
local z : (number?) -> nil = x -- Not OK
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '((never) -> string?) & ((number) -> number?)' could not be converted into '(number?) -> nil'; none of the intersection parts are compatible");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_overlapping_results_and_variadics")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local x : ((string?) -> (string | number)) & ((number?) -> ...number)
|
||||
local y : ((nil) -> (number, number?)) = x -- OK
|
||||
local z : ((string | number) -> (number, number?)) = x -- Not OK
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '((number?) -> (...number)) & ((string?) -> number | string)' could not be converted into '(number | string) -> (number, number?)'; none of the intersection parts are compatible");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_1")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
function f<a...,b...>()
|
||||
local x : (() -> a...) & (() -> b...)
|
||||
local y : (() -> b...) & (() -> a...) = x -- OK
|
||||
local z : () -> () = x -- Not OK
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '(() -> (a...)) & (() -> (b...))' could not be converted into '() -> ()'; none of the intersection parts are compatible");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_2")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
function f<a...,b...>()
|
||||
local x : ((a...) -> ()) & ((b...) -> ())
|
||||
local y : ((b...) -> ()) & ((a...) -> ()) = x -- OK
|
||||
local z : () -> () = x -- Not OK
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '((a...) -> ()) & ((b...) -> ())' could not be converted into '() -> ()'; none of the intersection parts are compatible");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_3")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
function f<a...>()
|
||||
local x : (() -> a...) & (() -> (number?,a...))
|
||||
local y : (() -> (number?,a...)) & (() -> a...) = x -- OK
|
||||
local z : () -> (number) = x -- Not OK
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '(() -> (a...)) & (() -> (number?, a...))' could not be converted into '() -> number'; none of the intersection parts are compatible");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_4")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
function f<a...>()
|
||||
local x : ((a...) -> ()) & ((number,a...) -> number)
|
||||
local y : ((number,a...) -> number) & ((a...) -> ()) = x -- OK
|
||||
local z : (number?) -> () = x -- Not OK
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '((a...) -> ()) & ((number, a...) -> number)' could not be converted into '(number?) -> ()'; none of the intersection parts are compatible");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatables")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local a : string? = nil
|
||||
local b : number? = nil
|
||||
|
||||
local x = setmetatable({}, { p = 5, q = a });
|
||||
local y = setmetatable({}, { q = b, r = "hi" });
|
||||
local z = setmetatable({}, { p = 5, q = nil, r = "hi" });
|
||||
|
||||
type X = typeof(x)
|
||||
type Y = typeof(y)
|
||||
type Z = typeof(z)
|
||||
|
||||
local xy : X&Y = z;
|
||||
local yx : Y&X = z;
|
||||
z = xy;
|
||||
z = yx;
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatable_subtypes")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local x = setmetatable({ a = 5 }, { p = 5 });
|
||||
local y = setmetatable({ b = "hi" }, { p = 5, q = "hi" });
|
||||
local z = setmetatable({ a = 5, b = "hi" }, { p = 5, q = "hi" });
|
||||
|
||||
type X = typeof(x)
|
||||
type Y = typeof(y)
|
||||
type Z = typeof(z)
|
||||
|
||||
local xy : X&Y = z;
|
||||
local yx : Y&X = z;
|
||||
z = xy;
|
||||
z = yx;
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatables_with_properties")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local x = setmetatable({ a = 5 }, { p = 5 });
|
||||
local y = setmetatable({ b = "hi" }, { q = "hi" });
|
||||
local z = setmetatable({ a = 5, b = "hi" }, { p = 5, q = "hi" });
|
||||
|
||||
type X = typeof(x)
|
||||
type Y = typeof(y)
|
||||
type Z = typeof(z)
|
||||
|
||||
local xy : X&Y = z;
|
||||
z = xy;
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatable_with table")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local x = setmetatable({ a = 5 }, { p = 5 });
|
||||
local z = setmetatable({ a = 5, b = "hi" }, { p = 5 });
|
||||
|
||||
type X = typeof(x)
|
||||
type Y = { b : string }
|
||||
type Z = typeof(z)
|
||||
|
||||
-- TODO: once we have shape types, we should be able to initialize these with z
|
||||
local xy : X&Y;
|
||||
local yx : Y&X;
|
||||
z = xy;
|
||||
z = yx;
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "CLI-44817")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type X = {x: number}
|
||||
type Y = {y: number}
|
||||
type Z = {z: number}
|
||||
|
||||
type XY = {x: number, y: number}
|
||||
type XYZ = {x:number, y: number, z: number}
|
||||
|
||||
local xy: XY = {x = 0, y = 0}
|
||||
local xyz: XYZ = {x = 0, y = 0, z = 0}
|
||||
|
||||
local xNy: X&Y = xy
|
||||
local xNyNz: X&Y&Z = xyz
|
||||
|
||||
local t1: XY = xNy -- Type 'X & Y' could not be converted into 'XY'
|
||||
local t2: XY = xNyNz -- Type 'X & Y & Z' could not be converted into 'XY'
|
||||
local t3: XYZ = xNyNz -- Type 'X & Y & Z' could not be converted into 'XYZ'
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
|
||||
#include "doctest.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauSpecialTypesAsterisked)
|
||||
|
@ -248,7 +250,24 @@ end
|
|||
|
||||
return m
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::LuauInstantiateInSubtyping)
|
||||
{
|
||||
// though this didn't error before the flag, it seems as though it should error since fields of a table are invariant.
|
||||
// the user's intent would likely be that these "method" fields would be read-only, but without an annotation, accepting this should be unsound.
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
CHECK_EQ(R"(Type 'n' could not be converted into 't1 where t1 = {- Clone: (t1) -> (a...) -}'
|
||||
caused by:
|
||||
Property 'Clone' is not compatible. Type '<a>(a) -> ()' could not be converted into 't1 where t1 = ({- Clone: t1 -}) -> (a...)'; different number of generic type parameters)",
|
||||
toString(result.errors[0]));
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "custom_require_global")
|
||||
|
@ -367,8 +386,6 @@ type Table = typeof(tbl)
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "do_not_modify_imported_types_5")
|
||||
{
|
||||
ScopedFastFlag luauInplaceDemoteSkipAllBound{"LuauInplaceDemoteSkipAllBound", true};
|
||||
|
||||
fileResolver.source["game/A"] = R"(
|
||||
export type Type = {x: number, y: number}
|
||||
local arrayops = {}
|
||||
|
|
|
@ -271,30 +271,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "bail_early_if_unification_is_too_complicated
|
|||
}
|
||||
}
|
||||
|
||||
// Should be in TypeInfer.tables.test.cpp
|
||||
// It's unsound to instantiate tables containing generic methods,
|
||||
// since mutating properties means table properties should be invariant.
|
||||
// We currently allow this but we shouldn't!
|
||||
TEST_CASE_FIXTURE(Fixture, "invariant_table_properties_means_instantiating_tables_in_call_is_unsound")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
local t = {}
|
||||
function t.m(x) return x end
|
||||
local a : string = t.m("hi")
|
||||
local b : number = t.m(5)
|
||||
function f(x : { m : (number)->number })
|
||||
x.m = function(x) return 1+x end
|
||||
end
|
||||
f(t) -- This shouldn't typecheck
|
||||
local c : string = t.m("hi")
|
||||
)");
|
||||
|
||||
// TODO: this should error!
|
||||
// This should be fixed by replacing generic tables by generics with type bounds.
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
// FIXME: Move this test to another source file when removing FFlag::LuauLowerBoundsCalculation
|
||||
TEST_CASE_FIXTURE(Fixture, "do_not_ice_when_trying_to_pick_first_of_generic_type_pack")
|
||||
{
|
||||
|
@ -608,7 +584,8 @@ TEST_CASE_FIXTURE(Fixture, "free_options_cannot_be_unified_together")
|
|||
|
||||
InternalErrorReporter iceHandler;
|
||||
UnifierSharedState sharedState{&iceHandler};
|
||||
Unifier u{&arena, singletonTypes, Mode::Strict, NotNull{scope.get()}, Location{}, Variance::Covariant, sharedState};
|
||||
Normalizer normalizer{&arena, singletonTypes, NotNull{&sharedState}};
|
||||
Unifier u{NotNull{&normalizer}, Mode::Strict, NotNull{scope.get()}, Location{}, Variance::Covariant};
|
||||
|
||||
u.tryUnify(option1, option2);
|
||||
|
||||
|
@ -635,4 +612,87 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_loop_with_zero_iterators")
|
|||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
// Ideally, we would not try to export a function type with generic types from incorrect scope
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "generic_type_leak_to_module_interface")
|
||||
{
|
||||
ScopedFastFlag LuauAnyifyModuleReturnGenerics{"LuauAnyifyModuleReturnGenerics", true};
|
||||
|
||||
fileResolver.source["game/A"] = R"(
|
||||
local wrapStrictTable
|
||||
|
||||
local metatable = {
|
||||
__index = function(self, key)
|
||||
local value = self.__tbl[key]
|
||||
if type(value) == "table" then
|
||||
-- unification of the free 'wrapStrictTable' with this function type causes generics of this function to leak out of scope
|
||||
return wrapStrictTable(value, self.__name .. "." .. key)
|
||||
end
|
||||
return value
|
||||
end,
|
||||
}
|
||||
|
||||
return wrapStrictTable
|
||||
)";
|
||||
|
||||
frontend.check("game/A");
|
||||
|
||||
fileResolver.source["game/B"] = R"(
|
||||
local wrapStrictTable = require(game.A)
|
||||
|
||||
local Constants = {}
|
||||
|
||||
return wrapStrictTable(Constants, "Constants")
|
||||
)";
|
||||
|
||||
frontend.check("game/B");
|
||||
|
||||
ModulePtr m = frontend.moduleResolver.modules["game/B"];
|
||||
REQUIRE(m);
|
||||
|
||||
std::optional<TypeId> result = first(m->getModuleScope()->returnType);
|
||||
REQUIRE(result);
|
||||
CHECK(get<AnyTypeVar>(*result));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "generic_type_leak_to_module_interface_variadic")
|
||||
{
|
||||
ScopedFastFlag LuauAnyifyModuleReturnGenerics{"LuauAnyifyModuleReturnGenerics", true};
|
||||
|
||||
fileResolver.source["game/A"] = R"(
|
||||
local wrapStrictTable
|
||||
|
||||
local metatable = {
|
||||
__index = function<T>(self, key, ...: T)
|
||||
local value = self.__tbl[key]
|
||||
if type(value) == "table" then
|
||||
-- unification of the free 'wrapStrictTable' with this function type causes generics of this function to leak out of scope
|
||||
return wrapStrictTable(value, self.__name .. "." .. key)
|
||||
end
|
||||
return ...
|
||||
end,
|
||||
}
|
||||
|
||||
return wrapStrictTable
|
||||
)";
|
||||
|
||||
frontend.check("game/A");
|
||||
|
||||
fileResolver.source["game/B"] = R"(
|
||||
local wrapStrictTable = require(game.A)
|
||||
|
||||
local Constants = {}
|
||||
|
||||
return wrapStrictTable(Constants, "Constants")
|
||||
)";
|
||||
|
||||
frontend.check("game/B");
|
||||
|
||||
ModulePtr m = frontend.moduleResolver.modules["game/B"];
|
||||
REQUIRE(m);
|
||||
|
||||
std::optional<TypeId> result = first(m->getModuleScope()->returnType);
|
||||
REQUIRE(result);
|
||||
CHECK(get<AnyTypeVar>(*result));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -11,7 +11,8 @@
|
|||
|
||||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauLowerBoundsCalculation);
|
||||
LUAU_FASTFLAG(LuauLowerBoundsCalculation)
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
|
||||
TEST_SUITE_BEGIN("TableTests");
|
||||
|
||||
|
@ -2038,11 +2039,22 @@ caused by:
|
|||
caused by:
|
||||
Property 'y' is not compatible. Type 'string' could not be converted into 'number')");
|
||||
|
||||
CHECK_EQ(toString(result.errors[1]), R"(Type 'b2' could not be converted into 'a2'
|
||||
if (FFlag::LuauInstantiateInSubtyping)
|
||||
{
|
||||
CHECK_EQ(toString(result.errors[1]), R"(Type 'b2' could not be converted into 'a2'
|
||||
caused by:
|
||||
Type '{ __call: <a, b>(a, b) -> () }' could not be converted into '{ __call: <a>(a) -> () }'
|
||||
caused by:
|
||||
Property '__call' is not compatible. Type '<a, b>(a, b) -> ()' could not be converted into '<a>(a) -> ()'; different number of generic type parameters)");
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ(toString(result.errors[1]), R"(Type 'b2' could not be converted into 'a2'
|
||||
caused by:
|
||||
Type '{ __call: (a, b) -> () }' could not be converted into '{ __call: <a>(a) -> () }'
|
||||
caused by:
|
||||
Property '__call' is not compatible. Type '(a, b) -> ()' could not be converted into '<a>(a) -> ()'; different number of generic type parameters)");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "error_detailed_indexer_key")
|
||||
|
@ -3173,4 +3185,53 @@ caused by:
|
|||
CHECK_EQ("<a, b...>(t1) -> string where t1 = {+ absolutely_no_scalar_has_this_method: (t1) -> (a, b...) +}", toString(requireType("f")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "invariant_table_properties_means_instantiating_tables_in_call_is_unsound")
|
||||
{
|
||||
ScopedFastFlag sff[]{
|
||||
{"LuauInstantiateInSubtyping", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
local t = {}
|
||||
function t.m(x) return x end
|
||||
local a : string = t.m("hi")
|
||||
local b : number = t.m(5)
|
||||
function f(x : { m : (number)->number })
|
||||
x.m = function(x) return 1+x end
|
||||
end
|
||||
f(t) -- This shouldn't typecheck
|
||||
local c : string = t.m("hi")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Type 't' could not be converted into '{| m: (number) -> number |}'
|
||||
caused by:
|
||||
Property 'm' is not compatible. Type '<a>(a) -> a' could not be converted into '(number) -> number'; different number of generic type parameters)");
|
||||
// this error message is not great since the underlying issue is that the context is invariant,
|
||||
// and `(number) -> number` cannot be a subtype of `<a>(a) -> a`.
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "generic_table_instantiation_potential_regression")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
|
||||
function f(x)
|
||||
x.p = 5
|
||||
return x
|
||||
end
|
||||
local g : ({ p : number, q : string }) -> ({ p : number, r : boolean }) = f
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
MissingProperties* error = get<MissingProperties>(result.errors[0]);
|
||||
REQUIRE(error != nullptr);
|
||||
REQUIRE(error->properties.size() == 1);
|
||||
|
||||
CHECK_EQ("r", error->properties[0]);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -17,7 +17,9 @@
|
|||
LUAU_FASTFLAG(LuauLowerBoundsCalculation);
|
||||
LUAU_FASTFLAG(LuauFixLocationSpanTableIndexExpr);
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping);
|
||||
LUAU_FASTFLAG(LuauSpecialTypesAsterisked);
|
||||
LUAU_FASTFLAG(LuauCheckGenericHOFTypes);
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
|
@ -999,7 +1001,26 @@ TEST_CASE_FIXTURE(Fixture, "cli_50041_committing_txnlog_in_apollo_client_error")
|
|||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
if (FFlag::LuauInstantiateInSubtyping && !FFlag::LuauCheckGenericHOFTypes)
|
||||
{
|
||||
// though this didn't error before the flag, it seems as though it should error since fields of a table are invariant.
|
||||
// the user's intent would likely be that these "method" fields would be read-only, but without an annotation, accepting this should be unsound.
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
CHECK_EQ(R"(Type 't1 where t1 = {+ getStoreFieldName: (t1, {| fieldName: string |} & {| from: number? |}) -> (a, b...) +}' could not be converted into 'Policies'
|
||||
caused by:
|
||||
Property 'getStoreFieldName' is not compatible. Type 't1 where t1 = ({+ getStoreFieldName: t1 +}, {| fieldName: string |} & {| from: number? |}) -> (a, b...)' could not be converted into '(Policies, FieldSpecifier) -> string'
|
||||
caused by:
|
||||
Argument #2 type is not compatible. Type 'FieldSpecifier' could not be converted into 'FieldSpecifier & {| from: number? |}'
|
||||
caused by:
|
||||
Not all intersection parts are compatible. Table type 'FieldSpecifier' not compatible with type '{| from: number? |}' because the former has extra field 'fieldName')",
|
||||
toString(result.errors[0]));
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "type_infer_recursion_limit_no_ice")
|
||||
|
@ -1020,6 +1041,43 @@ TEST_CASE_FIXTURE(Fixture, "type_infer_recursion_limit_no_ice")
|
|||
CHECK_EQ("Code is too complex to typecheck! Consider simplifying the code around this area", toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "type_infer_recursion_limit_normalizer")
|
||||
{
|
||||
ScopedFastInt sfi("LuauTypeInferRecursionLimit", 10);
|
||||
ScopedFastFlag sffs[] {
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
{"LuauAutocompleteDynamicLimits", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function f<a,b,c,d,e,f,g,h,i,j>()
|
||||
local x : a&b&c&d&e&f&g&h&(i?)
|
||||
local y : (a&b&c&d&e&f&g&h&i)? = x
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
CHECK_EQ("Internal error: Code is too complex to typecheck! Consider adding type annotations around this area", toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "type_infer_cache_limit_normalizer")
|
||||
{
|
||||
ScopedFastInt sfi("LuauNormalizeCacheLimit", 10);
|
||||
ScopedFastFlag sffs[] {
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local x : ((number) -> number) & ((string) -> string) & ((nil) -> nil) & (({}) -> {})
|
||||
local y : (number | string | nil | {}) -> (number | string | nil | {}) = x
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
CHECK_EQ("Internal error: Code is too complex to typecheck! Consider adding type annotations around this area", toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "follow_on_new_types_in_substitution")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
|
|
|
@ -17,8 +17,8 @@ struct TryUnifyFixture : Fixture
|
|||
ScopePtr globalScope{new Scope{arena.addTypePack({TypeId{}})}};
|
||||
InternalErrorReporter iceHandler;
|
||||
UnifierSharedState unifierState{&iceHandler};
|
||||
|
||||
Unifier state{&arena, singletonTypes, Mode::Strict, NotNull{globalScope.get()}, Location{}, Variance::Covariant, unifierState};
|
||||
Normalizer normalizer{&arena, singletonTypes, NotNull{&unifierState}};
|
||||
Unifier state{NotNull{&normalizer}, Mode::Strict, NotNull{globalScope.get()}, Location{}, Variance::Covariant};
|
||||
};
|
||||
|
||||
TEST_SUITE_BEGIN("TryUnifyTests");
|
||||
|
|
|
@ -1000,4 +1000,23 @@ TEST_CASE_FIXTURE(Fixture, "unify_variadic_tails_in_arguments_free")
|
|||
CHECK_EQ(toString(result.errors[0]), "Type 'number' could not be converted into 'boolean'");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "type_packs_with_tails_in_vararg_adjustment")
|
||||
{
|
||||
ScopedFastFlag luauFixVarargExprHeadType{"LuauFixVarargExprHeadType", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function wrapReject<TArg, TResult>(fn: (self: any, ...TArg) -> ...TResult): (self: any, ...TArg) -> ...TResult
|
||||
return function(self, ...)
|
||||
local arguments = { ... }
|
||||
local ok, result = pcall(function()
|
||||
return fn(self, table.unpack(arguments))
|
||||
end)
|
||||
return result
|
||||
end
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -541,5 +541,182 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_union_write_indirect")
|
|||
R"(Type '(string) -> number' could not be converted into '((number) -> string) | ((number) -> string)'; none of the union options are compatible)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "union_true_and_false")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local x : boolean
|
||||
local y1 : (true | false) = x -- OK
|
||||
local y2 : (true | false | (string & number)) = x -- OK
|
||||
local y3 : (true | (string & number) | false) = x -- OK
|
||||
local y4 : (true | (boolean & true) | false) = x -- OK
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "union_of_functions")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local x : (number) -> number?
|
||||
local y : ((number?) -> number?) | ((number) -> number) = x -- OK
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "union_of_generic_functions")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local x : <a>(a) -> a?
|
||||
local y : (<a>(a?) -> a?) | (<b>(b) -> b) = x -- Not OK
|
||||
)");
|
||||
|
||||
// TODO: should this example typecheck?
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "union_of_generic_typepack_functions")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local x : <a...>(number, a...) -> (number?, a...)
|
||||
local y : (<a...>(number?, a...) -> (number?, a...)) | (<b...>(number, b...) -> (number, b...)) = x -- Not OK
|
||||
)");
|
||||
|
||||
// TODO: should this example typecheck?
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "union_of_functions_mentioning_generics")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function f<a,b>()
|
||||
local x : (a) -> a?
|
||||
local y : ((a?) -> nil) | ((a) -> a) = x -- OK
|
||||
local z : ((b?) -> nil) | ((b) -> b) = x -- Not OK
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '(a) -> a?' could not be converted into '((b) -> b) | ((b?) -> nil)'; none of the union options are compatible");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "union_of_functions_mentioning_generic_typepacks")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function f<a...>()
|
||||
local x : (number, a...) -> (number?, a...)
|
||||
local y : ((number | string, a...) -> (number, a...)) | ((number?, a...) -> (nil, a...)) = x -- OK
|
||||
local z : ((number) -> number) | ((number?, a...) -> (number?, a...)) = x -- Not OK
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '(number, a...) -> (number?, a...)' could not be converted into '((number) -> number) | ((number?, a...) -> (number?, a...))'; none of the union options are compatible");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_arities")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local x : (number) -> number?
|
||||
local y : ((number?) -> number) | ((number | string) -> nil) = x -- OK
|
||||
local z : ((number, string?) -> number) | ((number) -> nil) = x -- Not OK
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '(number) -> number?' could not be converted into '((number) -> nil) | ((number, string?) -> number)'; none of the union options are compatible");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_arities")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local x : () -> (number | string)
|
||||
local y : (() -> number) | (() -> string) = x -- OK
|
||||
local z : (() -> number) | (() -> (string, string)) = x -- Not OK
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '() -> number | string' could not be converted into '(() -> (string, string)) | (() -> number)'; none of the union options are compatible");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_variadics")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local x : (...nil) -> (...number?)
|
||||
local y : ((...string?) -> (...number)) | ((...number?) -> nil) = x -- OK
|
||||
local z : ((...string?) -> (...number)) | ((...string?) -> nil) = x -- OK
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '(...nil) -> (...number?)' could not be converted into '((...string?) -> (...number)) | ((...string?) -> nil)'; none of the union options are compatible");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_variadics")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local x : (number) -> ()
|
||||
local y : ((number?) -> ()) | ((...number) -> ()) = x -- OK
|
||||
local z : ((number?) -> ()) | ((...number?) -> ()) = x -- Not OK
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '(number) -> ()' could not be converted into '((...number?) -> ()) | ((number?) -> ())'; none of the union options are compatible");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_variadics")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local x : () -> (number?, ...number)
|
||||
local y : (() -> (...number)) | (() -> nil) = x -- OK
|
||||
local z : (() -> (...number)) | (() -> number) = x -- OK
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '() -> (number?, ...number)' could not be converted into '(() -> (...number)) | (() -> number)'; none of the union options are compatible");
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -508,6 +508,9 @@ assert((function() function cmp(a,b) return a<b,a<=b,a>b,a>=b end return concat(
|
|||
assert((function() function cmp(a,b) return a<b,a<=b,a>b,a>=b end return concat(cmp('abc', 'abd')) end)() == "true,true,false,false")
|
||||
assert((function() function cmp(a,b) return a<b,a<=b,a>b,a>=b end return concat(cmp('ab\\0c', 'ab\\0d')) end)() == "true,true,false,false")
|
||||
assert((function() function cmp(a,b) return a<b,a<=b,a>b,a>=b end return concat(cmp('ab\\0c', 'ab\\0')) end)() == "false,false,true,true")
|
||||
assert((function() function cmp(a,b) return a<b,a<=b,a>b,a>=b end return concat(cmp('\\0a', '\\0b')) end)() == "true,true,false,false")
|
||||
assert((function() function cmp(a,b) return a<b,a<=b,a>b,a>=b end return concat(cmp('a', 'a\\0')) end)() == "true,true,false,false")
|
||||
assert((function() function cmp(a,b) return a<b,a<=b,a>b,a>=b end return concat(cmp('a', '\200')) end)() == "true,true,false,false")
|
||||
|
||||
-- array access
|
||||
assert((function() local a = {4,5,6} return a[3] end)() == 6)
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include <sys/sysctl.h>
|
||||
#endif
|
||||
|
||||
#include <random>
|
||||
#include <optional>
|
||||
|
||||
// Indicates if verbose output is enabled; can be overridden via --verbose
|
||||
|
@ -30,6 +31,10 @@ bool verbose = false;
|
|||
// Default optimization level for conformance test; can be overridden via -On
|
||||
int optimizationLevel = 1;
|
||||
|
||||
// Something to seed a pseudorandom number generator with. Defaults to
|
||||
// something from std::random_device.
|
||||
std::optional<unsigned> randomSeed;
|
||||
|
||||
static bool skipFastFlag(const char* flagName)
|
||||
{
|
||||
if (strncmp(flagName, "Test", 4) == 0)
|
||||
|
@ -261,6 +266,16 @@ int main(int argc, char** argv)
|
|||
optimizationLevel = level;
|
||||
}
|
||||
|
||||
int rseed = -1;
|
||||
if (doctest::parseIntOption(argc, argv, "--random-seed=", doctest::option_int, rseed))
|
||||
randomSeed = unsigned(rseed);
|
||||
|
||||
if (doctest::parseOption(argc, argv, "--randomize") && !randomSeed)
|
||||
{
|
||||
randomSeed = std::random_device()();
|
||||
printf("Using RNG seed %u\n", *randomSeed);
|
||||
}
|
||||
|
||||
if (std::vector<doctest::String> flags; doctest::parseCommaSepArgs(argc, argv, "--fflags=", flags))
|
||||
setFastFlags(flags);
|
||||
|
||||
|
@ -295,6 +310,8 @@ int main(int argc, char** argv)
|
|||
printf(" --verbose Enables verbose output (e.g. lua 'print' statements)\n");
|
||||
printf(" --fflags= Sets specified fast flags\n");
|
||||
printf(" --list-fflags List all fast flags\n");
|
||||
printf(" --randomize Use a random RNG seed\n");
|
||||
printf(" --random-seed=n Use a particular RNG seed\n");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -5,3 +5,6 @@ type synthetic add -x "^Luau::Variant<.+>$" -l lldb_formatters.LuauVariantSynthe
|
|||
type summary add -x "^Luau::Variant<.+>$" -F lldb_formatters.luau_variant_summary
|
||||
|
||||
type synthetic add -x "^Luau::AstArray<.+>$" -l lldb_formatters.AstArraySyntheticChildrenProvider
|
||||
|
||||
type summary add --summary-string "${var.line}:${var.column}" Luau::Position
|
||||
type summary add --summary-string "${var.begin}-${var.end}" Luau::Location
|
||||
|
|
|
@ -22,4 +22,25 @@
|
|||
</Expand>
|
||||
</Type>
|
||||
|
||||
<Type Name="Luau::AstName">
|
||||
<DisplayString>{value,na}</DisplayString>
|
||||
</Type>
|
||||
|
||||
<Type Name="Luau::AstLocal">
|
||||
<DisplayString>{name.value,na}</DisplayString>
|
||||
</Type>
|
||||
|
||||
<Type Name="Luau::Symbol">
|
||||
<DisplayString Condition="local">local {local->name.value,na}</DisplayString>
|
||||
<DisplayString>global {global.value,na}</DisplayString>
|
||||
</Type>
|
||||
|
||||
<Type Name="Luau::Position">
|
||||
<DisplayString>{line}:{column}</DisplayString>
|
||||
</Type>
|
||||
|
||||
<Type Name="Luau::Location">
|
||||
<DisplayString>{begin}-{end}</DisplayString>
|
||||
</Type>
|
||||
|
||||
</AutoVisualizer>
|
||||
|
|
|
@ -22,6 +22,10 @@ def safeParseInt(i, default=0):
|
|||
return default
|
||||
|
||||
|
||||
def makeDottedName(path):
|
||||
return ".".join(path)
|
||||
|
||||
|
||||
class Handler(x.ContentHandler):
|
||||
def __init__(self, failList):
|
||||
self.currentTest = []
|
||||
|
@ -41,7 +45,7 @@ class Handler(x.ContentHandler):
|
|||
if self.currentTest:
|
||||
passed = attrs["test_case_success"] == "true"
|
||||
|
||||
dottedName = ".".join(self.currentTest)
|
||||
dottedName = makeDottedName(self.currentTest)
|
||||
|
||||
# Sometimes we get multiple XML trees for the same test. All of
|
||||
# them must report a pass in order for us to consider the test
|
||||
|
@ -60,6 +64,10 @@ class Handler(x.ContentHandler):
|
|||
self.currentTest.pop()
|
||||
|
||||
|
||||
def print_stderr(*args, **kw):
|
||||
print(*args, **kw, file=sys.stderr)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Run Luau.UnitTest with deferred constraint resolution enabled"
|
||||
|
@ -80,6 +88,16 @@ def main():
|
|||
help="Write a new faillist.txt after running tests.",
|
||||
)
|
||||
|
||||
parser.add_argument("--randomize", action="store_true", help="Pick a random seed")
|
||||
|
||||
parser.add_argument(
|
||||
"--random-seed",
|
||||
action="store",
|
||||
dest="random_seed",
|
||||
type=int,
|
||||
help="Accept a specific RNG seed",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
failList = loadFailList()
|
||||
|
@ -90,7 +108,12 @@ def main():
|
|||
"--fflags=true,DebugLuauDeferredConstraintResolution=true",
|
||||
]
|
||||
|
||||
print('>', ' '.join(commandLine), file=sys.stderr)
|
||||
if args.random_seed:
|
||||
commandLine.append("--random-seed=" + str(args.random_seed))
|
||||
elif args.randomize:
|
||||
commandLine.append("--randomize")
|
||||
|
||||
print_stderr(">", " ".join(commandLine))
|
||||
|
||||
p = sp.Popen(
|
||||
commandLine,
|
||||
|
@ -104,15 +127,21 @@ def main():
|
|||
sys.stdout.buffer.write(line)
|
||||
return
|
||||
else:
|
||||
x.parse(p.stdout, handler)
|
||||
try:
|
||||
x.parse(p.stdout, handler)
|
||||
except x.SAXParseException as e:
|
||||
print_stderr(
|
||||
f"XML parsing failed during test {makeDottedName(handler.currentTest)}. That probably means that the test crashed"
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
p.wait()
|
||||
|
||||
for testName, passed in handler.results.items():
|
||||
if passed and testName in failList:
|
||||
print("UNEXPECTED: {} should have failed".format(testName))
|
||||
print_stderr(f"UNEXPECTED: {testName} should have failed")
|
||||
elif not passed and testName not in failList:
|
||||
print("UNEXPECTED: {} should have passed".format(testName))
|
||||
print_stderr(f"UNEXPECTED: {testName} should have passed")
|
||||
|
||||
if args.write:
|
||||
newFailList = sorted(
|
||||
|
@ -126,14 +155,11 @@ def main():
|
|||
with open(FAIL_LIST_PATH, "w", newline="\n") as f:
|
||||
for name in newFailList:
|
||||
print(name, file=f)
|
||||
print("Updated faillist.txt", file=sys.stderr)
|
||||
print_stderr("Updated faillist.txt")
|
||||
|
||||
if handler.numSkippedTests > 0:
|
||||
print(
|
||||
"{} test(s) were skipped! That probably means that a test segfaulted!".format(
|
||||
handler.numSkippedTests
|
||||
),
|
||||
file=sys.stderr,
|
||||
print_stderr(
|
||||
f"{handler.numSkippedTests} test(s) were skipped! That probably means that a test segfaulted!"
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
|
@ -143,7 +169,7 @@ def main():
|
|||
)
|
||||
|
||||
if ok:
|
||||
print("Everything in order!", file=sys.stderr)
|
||||
print_stderr("Everything in order!")
|
||||
|
||||
sys.exit(0 if ok else 1)
|
||||
|
||||
|
|
Loading…
Reference in a new issue