Sync to upstream/release/647

This commit is contained in:
Vighnesh 2024-10-11 09:38:27 -07:00
parent 8531df04c9
commit aa2e5c096d
24 changed files with 423 additions and 183 deletions

View File

@ -6,7 +6,7 @@
#include <optional>
#include <string>
#include <unordered_map>
#include <map>
#include <vector>
using lua_State = struct lua_State;
@ -182,7 +182,7 @@ struct TypeFunctionProperty
struct TypeFunctionTableType
{
using Name = std::string;
using Props = std::unordered_map<Name, TypeFunctionProperty>;
using Props = std::map<Name, TypeFunctionProperty>;
Props props;
@ -195,7 +195,7 @@ struct TypeFunctionTableType
struct TypeFunctionClassType
{
using Name = std::string;
using Props = std::unordered_map<Name, TypeFunctionProperty>;
using Props = std::map<Name, TypeFunctionProperty>;
Props props;
@ -260,6 +260,7 @@ bool isTypeUserData(lua_State* L, int idx);
TypeFunctionTypeId getTypeUserData(lua_State* L, int idx);
std::optional<TypeFunctionTypeId> optionalTypeUserData(lua_State* L, int idx);
void registerTypesLibrary(lua_State* L);
void registerTypeUserData(lua_State* L);
void setTypeFunctionEnvironment(lua_State* L);

View File

@ -881,7 +881,7 @@ struct AstJsonEncoder : public AstVisitor
PROP(name);
PROP(generics);
PROP(genericPacks);
PROP(type);
write("value", node->type);
PROP(exported);
}
);

View File

@ -26,7 +26,6 @@
*/
LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAGVARIABLE(LuauDCRMagicFunctionTypeChecker, false);
namespace Luau
{
@ -931,8 +930,7 @@ TypeId makeStringMetatable(NotNull<BuiltinTypes> builtinTypes)
formatFTV.isCheckedFunction = true;
const TypeId formatFn = arena->addType(formatFTV);
attachDcrMagicFunction(formatFn, dcrMagicFunctionFormat);
if (FFlag::LuauDCRMagicFunctionTypeChecker)
attachDcrMagicFunctionTypeCheck(formatFn, dcrMagicFunctionTypeCheckFormat);
attachDcrMagicFunctionTypeCheck(formatFn, dcrMagicFunctionTypeCheckFormat);
const TypeId stringToStringType = makeFunction(*arena, std::nullopt, {}, {}, {stringType}, {}, {stringType}, /* checked */ true);

View File

@ -31,10 +31,7 @@ LUAU_FASTFLAGVARIABLE(DebugLuauLogSolver, false)
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverIncludeDependencies, false)
LUAU_FASTFLAGVARIABLE(DebugLuauLogBindings, false)
LUAU_FASTINTVARIABLE(LuauSolverRecursionLimit, 500)
// The default value here is 643 because the first release in which this was implemented is 644,
// and actively we want new changes to be off by default until they're enabled consciously.
LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeSolverRelease, 643)
LUAU_DYNAMIC_FASTINT(LuauTypeSolverRelease)
namespace Luau
{

View File

@ -44,9 +44,9 @@ LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJsonFile, false)
LUAU_FASTFLAGVARIABLE(DebugLuauForbidInternalTypes, false)
LUAU_FASTFLAGVARIABLE(DebugLuauForceStrictMode, false)
LUAU_FASTFLAGVARIABLE(DebugLuauForceNonStrictMode, false)
LUAU_FASTFLAGVARIABLE(LuauSourceModuleUpdatedWithSelectedMode, false)
LUAU_FASTFLAGVARIABLE(LuauUserDefinedTypeFunctionNoEvaluation, false)
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauRunCustomModuleChecks, false)
LUAU_FASTFLAGVARIABLE(LuauMoreThoroughCycleDetection, false)
LUAU_FASTFLAG(StudioReportLuauAny2)
@ -883,14 +883,18 @@ void Frontend::addBuildQueueItems(
data.environmentScope = getModuleEnvironment(*sourceModule, data.config, frontendOptions.forAutocomplete);
data.recordJsonLog = FFlag::DebugLuauLogSolverToJson;
Mode mode = sourceModule->mode.value_or(data.config.mode);
const Mode mode = sourceModule->mode.value_or(data.config.mode);
// in NoCheck mode we only need to compute the value of .cyclic for typeck
// in the future we could replace toposort with an algorithm that can flag cyclic nodes by itself
// however, for now getRequireCycles isn't expensive in practice on the cases we care about, and long term
// all correct programs must be acyclic so this code triggers rarely
if (cycleDetected)
data.requireCycles = getRequireCycles(fileResolver, sourceNodes, sourceNode.get(), mode == Mode::NoCheck);
{
if (FFlag::LuauMoreThoroughCycleDetection)
data.requireCycles = getRequireCycles(fileResolver, sourceNodes, sourceNode.get(), false);
else
data.requireCycles = getRequireCycles(fileResolver, sourceNodes, sourceNode.get(), mode == Mode::NoCheck);
}
data.options = frontendOptions;
@ -922,8 +926,7 @@ void Frontend::checkBuildQueueItem(BuildQueueItem& item)
else
mode = sourceModule.mode.value_or(config.mode);
if (FFlag::LuauSourceModuleUpdatedWithSelectedMode)
item.sourceModule->mode = {mode};
item.sourceModule->mode = {mode};
ScopePtr environmentScope = item.environmentScope;
double timestamp = getTimestamp();
const std::vector<RequireCycle>& requireCycles = item.requireCycles;

View File

@ -16,8 +16,6 @@
#include "Luau/Unifier.h"
LUAU_FASTFLAGVARIABLE(DebugLuauCheckNormalizeInvariant, false)
LUAU_FASTFLAGVARIABLE(LuauFixReduceStackPressure, false);
LUAU_FASTFLAGVARIABLE(LuauFixCyclicTablesBlowingStack, false);
LUAU_FASTINTVARIABLE(LuauNormalizeCacheLimit, 100000);
LUAU_FASTFLAG(LuauSolverV2);
@ -25,16 +23,6 @@ LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAGVARIABLE(LuauUseNormalizeIntersectionLimit, false)
LUAU_FASTINTVARIABLE(LuauNormalizeIntersectionLimit, 200)
static bool fixReduceStackPressure()
{
return FFlag::LuauFixReduceStackPressure || FFlag::LuauSolverV2;
}
static bool fixCyclicTablesBlowingStack()
{
return FFlag::LuauFixCyclicTablesBlowingStack || FFlag::LuauSolverV2;
}
namespace Luau
{
@ -2583,43 +2571,29 @@ std::optional<TypeId> Normalizer::intersectionOfTables(TypeId here, TypeId there
if (tprop.readTy.has_value())
{
// if the intersection of the read types of a property is uninhabited, the whole table is `never`.
if (fixReduceStackPressure())
// We've seen these table prop elements before and we're about to ask if their intersection
// is inhabited
if (seenSet.contains(*hprop.readTy) && seenSet.contains(*tprop.readTy))
{
// We've seen these table prop elements before and we're about to ask if their intersection
// is inhabited
if (fixCyclicTablesBlowingStack())
{
if (seenSet.contains(*hprop.readTy) && seenSet.contains(*tprop.readTy))
{
seenSet.erase(*hprop.readTy);
seenSet.erase(*tprop.readTy);
return {builtinTypes->neverType};
}
else
{
seenSet.insert(*hprop.readTy);
seenSet.insert(*tprop.readTy);
}
}
NormalizationResult res = isIntersectionInhabited(*hprop.readTy, *tprop.readTy);
// Cleanup
if (fixCyclicTablesBlowingStack())
{
seenSet.erase(*hprop.readTy);
seenSet.erase(*tprop.readTy);
}
if (NormalizationResult::True != res)
return {builtinTypes->neverType};
seenSet.erase(*hprop.readTy);
seenSet.erase(*tprop.readTy);
return {builtinTypes->neverType};
}
else
{
if (NormalizationResult::False == isIntersectionInhabited(*hprop.readTy, *tprop.readTy))
return {builtinTypes->neverType};
seenSet.insert(*hprop.readTy);
seenSet.insert(*tprop.readTy);
}
NormalizationResult res = isIntersectionInhabited(*hprop.readTy, *tprop.readTy);
// Cleanup
seenSet.erase(*hprop.readTy);
seenSet.erase(*tprop.readTy);
if (NormalizationResult::True != res)
return {builtinTypes->neverType};
TypeId ty = simplifyIntersection(builtinTypes, NotNull{arena}, *hprop.readTy, *tprop.readTy).result;
prop.readTy = ty;
hereSubThere &= (ty == hprop.readTy);

View File

@ -1625,8 +1625,11 @@ void TypeChecker2::indexExprMetatableHelper(AstExprIndexExpr* indexExpr, const M
indexExprMetatableHelper(indexExpr, mtmt, exprType, indexType);
else
{
LUAU_ASSERT(tt || get<PrimitiveType>(follow(metaTable->table)));
if (!(DFInt::LuauTypeSolverRelease >= 647))
{
LUAU_ASSERT(tt || get<PrimitiveType>(follow(metaTable->table)));
}
// CLI-122161: We're not handling unions correctly (probably).
reportError(CannotExtendTable{exprType, CannotExtendTable::Indexer, "indexer??"}, indexExpr->location);
}
}

View File

@ -48,6 +48,7 @@ LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyUseGuesserDepth, -1);
LUAU_FASTFLAGVARIABLE(DebugLuauLogTypeFamilies, false)
LUAU_FASTFLAGVARIABLE(LuauUserDefinedTypeFunctions2, false)
LUAU_FASTFLAG(LuauUserDefinedTypeFunctionNoEvaluation)
LUAU_FASTFLAG(LuauUserTypeFunFixRegister)
LUAU_DYNAMIC_FASTINT(LuauTypeSolverRelease)
@ -1018,9 +1019,11 @@ void TypeFunctionRuntime::prepareState()
setTypeFunctionEnvironment(L);
// Register type userdata
registerTypeUserData(L);
if (FFlag::LuauUserTypeFunFixRegister)
registerTypesLibrary(L);
luaL_sandbox(L);
luaL_sandboxthread(L);
}

View File

@ -15,6 +15,7 @@
LUAU_DYNAMIC_FASTINT(LuauTypeFunctionSerdeIterationLimit)
LUAU_DYNAMIC_FASTINT(LuauTypeSolverRelease)
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunFixRegister, false)
namespace Luau
{
@ -1381,11 +1382,11 @@ static int isEqualToType(lua_State* L)
return 1;
}
// Register the type userdata
void registerTypeUserData(lua_State* L)
void registerTypesLibrary(lua_State* L)
{
// List of fields for type userdata
luaL_Reg typeUserdataFields[] = {
LUAU_ASSERT(FFlag::LuauUserTypeFunFixRegister);
luaL_Reg fields[] = {
{"unknown", createUnknown},
{"never", createNever},
{"any", createAny},
@ -1395,8 +1396,7 @@ void registerTypeUserData(lua_State* L)
{nullptr, nullptr}
};
// List of methods for type userdata
luaL_Reg typeUserdataMethods[] = {
luaL_Reg methods[] = {
{"singleton", createSingleton},
{"negationof", createNegation},
{"unionof", createUnion},
@ -1405,72 +1405,191 @@ void registerTypeUserData(lua_State* L)
{"newfunction", createFunction},
{"copy", deepCopy},
// Common methods
{"is", checkTag},
// Negation type methods
{"inner", getNegatedValue},
// Singleton type methods
{"value", getSingletonValue},
// Table type methods
{"setproperty", setTableProp},
{"setreadproperty", setReadTableProp},
{"setwriteproperty", setWriteTableProp},
{"readproperty", readTableProp},
{"writeproperty", writeTableProp},
{"properties", getProps},
{"setindexer", setTableIndexer},
{"setreadindexer", setTableReadIndexer},
{"setwriteindexer", setTableWriteIndexer},
{"indexer", getIndexer},
{"readindexer", getReadIndexer},
{"writeindexer", getWriteIndexer},
{"setmetatable", setTableMetatable},
{"metatable", getMetatable},
// Function type methods
{"setparameters", setFunctionParameters},
{"parameters", getFunctionParameters},
{"setreturns", setFunctionReturns},
{"returns", getFunctionReturns},
// Union and Intersection type methods
{"components", getComponents},
// Class type methods
{"parent", getClassParent},
{"indexer", getIndexer},
{nullptr, nullptr}
};
// Create and register metatable for type userdata
luaL_newmetatable(L, "type");
// Protect metatable from being fetched.
lua_pushstring(L, "The metatable is locked");
lua_setfield(L, -2, "__metatable");
// Set type userdata metatable's __eq to type_equals()
lua_pushcfunction(L, isEqualToType, "__eq");
lua_setfield(L, -2, "__eq");
// Set type userdata metatable's __index to itself
lua_pushvalue(L, -1); // Push a copy of type userdata metatable
lua_setfield(L, -2, "__index");
luaL_register(L, nullptr, typeUserdataMethods);
luaL_register(L, "types", methods);
// Set fields for type userdata
for (luaL_Reg* l = typeUserdataFields; l->name; l++)
for (luaL_Reg* l = fields; l->name; l++)
{
l->func(L);
lua_setfield(L, -2, l->name);
}
// Set types library as a global name "types"
lua_setglobal(L, "types");
lua_pop(L, 1);
}
static int typeUserdataIndex(lua_State* L)
{
TypeFunctionTypeId self = getTypeUserData(L, 1);
const char* field = luaL_checkstring(L, 2);
if (strcmp(field, "tag") == 0)
{
lua_pushstring(L, getTag(L, self).c_str());
return 1;
}
lua_pushvalue(L, lua_upvalueindex(1));
lua_getfield(L, -1, field);
return 1;
}
void registerTypeUserData(lua_State* L)
{
if (FFlag::LuauUserTypeFunFixRegister)
{
luaL_Reg typeUserdataMethods[] = {
{"is", checkTag},
// Negation type methods
{"inner", getNegatedValue},
// Singleton type methods
{"value", getSingletonValue},
// Table type methods
{"setproperty", setTableProp},
{"setreadproperty", setReadTableProp},
{"setwriteproperty", setWriteTableProp},
{"readproperty", readTableProp},
{"writeproperty", writeTableProp},
{"properties", getProps},
{"setindexer", setTableIndexer},
{"setreadindexer", setTableReadIndexer},
{"setwriteindexer", setTableWriteIndexer},
{"indexer", getIndexer},
{"readindexer", getReadIndexer},
{"writeindexer", getWriteIndexer},
{"setmetatable", setTableMetatable},
{"metatable", getMetatable},
// Function type methods
{"setparameters", setFunctionParameters},
{"parameters", getFunctionParameters},
{"setreturns", setFunctionReturns},
{"returns", getFunctionReturns},
// Union and Intersection type methods
{"components", getComponents},
// Class type methods
{"parent", getClassParent},
{nullptr, nullptr}
};
// Create and register metatable for type userdata
luaL_newmetatable(L, "type");
// Protect metatable from being changed
lua_pushstring(L, "The metatable is locked");
lua_setfield(L, -2, "__metatable");
lua_pushcfunction(L, isEqualToType, "__eq");
lua_setfield(L, -2, "__eq");
// Indexing will be a dynamic function because some type fields are dynamic
lua_newtable(L);
luaL_register(L, nullptr, typeUserdataMethods);
lua_setreadonly(L, -1, true);
lua_pushcclosure(L, typeUserdataIndex, "__index", 1);
lua_setfield(L, -2, "__index");
lua_setreadonly(L, -1, true);
lua_pop(L, 1);
}
else
{
// List of fields for type userdata
luaL_Reg typeUserdataFields[] = {
{"unknown", createUnknown},
{"never", createNever},
{"any", createAny},
{"boolean", createBoolean},
{"number", createNumber},
{"string", createString},
{nullptr, nullptr}
};
// List of methods for type userdata
luaL_Reg typeUserdataMethods[] = {
{"singleton", createSingleton},
{"negationof", createNegation},
{"unionof", createUnion},
{"intersectionof", createIntersection},
{"newtable", createTable},
{"newfunction", createFunction},
{"copy", deepCopy},
// Common methods
{"is", checkTag},
// Negation type methods
{"inner", getNegatedValue},
// Singleton type methods
{"value", getSingletonValue},
// Table type methods
{"setproperty", setTableProp},
{"setreadproperty", setReadTableProp},
{"setwriteproperty", setWriteTableProp},
{"readproperty", readTableProp},
{"writeproperty", writeTableProp},
{"properties", getProps},
{"setindexer", setTableIndexer},
{"setreadindexer", setTableReadIndexer},
{"setwriteindexer", setTableWriteIndexer},
{"indexer", getIndexer},
{"readindexer", getReadIndexer},
{"writeindexer", getWriteIndexer},
{"setmetatable", setTableMetatable},
{"metatable", getMetatable},
// Function type methods
{"setparameters", setFunctionParameters},
{"parameters", getFunctionParameters},
{"setreturns", setFunctionReturns},
{"returns", getFunctionReturns},
// Union and Intersection type methods
{"components", getComponents},
// Class type methods
{"parent", getClassParent},
{"indexer", getIndexer},
{nullptr, nullptr}
};
// Create and register metatable for type userdata
luaL_newmetatable(L, "type");
// Protect metatable from being fetched.
lua_pushstring(L, "The metatable is locked");
lua_setfield(L, -2, "__metatable");
// Set type userdata metatable's __eq to type_equals()
lua_pushcfunction(L, isEqualToType, "__eq");
lua_setfield(L, -2, "__eq");
// Set type userdata metatable's __index to itself
lua_pushvalue(L, -1); // Push a copy of type userdata metatable
lua_setfield(L, -2, "__index");
luaL_register(L, nullptr, typeUserdataMethods);
// Set fields for type userdata
for (luaL_Reg* l = typeUserdataFields; l->name; l++)
{
l->func(L);
lua_setfield(L, -2, l->name);
}
// Set types library as a global name "types"
lua_setglobal(L, "types");
}
// Sets up a destructor for the type userdata.
lua_setuserdatadtor(L, kTypeUserdataTag, deallocTypeUserData);

View File

@ -5,6 +5,11 @@
LUAU_FASTFLAG(LuauNativeAttribute);
// The default value here is 643 because the first release in which this was implemented is 644,
// and actively we want new changes to be off by default until they're enabled consciously.
// The flag is placed in AST project here to be common in all Luau libraries
LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeSolverRelease, 643)
namespace Luau
{

View File

@ -2,11 +2,14 @@
#include "Luau/Common.h"
#include "Luau/ExperimentalFlags.h"
#include <limits> // TODO: remove with LuauTypeSolverRelease
#include <string_view>
#include <stdio.h>
#include <string.h>
LUAU_DYNAMIC_FASTINT(LuauTypeSolverRelease)
static void setLuauFlag(std::string_view name, bool state)
{
for (Luau::FValue<bool>* flag = Luau::FValue<bool>::list; flag; flag = flag->next)
@ -23,6 +26,13 @@ static void setLuauFlag(std::string_view name, bool state)
static void setLuauFlags(bool state)
{
if (state)
{
// Setting flags to 'true' means enabling all Luau flags including new type solver
// In that case, it is provided with all fixes enabled (as if each fix had its own boolean flag)
DFInt::LuauTypeSolverRelease.value = std::numeric_limits<int>::max();
}
for (Luau::FValue<bool>* flag = Luau::FValue<bool>::list; flag; flag = flag->next)
if (strncmp(flag->name, "Luau", 4) == 0)
flag->value = state;

View File

@ -346,13 +346,13 @@ void SharedCodeGenContextDeleter::operator()(const SharedCodeGenContext* codeGen
return static_cast<BaseCodeGenContext*>(L->global->ecb.context);
}
static void onCloseState(lua_State* L) noexcept
static void onCloseState(lua_State* L)
{
getCodeGenContext(L)->onCloseState();
L->global->ecb = lua_ExecutionCallbacks{};
}
static void onDestroyFunction(lua_State* L, Proto* proto) noexcept
static void onDestroyFunction(lua_State* L, Proto* proto)
{
getCodeGenContext(L)->onDestroyFunction(proto->execdata);
proto->execdata = nullptr;

View File

@ -453,6 +453,8 @@ struct lua_Callbacks
void (*debugstep)(lua_State* L, lua_Debug* ar); // gets called after each instruction in single step mode
void (*debuginterrupt)(lua_State* L, lua_Debug* ar); // gets called when thread execution is interrupted by break in another thread
void (*debugprotectederror)(lua_State* L); // gets called when protected call results in an error
void (*onallocate)(lua_State* L, int osize, int nsize); // gets called when memory is allocated
};
typedef struct lua_Callbacks lua_Callbacks;

View File

@ -504,6 +504,11 @@ void* luaM_new_(lua_State* L, size_t nsize, uint8_t memcat)
g->totalbytes += nsize;
g->memcatbytes[memcat] += nsize;
if (LUAU_UNLIKELY(!!g->cb.onallocate))
{
g->cb.onallocate(L, 0, nsize);
}
return block;
}
@ -539,6 +544,11 @@ GCObject* luaM_newgco_(lua_State* L, size_t nsize, uint8_t memcat)
g->totalbytes += nsize;
g->memcatbytes[memcat] += nsize;
if (LUAU_UNLIKELY(!!g->cb.onallocate))
{
g->cb.onallocate(L, 0, nsize);
}
return (GCObject*)block;
}
@ -618,6 +628,12 @@ void* luaM_realloc_(lua_State* L, void* block, size_t osize, size_t nsize, uint8
LUAU_ASSERT((nsize == 0) == (result == NULL));
g->totalbytes = (g->totalbytes - osize) + nsize;
g->memcatbytes[memcat] += nsize - osize;
if (LUAU_UNLIKELY(!!g->cb.onallocate))
{
g->cb.onallocate(L, osize, nsize);
}
return result;
}

View File

@ -508,6 +508,9 @@ def runTest(subdir, filename, filepath):
filepath = os.path.abspath(filepath)
mainVm = os.path.abspath(arguments.vm)
if not os.path.isfile(mainVm):
print(f"{colored(Color.RED, 'ERROR')}: VM executable '{mainVm}' does not exist.")
sys.exit(1)
# Process output will contain the test name and execution times
mainOutput = getVmOutput(substituteArguments(mainVm, getExtraArguments(filepath)) + " " + filepath)
@ -887,9 +890,11 @@ def run(args, argsubcb):
analyzeResult('', mainResult, compareResults)
else:
all_files = [subdir + os.sep + filename for subdir, dirs, files in os.walk(arguments.folder) for filename in files]
if len(all_files) == 0:
print(f"{colored(Color.YELLOW, 'WARNING')}: No test files found in '{arguments.folder}'.")
for filepath in sorted(all_files):
subdir, filename = os.path.split(filepath)
if filename.endswith(".lua"):
if filename.endswith(".lua") or filename.endswith(".luau"):
if arguments.run_test == None or re.match(arguments.run_test, filename[:-4]):
runTest(subdir, filename, filepath)

View File

@ -138,7 +138,7 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_table_array")
CHECK(
json ==
R"({"type":"AstStatBlock","location":"0,0 - 0,17","hasEnd":true,"body":[{"type":"AstStatTypeAlias","location":"0,0 - 0,17","name":"X","generics":[],"genericPacks":[],"type":{"type":"AstTypeTable","location":"0,9 - 0,17","props":[],"indexer":{"location":"0,10 - 0,16","indexType":{"type":"AstTypeReference","location":"0,10 - 0,16","name":"number","nameLocation":"0,10 - 0,16","parameters":[]},"resultType":{"type":"AstTypeReference","location":"0,10 - 0,16","name":"string","nameLocation":"0,10 - 0,16","parameters":[]}}},"exported":false}]})"
R"({"type":"AstStatBlock","location":"0,0 - 0,17","hasEnd":true,"body":[{"type":"AstStatTypeAlias","location":"0,0 - 0,17","name":"X","generics":[],"genericPacks":[],"value":{"type":"AstTypeTable","location":"0,9 - 0,17","props":[],"indexer":{"location":"0,10 - 0,16","indexType":{"type":"AstTypeReference","location":"0,10 - 0,16","name":"number","nameLocation":"0,10 - 0,16","parameters":[]},"resultType":{"type":"AstTypeReference","location":"0,10 - 0,16","name":"string","nameLocation":"0,10 - 0,16","parameters":[]}}},"exported":false}]})"
);
}
@ -151,7 +151,7 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_table_indexer")
CHECK(
json ==
R"({"type":"AstStatBlock","location":"0,0 - 0,17","hasEnd":true,"body":[{"type":"AstStatTypeAlias","location":"0,0 - 0,17","name":"X","generics":[],"genericPacks":[],"type":{"type":"AstTypeTable","location":"0,9 - 0,17","props":[],"indexer":{"location":"0,10 - 0,16","indexType":{"type":"AstTypeReference","location":"0,10 - 0,16","name":"number","nameLocation":"0,10 - 0,16","parameters":[]},"resultType":{"type":"AstTypeReference","location":"0,10 - 0,16","name":"string","nameLocation":"0,10 - 0,16","parameters":[]}}},"exported":false}]})"
R"({"type":"AstStatBlock","location":"0,0 - 0,17","hasEnd":true,"body":[{"type":"AstStatTypeAlias","location":"0,0 - 0,17","name":"X","generics":[],"genericPacks":[],"value":{"type":"AstTypeTable","location":"0,9 - 0,17","props":[],"indexer":{"location":"0,10 - 0,16","indexType":{"type":"AstTypeReference","location":"0,10 - 0,16","name":"number","nameLocation":"0,10 - 0,16","parameters":[]},"resultType":{"type":"AstTypeReference","location":"0,10 - 0,16","name":"string","nameLocation":"0,10 - 0,16","parameters":[]}}},"exported":false}]})"
);
}
@ -408,7 +408,7 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatTypeAlias")
AstStat* statement = expectParseStatement("type A = B");
std::string_view expected =
R"({"type":"AstStatTypeAlias","location":"0,0 - 0,10","name":"A","generics":[],"genericPacks":[],"type":{"type":"AstTypeReference","location":"0,9 - 0,10","name":"B","nameLocation":"0,9 - 0,10","parameters":[]},"exported":false})";
R"({"type":"AstStatTypeAlias","location":"0,0 - 0,10","name":"A","generics":[],"genericPacks":[],"value":{"type":"AstTypeReference","location":"0,9 - 0,10","name":"B","nameLocation":"0,9 - 0,10","parameters":[]},"exported":false})";
CHECK(toJson(statement) == expected);
}
@ -462,7 +462,7 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_annotation")
AstStat* statement = expectParseStatement("type T = ((number) -> (string | nil)) & ((string) -> ())");
std::string_view expected =
R"({"type":"AstStatTypeAlias","location":"0,0 - 0,55","name":"T","generics":[],"genericPacks":[],"type":{"type":"AstTypeIntersection","location":"0,9 - 0,55","types":[{"type":"AstTypeFunction","location":"0,10 - 0,36","generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,11 - 0,17","name":"number","nameLocation":"0,11 - 0,17","parameters":[]}]},"argNames":[],"returnTypes":{"type":"AstTypeList","types":[{"type":"AstTypeUnion","location":"0,23 - 0,35","types":[{"type":"AstTypeReference","location":"0,23 - 0,29","name":"string","nameLocation":"0,23 - 0,29","parameters":[]},{"type":"AstTypeReference","location":"0,32 - 0,35","name":"nil","nameLocation":"0,32 - 0,35","parameters":[]}]}]}},{"type":"AstTypeFunction","location":"0,41 - 0,55","generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,42 - 0,48","name":"string","nameLocation":"0,42 - 0,48","parameters":[]}]},"argNames":[],"returnTypes":{"type":"AstTypeList","types":[]}}]},"exported":false})";
R"({"type":"AstStatTypeAlias","location":"0,0 - 0,55","name":"T","generics":[],"genericPacks":[],"value":{"type":"AstTypeIntersection","location":"0,9 - 0,55","types":[{"type":"AstTypeFunction","location":"0,10 - 0,36","generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,11 - 0,17","name":"number","nameLocation":"0,11 - 0,17","parameters":[]}]},"argNames":[],"returnTypes":{"type":"AstTypeList","types":[{"type":"AstTypeUnion","location":"0,23 - 0,35","types":[{"type":"AstTypeReference","location":"0,23 - 0,29","name":"string","nameLocation":"0,23 - 0,29","parameters":[]},{"type":"AstTypeReference","location":"0,32 - 0,35","name":"nil","nameLocation":"0,32 - 0,35","parameters":[]}]}]}},{"type":"AstTypeFunction","location":"0,41 - 0,55","generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,42 - 0,48","name":"string","nameLocation":"0,42 - 0,48","parameters":[]}]},"argNames":[],"returnTypes":{"type":"AstTypeList","types":[]}}]},"exported":false})";
CHECK(toJson(statement) == expected);
}
@ -474,7 +474,7 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_type_literal")
auto json = toJson(statement);
std::string_view expected =
R"({"type":"AstStatTypeAlias","location":"0,0 - 0,73","name":"Action","generics":[],"genericPacks":[],"type":{"type":"AstTypeTable","location":"0,14 - 0,73","props":[{"name":"strings","type":"AstTableProp","location":"0,16 - 0,23","propType":{"type":"AstTypeUnion","location":"0,25 - 0,40","types":[{"type":"AstTypeSingletonString","location":"0,25 - 0,28","value":"A"},{"type":"AstTypeSingletonString","location":"0,31 - 0,34","value":"B"},{"type":"AstTypeSingletonString","location":"0,37 - 0,40","value":"C"}]}},{"name":"mixed","type":"AstTableProp","location":"0,42 - 0,47","propType":{"type":"AstTypeUnion","location":"0,49 - 0,71","types":[{"type":"AstTypeSingletonString","location":"0,49 - 0,55","value":"This"},{"type":"AstTypeSingletonString","location":"0,58 - 0,64","value":"That"},{"type":"AstTypeSingletonBool","location":"0,67 - 0,71","value":true}]}}],"indexer":null},"exported":false})";
R"({"type":"AstStatTypeAlias","location":"0,0 - 0,73","name":"Action","generics":[],"genericPacks":[],"value":{"type":"AstTypeTable","location":"0,14 - 0,73","props":[{"name":"strings","type":"AstTableProp","location":"0,16 - 0,23","propType":{"type":"AstTypeUnion","location":"0,25 - 0,40","types":[{"type":"AstTypeSingletonString","location":"0,25 - 0,28","value":"A"},{"type":"AstTypeSingletonString","location":"0,31 - 0,34","value":"B"},{"type":"AstTypeSingletonString","location":"0,37 - 0,40","value":"C"}]}},{"name":"mixed","type":"AstTableProp","location":"0,42 - 0,47","propType":{"type":"AstTypeUnion","location":"0,49 - 0,71","types":[{"type":"AstTypeSingletonString","location":"0,49 - 0,55","value":"This"},{"type":"AstTypeSingletonString","location":"0,58 - 0,64","value":"That"},{"type":"AstTypeSingletonBool","location":"0,67 - 0,71","value":true}]}}],"indexer":null},"exported":false})";
CHECK(toJson(statement) == expected);
}
@ -484,7 +484,7 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_indexed_type_literal")
AstStat* statement = expectParseStatement(R"(type StringSet = { [string]: true })");
std::string_view expected =
R"({"type":"AstStatTypeAlias","location":"0,0 - 0,35","name":"StringSet","generics":[],"genericPacks":[],"type":{"type":"AstTypeTable","location":"0,17 - 0,35","props":[],"indexer":{"location":"0,19 - 0,33","indexType":{"type":"AstTypeReference","location":"0,20 - 0,26","name":"string","nameLocation":"0,20 - 0,26","parameters":[]},"resultType":{"type":"AstTypeSingletonBool","location":"0,29 - 0,33","value":true}}},"exported":false})";
R"({"type":"AstStatTypeAlias","location":"0,0 - 0,35","name":"StringSet","generics":[],"genericPacks":[],"value":{"type":"AstTypeTable","location":"0,17 - 0,35","props":[],"indexer":{"location":"0,19 - 0,33","indexType":{"type":"AstTypeReference","location":"0,20 - 0,26","name":"string","nameLocation":"0,20 - 0,26","parameters":[]},"resultType":{"type":"AstTypeSingletonBool","location":"0,29 - 0,33","value":true}}},"exported":false})";
CHECK(toJson(statement) == expected);
}
@ -494,7 +494,7 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstTypeFunction")
AstStat* statement = expectParseStatement(R"(type fun = (string, bool, named: number) -> ())");
std::string_view expected =
R"({"type":"AstStatTypeAlias","location":"0,0 - 0,46","name":"fun","generics":[],"genericPacks":[],"type":{"type":"AstTypeFunction","location":"0,11 - 0,46","generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,12 - 0,18","name":"string","nameLocation":"0,12 - 0,18","parameters":[]},{"type":"AstTypeReference","location":"0,20 - 0,24","name":"bool","nameLocation":"0,20 - 0,24","parameters":[]},{"type":"AstTypeReference","location":"0,33 - 0,39","name":"number","nameLocation":"0,33 - 0,39","parameters":[]}]},"argNames":[null,null,{"type":"AstArgumentName","name":"named","location":"0,26 - 0,31"}],"returnTypes":{"type":"AstTypeList","types":[]}},"exported":false})";
R"({"type":"AstStatTypeAlias","location":"0,0 - 0,46","name":"fun","generics":[],"genericPacks":[],"value":{"type":"AstTypeFunction","location":"0,11 - 0,46","generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,12 - 0,18","name":"string","nameLocation":"0,12 - 0,18","parameters":[]},{"type":"AstTypeReference","location":"0,20 - 0,24","name":"bool","nameLocation":"0,20 - 0,24","parameters":[]},{"type":"AstTypeReference","location":"0,33 - 0,39","name":"number","nameLocation":"0,33 - 0,39","parameters":[]}]},"argNames":[null,null,{"type":"AstArgumentName","name":"named","location":"0,26 - 0,31"}],"returnTypes":{"type":"AstTypeList","types":[]}},"exported":false})";
CHECK(toJson(statement) == expected);
}
@ -507,7 +507,7 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstTypeError")
AstStat* statement = parseResult.root->body.data[0];
std::string_view expected =
R"({"type":"AstStatTypeAlias","location":"0,0 - 0,9","name":"T","generics":[],"genericPacks":[],"type":{"type":"AstTypeError","location":"0,8 - 0,9","types":[],"messageIndex":0},"exported":false})";
R"({"type":"AstStatTypeAlias","location":"0,0 - 0,9","name":"T","generics":[],"genericPacks":[],"value":{"type":"AstTypeError","location":"0,8 - 0,9","types":[],"messageIndex":0},"exported":false})";
CHECK(toJson(statement) == expected);
}

View File

@ -30,7 +30,7 @@ template<class BaseType>
struct ACFixtureImpl : BaseType
{
ACFixtureImpl()
: BaseType(true, true)
: BaseType(true)
{
}

View File

@ -25,9 +25,7 @@
static const char* mainModuleName = "MainModule";
LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAG(DebugLuauFreezeArena);
LUAU_FASTFLAG(DebugLuauLogSolverToJsonFile)
LUAU_FASTFLAG(LuauDCRMagicFunctionTypeChecker);
LUAU_DYNAMIC_FASTINT(LuauTypeSolverRelease)
extern std::optional<unsigned> randomSeed; // tests/main.cpp
@ -152,15 +150,8 @@ const Config& TestConfigResolver::getConfig(const ModuleName& name) const
return defaultConfig;
}
Fixture::Fixture(bool freeze, bool prepareAutocomplete)
: sff_DebugLuauFreezeArena(FFlag::DebugLuauFreezeArena, freeze)
, sff_LuauDCRMagicFunctionTypeChecker(FFlag::LuauDCRMagicFunctionTypeChecker, true)
// The first value of LuauTypeSolverRelease was 643, so as long as this is
// some number greater than 900 (5 years worth of releases), all tests that
// run under the new solver will run against all of the changes guarded by
// this flag.
, sff_LuauTypeSolverRelease(DFInt::LuauTypeSolverRelease, std::numeric_limits<int>::max())
, frontend(
Fixture::Fixture(bool prepareAutocomplete)
: frontend(
&fileResolver,
&configResolver,
{/* retainFullTypeGraphs= */ true, /* forAutocomplete */ false, /* runLintChecks */ false, /* randomConstraintResolutionSeed */ randomSeed}
@ -583,8 +574,8 @@ LoadDefinitionFileResult Fixture::loadDefinition(const std::string& source)
return result;
}
BuiltinsFixture::BuiltinsFixture(bool freeze, bool prepareAutocomplete)
: Fixture(freeze, prepareAutocomplete)
BuiltinsFixture::BuiltinsFixture(bool prepareAutocomplete)
: Fixture(prepareAutocomplete)
{
Luau::unfreeze(frontend.globals.globalTypes);
Luau::unfreeze(frontend.globalsForAutocomplete.globalTypes);

View File

@ -25,6 +25,8 @@
#include <optional>
#include <vector>
LUAU_FASTFLAG(DebugLuauFreezeArena)
namespace Luau
{
@ -63,7 +65,7 @@ struct TestConfigResolver : ConfigResolver
struct Fixture
{
explicit Fixture(bool freeze = true, bool prepareAutocomplete = false);
explicit Fixture(bool prepareAutocomplete = false);
~Fixture();
// Throws Luau::ParseErrors if the parse fails.
@ -100,36 +102,14 @@ struct Fixture
TypeId requireTypeAlias(const std::string& name);
TypeId requireExportedType(const ModuleName& moduleName, const std::string& name);
// TODO: Should this be in a container of some kind? Seems a little silly
// to have a bunch of flags sitting on the text fixture.
// While most flags can be flipped inside the unit test, some code changes affect the state that is part of Fixture initialization
// Most often those are changes related to builtin type definitions.
// In that case, flag can be forced to 'true' using the example below:
// ScopedFastFlag sff_LuauExampleFlagDefinition{FFlag::LuauExampleFlagDefinition, true};
// We have a couple flags that are OK to set for all tests and, in some
// cases, cannot easily be flipped on or off on a per-test basis. For these
// we set them as part of constructing the test fixture.
/* From the original commit:
*
* > This enables arena freezing for all but two unit tests. Arena
* > freezing marks the `TypeArena`'s underlying memory as read-only,
* > raising an access violation whenever you mutate it. This is useful
* > for tracking down violations of Luau's memory model.
*/
ScopedFastFlag sff_DebugLuauFreezeArena;
/* Magic typechecker functions for the new solver are initialized when the
* typechecker frontend is initialized, which is done at the beginning of
* the test: we set this flag as part of the fixture as we always want to
* enable the magic functions for, say, `string.format`.
*/
ScopedFastFlag sff_LuauDCRMagicFunctionTypeChecker;
/* While the new solver is being rolled out we are using a monotonically
* increasing version number to track new changes, we just set it to a
* sufficiently high number in tests to ensure that any guards in prod
* code pass in tests (so we don't accidentally reintroduce a bug before
* it's unflagged).
*/
ScopedFastInt sff_LuauTypeSolverRelease;
// Arena freezing marks the `TypeArena`'s underlying memory as read-only, raising an access violation whenever you mutate it.
// This is useful for tracking down violations of Luau's memory model.
ScopedFastFlag sff_DebugLuauFreezeArena{FFlag::DebugLuauFreezeArena, true};
TestFileResolver fileResolver;
TestConfigResolver configResolver;
@ -158,7 +138,7 @@ struct Fixture
struct BuiltinsFixture : Fixture
{
BuiltinsFixture(bool freeze = true, bool prepareAutocomplete = false);
BuiltinsFixture(bool prepareAutocomplete = false);
};
std::optional<std::string> pathExprToModuleName(const ModuleName& currentModuleName, const std::vector<std::string_view>& segments);

View File

@ -21,7 +21,7 @@ struct TypeFunctionFixture : Fixture
TypeFunction swapFunction;
TypeFunctionFixture()
: Fixture(true, false)
: Fixture(false)
{
swapFunction = TypeFunction{
/* name */ "Swap",

View File

@ -11,6 +11,7 @@ LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauUserDefinedTypeFunctionsSyntax2)
LUAU_FASTFLAG(LuauUserDefinedTypeFunctions2)
LUAU_FASTFLAG(LuauUserDefinedTypeFunctionNoEvaluation)
LUAU_FASTFLAG(LuauUserTypeFunFixRegister)
TEST_SUITE_BEGIN("UserDefinedTypeFunctionTests");
@ -1101,4 +1102,101 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_strip_indexer")
CHECK(toString(tpm->givenTp) == "{ foo: string }");
}
TEST_CASE_FIXTURE(BuiltinsFixture, "no_type_methods_on_types")
{
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
ScopedFastFlag luauUserTypeFunFixRegister{FFlag::LuauUserTypeFunFixRegister, true};
CheckResult result = check(R"(
type function test(x)
return if types.is(x, "number") then types.string else types.boolean
end
local function ok(tbl: test<number>): never return tbl end
)");
LUAU_REQUIRE_ERROR_COUNT(4, result);
CHECK(toString(result.errors[0]) == R"('test' type function errored at runtime: [string "test"]:3: attempt to call a nil value)");
}
TEST_CASE_FIXTURE(BuiltinsFixture, "no_types_functions_on_type")
{
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
ScopedFastFlag luauUserTypeFunFixRegister{FFlag::LuauUserTypeFunFixRegister, true};
CheckResult result = check(R"(
type function test(x)
return x.singleton("a")
end
local function ok(tbl: test<number>): never return tbl end
)");
LUAU_REQUIRE_ERROR_COUNT(4, result);
CHECK(toString(result.errors[0]) == R"('test' type function errored at runtime: [string "test"]:3: attempt to call a nil value)");
}
TEST_CASE_FIXTURE(BuiltinsFixture, "no_metatable_writes")
{
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
ScopedFastFlag luauUserTypeFunFixRegister{FFlag::LuauUserTypeFunFixRegister, true};
CheckResult result = check(R"(
type function test(x)
local a = x.__index
a.is = function() return false end
return types.singleton(x.is("number"))
end
local function ok(tbl: test<number>): never return tbl end
)");
LUAU_REQUIRE_ERROR_COUNT(4, result);
CHECK(toString(result.errors[0]) == R"('test' type function errored at runtime: [string "test"]:4: attempt to index nil with 'is')");
}
TEST_CASE_FIXTURE(BuiltinsFixture, "no_eq_field")
{
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
ScopedFastFlag luauUserTypeFunFixRegister{FFlag::LuauUserTypeFunFixRegister, true};
CheckResult result = check(R"(
type function test(x)
return types.singleton(x.__eq(x, types.number))
end
local function ok(tbl: test<number>): never return tbl end
)");
LUAU_REQUIRE_ERROR_COUNT(4, result);
CHECK(toString(result.errors[0]) == R"('test' type function errored at runtime: [string "test"]:3: attempt to call a nil value)");
}
TEST_CASE_FIXTURE(BuiltinsFixture, "tag_field")
{
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
ScopedFastFlag luauUserTypeFunFixRegister{FFlag::LuauUserTypeFunFixRegister, true};
CheckResult result = check(R"(
type function test(x)
return types.singleton(x.tag)
end
local function ok1(tbl: test<number>): never return tbl end
local function ok2(tbl: test<string>): never return tbl end
local function ok3(tbl: test<{}>): never return tbl end
)");
LUAU_REQUIRE_ERROR_COUNT(3, result);
CHECK(toString(result.errors[0]) == R"(Type pack '"number"' could not be converted into 'never'; at [0], "number" is not a subtype of never)");
CHECK(toString(result.errors[1]) == R"(Type pack '"string"' could not be converted into 'never'; at [0], "string" is not a subtype of never)");
CHECK(toString(result.errors[2]) == R"(Type pack '"table"' could not be converted into 'never'; at [0], "table" is not a subtype of never)");
}
TEST_SUITE_END();

View File

@ -4866,4 +4866,29 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "subtyping_with_a_metatable_table_path")
);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "metatable_union_type")
{
ScopedFastFlag _{FFlag::LuauSolverV2, true};
// This will have one (legitimate) error but previously would crash.
auto result = check(R"(
local function set(key, value)
local Message = {}
function Message.new(message)
local self = message or {}
setmetatable(self, Message)
return self
end
local self = Message.new(nil)
self[key] = value
end
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ(
"Cannot add indexer to table '{ @metatable t1, (nil & ~(false?)) | { } } where t1 = { new: <a>(a) -> { @metatable t1, (a & ~(false?)) | { } } }'",
toString(result.errors[0])
);
}
TEST_SUITE_END();

View File

@ -27,9 +27,13 @@
#include <sys/sysctl.h>
#endif
#include <limits> // TODO: remove with LuauTypeSolverRelease
#include <optional>
#include <stdio.h>
LUAU_DYNAMIC_FASTINT(LuauTypeSolverRelease)
// Indicates if verbose output is enabled; can be overridden via --verbose
// Currently, this enables output from 'print', but other verbose output could be enabled eventually.
bool verbose = false;
@ -411,6 +415,12 @@ int main(int argc, char** argv)
printf("Using RNG seed %u\n", *randomSeed);
}
// New Luau type solver uses a temporary scheme where fixes are made under a single version flag
// When flags are enabled, new solver is enabled with all new features and fixes
// When it's disabled, this value should have no effect (all uses under a new solver)
// Flag setup argument can still be used to override this to a specific value if desired
DFInt::LuauTypeSolverRelease.value = std::numeric_limits<int>::max();
if (std::vector<doctest::String> flags; doctest::parseCommaSepArgs(argc, argv, "--fflags=", flags))
setFastFlags(flags);

View File

@ -135,7 +135,7 @@ def add_argument_parsers(parser):
interestness_parser.add_argument('--auto', dest='mode', action='store_const', const=InterestnessMode.AUTO,
default=InterestnessMode.AUTO, help='Automatically figure out which one of --pass or --fail should be used')
interestness_parser.add_argument('--fail', dest='mode', action='store_const', const=InterestnessMode.FAIL,
help='You want this if omitting --fflags=true causes tests to fail')
help='You want this if passing --fflags=true causes tests to fail')
interestness_parser.add_argument('--pass', dest='mode', action='store_const', const=InterestnessMode.PASS,
help='You want this if passing --fflags=true causes tests to pass')
interestness_parser.add_argument('--timeout', dest='timeout', type=int, default=0, metavar='SECONDS',