Sync to upstream/release/627 (#1266)

### What's new?

* Removed new `table.move` optimization because of correctness problems.

### New Type Solver

* Improved error messages for type families to describe what's wrong in
more detail, and ideally without using the term `type family` at all.
* Change `boolean` and `string` singletons in type checking to report
errors to the user when they've gotten an impossible type (indicating a
type error from their context).
* Split debugging flags for type family reduction
(`DebugLuauLogTypeFamilies`) from general solver logging
(`DebugLuauLogSolver`).
* Improve type simplification to support patterns like `(number |
string) | (string | number)` becoming `number | string`.

### Native Code Generation

* Use templated `luaV_doarith` to speedup vector operation fallbacks.
* Various small changes to better support arm64 on Windows.

### Internal Contributors
Co-authored-by: Aaron Weiss <aaronweiss@roblox.com>
Co-authored-by: Andy Friesen <afriesen@roblox.com>
Co-authored-by: James McNellis <jmcnellis@roblox.com>
Co-authored-by: Vighnesh Vijay <vvijay@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>

---------

Co-authored-by: Alexander McCord <amccord@roblox.com>
Co-authored-by: Andy Friesen <afriesen@roblox.com>
Co-authored-by: Vighnesh <vvijay@roblox.com>
Co-authored-by: Aviral Goel <agoel@roblox.com>
Co-authored-by: David Cope <dcope@roblox.com>
Co-authored-by: Lily Brown <lbrown@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
This commit is contained in:
aaron 2024-05-26 13:09:09 -04:00 committed by GitHub
parent 0dbe1a5022
commit c8fe77c268
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
33 changed files with 771 additions and 332 deletions

View File

@ -102,4 +102,12 @@ bool subsumesStrict(Scope* left, Scope* right);
// outermost-possible scope.
bool subsumes(Scope* left, Scope* right);
inline Scope* max(Scope* left, Scope* right)
{
if (subsumes(left, right))
return right;
else
return left;
}
} // namespace Luau

View File

@ -24,7 +24,6 @@
*/
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
LUAU_FASTFLAGVARIABLE(LuauMakeStringMethodsChecked, false);
namespace Luau
{
@ -773,153 +772,87 @@ TypeId makeStringMetatable(NotNull<BuiltinTypes> builtinTypes)
const TypePackId numberVariadicList = arena->addTypePack(TypePackVar{VariadicTypePack{numberType}});
if (FFlag::LuauMakeStringMethodsChecked)
{
FunctionType formatFTV{arena->addTypePack(TypePack{{stringType}, variadicTailPack}), oneStringPack};
formatFTV.magicFunction = &magicFunctionFormat;
formatFTV.isCheckedFunction = true;
const TypeId formatFn = arena->addType(formatFTV);
attachDcrMagicFunction(formatFn, dcrMagicFunctionFormat);
FunctionType formatFTV{arena->addTypePack(TypePack{{stringType}, variadicTailPack}), oneStringPack};
formatFTV.magicFunction = &magicFunctionFormat;
formatFTV.isCheckedFunction = true;
const TypeId formatFn = arena->addType(formatFTV);
attachDcrMagicFunction(formatFn, dcrMagicFunctionFormat);
const TypeId stringToStringType = makeFunction(*arena, std::nullopt, {}, {}, {stringType}, {}, {stringType}, /* checked */ true);
const TypeId stringToStringType = makeFunction(*arena, std::nullopt, {}, {}, {stringType}, {}, {stringType}, /* checked */ true);
const TypeId replArgType = arena->addType(
UnionType{{stringType, arena->addType(TableType({}, TableIndexer(stringType, stringType), TypeLevel{}, TableState::Generic)),
makeFunction(*arena, std::nullopt, {}, {}, {stringType}, {}, {stringType}, /* checked */ false)}});
const TypeId gsubFunc =
makeFunction(*arena, stringType, {}, {}, {stringType, replArgType, optionalNumber}, {}, {stringType, numberType}, /* checked */ false);
const TypeId gmatchFunc = makeFunction(
*arena, stringType, {}, {}, {stringType}, {}, {arena->addType(FunctionType{emptyPack, stringVariadicList})}, /* checked */ true);
attachMagicFunction(gmatchFunc, magicFunctionGmatch);
attachDcrMagicFunction(gmatchFunc, dcrMagicFunctionGmatch);
const TypeId replArgType =
arena->addType(UnionType{{stringType, arena->addType(TableType({}, TableIndexer(stringType, stringType), TypeLevel{}, TableState::Generic)),
makeFunction(*arena, std::nullopt, {}, {}, {stringType}, {}, {stringType}, /* checked */ false)}});
const TypeId gsubFunc =
makeFunction(*arena, stringType, {}, {}, {stringType, replArgType, optionalNumber}, {}, {stringType, numberType}, /* checked */ false);
const TypeId gmatchFunc =
makeFunction(*arena, stringType, {}, {}, {stringType}, {}, {arena->addType(FunctionType{emptyPack, stringVariadicList})}, /* checked */ true);
attachMagicFunction(gmatchFunc, magicFunctionGmatch);
attachDcrMagicFunction(gmatchFunc, dcrMagicFunctionGmatch);
FunctionType matchFuncTy{
arena->addTypePack({stringType, stringType, optionalNumber}), arena->addTypePack(TypePackVar{VariadicTypePack{stringType}})};
matchFuncTy.isCheckedFunction = true;
const TypeId matchFunc = arena->addType(matchFuncTy);
attachMagicFunction(matchFunc, magicFunctionMatch);
attachDcrMagicFunction(matchFunc, dcrMagicFunctionMatch);
FunctionType matchFuncTy{
arena->addTypePack({stringType, stringType, optionalNumber}), arena->addTypePack(TypePackVar{VariadicTypePack{stringType}})};
matchFuncTy.isCheckedFunction = true;
const TypeId matchFunc = arena->addType(matchFuncTy);
attachMagicFunction(matchFunc, magicFunctionMatch);
attachDcrMagicFunction(matchFunc, dcrMagicFunctionMatch);
FunctionType findFuncTy{arena->addTypePack({stringType, stringType, optionalNumber, optionalBoolean}),
arena->addTypePack(TypePack{{optionalNumber, optionalNumber}, stringVariadicList})};
findFuncTy.isCheckedFunction = true;
const TypeId findFunc = arena->addType(findFuncTy);
attachMagicFunction(findFunc, magicFunctionFind);
attachDcrMagicFunction(findFunc, dcrMagicFunctionFind);
FunctionType findFuncTy{arena->addTypePack({stringType, stringType, optionalNumber, optionalBoolean}),
arena->addTypePack(TypePack{{optionalNumber, optionalNumber}, stringVariadicList})};
findFuncTy.isCheckedFunction = true;
const TypeId findFunc = arena->addType(findFuncTy);
attachMagicFunction(findFunc, magicFunctionFind);
attachDcrMagicFunction(findFunc, dcrMagicFunctionFind);
// string.byte : string -> number? -> number? -> ...number
FunctionType stringDotByte{arena->addTypePack({stringType, optionalNumber, optionalNumber}), numberVariadicList};
stringDotByte.isCheckedFunction = true;
// string.byte : string -> number? -> number? -> ...number
FunctionType stringDotByte{arena->addTypePack({stringType, optionalNumber, optionalNumber}), numberVariadicList};
stringDotByte.isCheckedFunction = true;
// string.char : .... number -> string
FunctionType stringDotChar{numberVariadicList, arena->addTypePack({stringType})};
stringDotChar.isCheckedFunction = true;
// string.char : .... number -> string
FunctionType stringDotChar{numberVariadicList, arena->addTypePack({stringType})};
stringDotChar.isCheckedFunction = true;
// string.unpack : string -> string -> number? -> ...any
FunctionType stringDotUnpack{
arena->addTypePack(TypePack{{stringType, stringType, optionalNumber}}),
variadicTailPack,
};
stringDotUnpack.isCheckedFunction = true;
// string.unpack : string -> string -> number? -> ...any
FunctionType stringDotUnpack{
arena->addTypePack(TypePack{{stringType, stringType, optionalNumber}}),
variadicTailPack,
};
stringDotUnpack.isCheckedFunction = true;
TableType::Props stringLib = {
{"byte", {arena->addType(stringDotByte)}},
{"char", {arena->addType(stringDotChar)}},
{"find", {findFunc}},
{"format", {formatFn}}, // FIXME
{"gmatch", {gmatchFunc}},
{"gsub", {gsubFunc}},
{"len", {makeFunction(*arena, stringType, {}, {}, {}, {}, {numberType}, /* checked */ true)}},
{"lower", {stringToStringType}},
{"match", {matchFunc}},
{"rep", {makeFunction(*arena, stringType, {}, {}, {numberType}, {}, {stringType}, /* checked */ true)}},
{"reverse", {stringToStringType}},
{"sub", {makeFunction(*arena, stringType, {}, {}, {numberType, optionalNumber}, {}, {stringType}, /* checked */ true)}},
{"upper", {stringToStringType}},
{"split", {makeFunction(*arena, stringType, {}, {}, {optionalString}, {},
{arena->addType(TableType{{}, TableIndexer{numberType, stringType}, TypeLevel{}, TableState::Sealed})},
/* checked */ true)}},
{"pack", {arena->addType(FunctionType{
arena->addTypePack(TypePack{{stringType}, variadicTailPack}),
oneStringPack,
})}},
{"packsize", {makeFunction(*arena, stringType, {}, {}, {}, {}, {numberType}, /* checked */ true)}},
{"unpack", {arena->addType(stringDotUnpack)}},
};
assignPropDocumentationSymbols(stringLib, "@luau/global/string");
TableType::Props stringLib = {
{"byte", {arena->addType(stringDotByte)}},
{"char", {arena->addType(stringDotChar)}},
{"find", {findFunc}},
{"format", {formatFn}}, // FIXME
{"gmatch", {gmatchFunc}},
{"gsub", {gsubFunc}},
{"len", {makeFunction(*arena, stringType, {}, {}, {}, {}, {numberType}, /* checked */ true)}},
{"lower", {stringToStringType}},
{"match", {matchFunc}},
{"rep", {makeFunction(*arena, stringType, {}, {}, {numberType}, {}, {stringType}, /* checked */ true)}},
{"reverse", {stringToStringType}},
{"sub", {makeFunction(*arena, stringType, {}, {}, {numberType, optionalNumber}, {}, {stringType}, /* checked */ true)}},
{"upper", {stringToStringType}},
{"split", {makeFunction(*arena, stringType, {}, {}, {optionalString}, {},
{arena->addType(TableType{{}, TableIndexer{numberType, stringType}, TypeLevel{}, TableState::Sealed})},
/* checked */ true)}},
{"pack", {arena->addType(FunctionType{
arena->addTypePack(TypePack{{stringType}, variadicTailPack}),
oneStringPack,
})}},
{"packsize", {makeFunction(*arena, stringType, {}, {}, {}, {}, {numberType}, /* checked */ true)}},
{"unpack", {arena->addType(stringDotUnpack)}},
};
TypeId tableType = arena->addType(TableType{std::move(stringLib), std::nullopt, TypeLevel{}, TableState::Sealed});
assignPropDocumentationSymbols(stringLib, "@luau/global/string");
if (TableType* ttv = getMutable<TableType>(tableType))
ttv->name = "typeof(string)";
TypeId tableType = arena->addType(TableType{std::move(stringLib), std::nullopt, TypeLevel{}, TableState::Sealed});
return arena->addType(TableType{{{{"__index", {tableType}}}}, std::nullopt, TypeLevel{}, TableState::Sealed});
}
else
{
FunctionType formatFTV{arena->addTypePack(TypePack{{stringType}, variadicTailPack}), oneStringPack};
formatFTV.magicFunction = &magicFunctionFormat;
const TypeId formatFn = arena->addType(formatFTV);
attachDcrMagicFunction(formatFn, dcrMagicFunctionFormat);
if (TableType* ttv = getMutable<TableType>(tableType))
ttv->name = "typeof(string)";
const TypeId stringToStringType = makeFunction(*arena, std::nullopt, {}, {}, {stringType}, {}, {stringType});
const TypeId replArgType = arena->addType(
UnionType{{stringType, arena->addType(TableType({}, TableIndexer(stringType, stringType), TypeLevel{}, TableState::Generic)),
makeFunction(*arena, std::nullopt, {}, {}, {stringType}, {}, {stringType})}});
const TypeId gsubFunc = makeFunction(*arena, stringType, {}, {}, {stringType, replArgType, optionalNumber}, {}, {stringType, numberType});
const TypeId gmatchFunc =
makeFunction(*arena, stringType, {}, {}, {stringType}, {}, {arena->addType(FunctionType{emptyPack, stringVariadicList})});
attachMagicFunction(gmatchFunc, magicFunctionGmatch);
attachDcrMagicFunction(gmatchFunc, dcrMagicFunctionGmatch);
const TypeId matchFunc = arena->addType(FunctionType{
arena->addTypePack({stringType, stringType, optionalNumber}), arena->addTypePack(TypePackVar{VariadicTypePack{stringType}})});
attachMagicFunction(matchFunc, magicFunctionMatch);
attachDcrMagicFunction(matchFunc, dcrMagicFunctionMatch);
const TypeId findFunc = arena->addType(FunctionType{arena->addTypePack({stringType, stringType, optionalNumber, optionalBoolean}),
arena->addTypePack(TypePack{{optionalNumber, optionalNumber}, stringVariadicList})});
attachMagicFunction(findFunc, magicFunctionFind);
attachDcrMagicFunction(findFunc, dcrMagicFunctionFind);
TableType::Props stringLib = {
{"byte", {arena->addType(FunctionType{arena->addTypePack({stringType, optionalNumber, optionalNumber}), numberVariadicList})}},
{"char", {arena->addType(FunctionType{numberVariadicList, arena->addTypePack({stringType})})}},
{"find", {findFunc}},
{"format", {formatFn}}, // FIXME
{"gmatch", {gmatchFunc}},
{"gsub", {gsubFunc}},
{"len", {makeFunction(*arena, stringType, {}, {}, {}, {}, {numberType})}},
{"lower", {stringToStringType}},
{"match", {matchFunc}},
{"rep", {makeFunction(*arena, stringType, {}, {}, {numberType}, {}, {stringType})}},
{"reverse", {stringToStringType}},
{"sub", {makeFunction(*arena, stringType, {}, {}, {numberType, optionalNumber}, {}, {stringType})}},
{"upper", {stringToStringType}},
{"split", {makeFunction(*arena, stringType, {}, {}, {optionalString}, {},
{arena->addType(TableType{{}, TableIndexer{numberType, stringType}, TypeLevel{}, TableState::Sealed})})}},
{"pack", {arena->addType(FunctionType{
arena->addTypePack(TypePack{{stringType}, variadicTailPack}),
oneStringPack,
})}},
{"packsize", {makeFunction(*arena, stringType, {}, {}, {}, {}, {numberType})}},
{"unpack", {arena->addType(FunctionType{
arena->addTypePack(TypePack{{stringType, stringType, optionalNumber}}),
variadicTailPack,
})}},
};
assignPropDocumentationSymbols(stringLib, "@luau/global/string");
TypeId tableType = arena->addType(TableType{std::move(stringLib), std::nullopt, TypeLevel{}, TableState::Sealed});
if (TableType* ttv = getMutable<TableType>(tableType))
ttv->name = "typeof(string)";
return arena->addType(TableType{{{{"__index", {tableType}}}}, std::nullopt, TypeLevel{}, TableState::Sealed});
}
return arena->addType(TableType{{{{"__index", {tableType}}}}, std::nullopt, TypeLevel{}, TableState::Sealed});
}
static std::optional<WithPredicate<TypePackId>> magicFunctionSelect(

View File

@ -7,11 +7,13 @@
#include "Luau/NotNull.h"
#include "Luau/StringUtils.h"
#include "Luau/ToString.h"
#include "Luau/TypeFamily.h"
#include <optional>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <unordered_set>
LUAU_FASTINTVARIABLE(LuauIndentTypeMismatchMaxTypeLength, 10)
@ -61,6 +63,23 @@ static std::string wrongNumberOfArgsString(
namespace Luau
{
// this list of binary operator type families is used for better stringification of type families errors
static const std::unordered_map<std::string, const char*> kBinaryOps{
{"add", "+"}, {"sub", "-"}, {"mul", "*"}, {"div", "/"}, {"idiv", "//"}, {"pow", "^"}, {"mod", "%"}, {"concat", ".."}, {"and", "and"},
{"or", "or"}, {"lt", "< or >="}, {"le", "<= or >"}, {"eq", "== or ~="}
};
// this list of unary operator type families is used for better stringification of type families errors
static const std::unordered_map<std::string, const char*> kUnaryOps{
{"unm", "-"}, {"len", "#"}, {"not", "not"}
};
// this list of type families will receive a special error indicating that the user should file a bug on the GitHub repository
// putting a type family in this list indicates that it is expected to _always_ reduce
static const std::unordered_set<std::string> kUnreachableTypeFamilies{
"refine", "singleton", "union", "intersect"
};
struct ErrorConverter
{
FileResolver* fileResolver = nullptr;
@ -565,6 +584,96 @@ struct ErrorConverter
std::string operator()(const UninhabitedTypeFamily& e) const
{
auto tfit = get<TypeFamilyInstanceType>(e.ty);
LUAU_ASSERT(tfit); // Luau analysis has actually done something wrong if this type is not a type family.
if (!tfit)
return "Unexpected type " + Luau::toString(e.ty) + " flagged as an uninhabited type family.";
// unary operators
if (auto unaryString = kUnaryOps.find(tfit->family->name); unaryString != kUnaryOps.end())
{
std::string result = "Operator '" + std::string(unaryString->second) + "' could not be applied to ";
if (tfit->typeArguments.size() == 1 && tfit->packArguments.empty())
{
result += "operand of type " + Luau::toString(tfit->typeArguments[0]);
if (tfit->family->name != "not")
result += "; there is no corresponding overload for __" + tfit->family->name;
}
else
{
// if it's not the expected case, we ought to add a specialization later, but this is a sane default.
result += "operands of types ";
bool isFirst = true;
for (auto arg : tfit->typeArguments)
{
if (!isFirst)
result += ", ";
result += Luau::toString(arg);
isFirst = false;
}
for (auto packArg : tfit->packArguments)
result += ", " + Luau::toString(packArg);
}
return result;
}
// binary operators
if (auto binaryString = kBinaryOps.find(tfit->family->name); binaryString != kBinaryOps.end())
{
std::string result = "Operator '" + std::string(binaryString->second) + "' could not be applied to operands of types ";
if (tfit->typeArguments.size() == 2 && tfit->packArguments.empty())
{
// this is the expected case.
result += Luau::toString(tfit->typeArguments[0]) + " and " + Luau::toString(tfit->typeArguments[1]);
}
else
{
// if it's not the expected case, we ought to add a specialization later, but this is a sane default.
bool isFirst = true;
for (auto arg : tfit->typeArguments)
{
if (!isFirst)
result += ", ";
result += Luau::toString(arg);
isFirst = false;
}
for (auto packArg : tfit->packArguments)
result += ", " + Luau::toString(packArg);
}
result += "; there is no corresponding overload for __" + tfit->family->name;
return result;
}
// miscellaneous
if ("keyof" == tfit->family->name || "rawkeyof" == tfit->family->name)
{
if (tfit->typeArguments.size() == 1 && tfit->packArguments.empty())
return "Type '" + toString(tfit->typeArguments[0]) + "' does not have keys, so '" + Luau::toString(e.ty) + "' is invalid";
else
return "Type family instance " + Luau::toString(e.ty) + " is ill-formed, and thus invalid";
}
if (kUnreachableTypeFamilies.count(tfit->family->name))
{
return "Type family instance " + Luau::toString(e.ty) + " is uninhabited\n" +
"This is likely to be a bug, please report it at https://github.com/luau-lang/luau/issues";
}
// Everything should be specialized above to report a more descriptive error that hopefully does not mention "type families" explicitly.
// If we produce this message, it's an indication that we've missed a specialization and it should be fixed!
return "Type family instance " + Luau::toString(e.ty) + " is uninhabited";
}

View File

@ -2534,6 +2534,7 @@ std::optional<TypeId> Normalizer::intersectionOfTables(TypeId here, TypeId there
state = tttv->state;
TypeLevel level = max(httv->level, tttv->level);
Scope* scope = max(httv->scope, tttv->scope);
std::unique_ptr<TableType> result = nullptr;
bool hereSubThere = true;
@ -2644,7 +2645,7 @@ std::optional<TypeId> Normalizer::intersectionOfTables(TypeId here, TypeId there
if (prop.readTy || prop.writeTy)
{
if (!result.get())
result = std::make_unique<TableType>(TableType{state, level});
result = std::make_unique<TableType>(TableType{state, level, scope});
result->props[name] = prop;
}
}
@ -2654,7 +2655,7 @@ std::optional<TypeId> Normalizer::intersectionOfTables(TypeId here, TypeId there
if (httv->props.count(name) == 0)
{
if (!result.get())
result = std::make_unique<TableType>(TableType{state, level});
result = std::make_unique<TableType>(TableType{state, level, scope});
result->props[name] = tprop;
hereSubThere = false;
@ -2667,7 +2668,7 @@ std::optional<TypeId> Normalizer::intersectionOfTables(TypeId here, TypeId there
TypeId index = unionType(httv->indexer->indexType, tttv->indexer->indexType);
TypeId indexResult = intersectionType(httv->indexer->indexResultType, tttv->indexer->indexResultType);
if (!result.get())
result = std::make_unique<TableType>(TableType{state, level});
result = std::make_unique<TableType>(TableType{state, level, scope});
result->indexer = {index, indexResult};
hereSubThere &= (httv->indexer->indexType == index) && (httv->indexer->indexResultType == indexResult);
thereSubHere &= (tttv->indexer->indexType == index) && (tttv->indexer->indexResultType == indexResult);
@ -2675,14 +2676,14 @@ std::optional<TypeId> Normalizer::intersectionOfTables(TypeId here, TypeId there
else if (httv->indexer)
{
if (!result.get())
result = std::make_unique<TableType>(TableType{state, level});
result = std::make_unique<TableType>(TableType{state, level, scope});
result->indexer = httv->indexer;
thereSubHere = false;
}
else if (tttv->indexer)
{
if (!result.get())
result = std::make_unique<TableType>(TableType{state, level});
result = std::make_unique<TableType>(TableType{state, level, scope});
result->indexer = tttv->indexer;
hereSubThere = false;
}
@ -2697,7 +2698,7 @@ std::optional<TypeId> Normalizer::intersectionOfTables(TypeId here, TypeId there
if (result.get())
table = arena->addType(std::move(*result));
else
table = arena->addType(TableType{state, level});
table = arena->addType(TableType{state, level, scope});
}
if (tmtable && hmtable)

View File

@ -1255,6 +1255,10 @@ TypeId TypeSimplifier::union_(TypeId left, TypeId right)
case Relation::Coincident:
case Relation::Superset:
return left;
case Relation::Subset:
newParts.insert(right);
changed = true;
break;
default:
newParts.insert(part);
newParts.insert(right);

View File

@ -1242,13 +1242,14 @@ struct TypeChecker2
void visit(AstExprConstantBool* expr)
{
#if defined(LUAU_ENABLE_ASSERT)
// booleans use specialized inference logic for singleton types, which can lead to real type errors here.
const TypeId bestType = expr->value ? builtinTypes->trueType : builtinTypes->falseType;
const TypeId inferredType = lookupType(expr);
const SubtypingResult r = subtyping->isSubtype(bestType, inferredType);
LUAU_ASSERT(r.isSubtype || isErrorSuppressing(expr->location, inferredType));
#endif
if (!r.isSubtype && !isErrorSuppressing(expr->location, inferredType))
reportError(TypeMismatch{inferredType, bestType}, expr->location);
}
void visit(AstExprConstantNumber* expr)
@ -1264,13 +1265,14 @@ struct TypeChecker2
void visit(AstExprConstantString* expr)
{
#if defined(LUAU_ENABLE_ASSERT)
// strings use specialized inference logic for singleton types, which can lead to real type errors here.
const TypeId bestType = module->internalTypes.addType(SingletonType{StringSingleton{std::string{expr->value.data, expr->value.size}}});
const TypeId inferredType = lookupType(expr);
const SubtypingResult r = subtyping->isSubtype(bestType, inferredType);
LUAU_ASSERT(r.isSubtype || isErrorSuppressing(expr->location, inferredType));
#endif
if (!r.isSubtype && !isErrorSuppressing(expr->location, inferredType))
reportError(TypeMismatch{inferredType, bestType}, expr->location);
}
void visit(AstExprLocal* expr)

View File

@ -37,7 +37,7 @@ LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyApplicationCartesianProductLimit, 5'0
// when this value is set to a negative value, guessing will be totally disabled.
LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyUseGuesserDepth, -1);
LUAU_FASTFLAG(DebugLuauLogSolver);
LUAU_FASTFLAGVARIABLE(DebugLuauLogTypeFamilies, false);
namespace Luau
{
@ -184,7 +184,7 @@ struct FamilyReducer
if (subject->owningArena != ctx.arena.get())
ctx.ice->ice("Attempting to modify a type family instance from another arena", location);
if (FFlag::DebugLuauLogSolver)
if (FFlag::DebugLuauLogTypeFamilies)
printf("%s -> %s\n", toString(subject, {true}).c_str(), toString(replacement, {true}).c_str());
asMutable(subject)->ty.template emplace<Unifiable::Bound<T>>(replacement);
@ -206,7 +206,7 @@ struct FamilyReducer
if (reduction.uninhabited || force)
{
if (FFlag::DebugLuauLogSolver)
if (FFlag::DebugLuauLogTypeFamilies)
printf("%s is uninhabited\n", toString(subject, {true}).c_str());
if constexpr (std::is_same_v<T, TypeId>)
@ -216,7 +216,7 @@ struct FamilyReducer
}
else if (!reduction.uninhabited && !force)
{
if (FFlag::DebugLuauLogSolver)
if (FFlag::DebugLuauLogTypeFamilies)
printf("%s is irreducible; blocked on %zu types, %zu packs\n", toString(subject, {true}).c_str(), reduction.blockedTypes.size(),
reduction.blockedPacks.size());
@ -243,7 +243,7 @@ struct FamilyReducer
if (skip == SkipTestResult::Irreducible)
{
if (FFlag::DebugLuauLogSolver)
if (FFlag::DebugLuauLogTypeFamilies)
printf("%s is irreducible due to a dependency on %s\n", toString(subject, {true}).c_str(), toString(p, {true}).c_str());
irreducible.insert(subject);
@ -251,7 +251,7 @@ struct FamilyReducer
}
else if (skip == SkipTestResult::Defer)
{
if (FFlag::DebugLuauLogSolver)
if (FFlag::DebugLuauLogTypeFamilies)
printf("Deferring %s until %s is solved\n", toString(subject, {true}).c_str(), toString(p, {true}).c_str());
if constexpr (std::is_same_v<T, TypeId>)
@ -269,7 +269,7 @@ struct FamilyReducer
if (skip == SkipTestResult::Irreducible)
{
if (FFlag::DebugLuauLogSolver)
if (FFlag::DebugLuauLogTypeFamilies)
printf("%s is irreducible due to a dependency on %s\n", toString(subject, {true}).c_str(), toString(p, {true}).c_str());
irreducible.insert(subject);
@ -277,7 +277,7 @@ struct FamilyReducer
}
else if (skip == SkipTestResult::Defer)
{
if (FFlag::DebugLuauLogSolver)
if (FFlag::DebugLuauLogTypeFamilies)
printf("Deferring %s until %s is solved\n", toString(subject, {true}).c_str(), toString(p, {true}).c_str());
if constexpr (std::is_same_v<T, TypeId>)
@ -297,7 +297,7 @@ struct FamilyReducer
{
if (shouldGuess.contains(subject))
{
if (FFlag::DebugLuauLogSolver)
if (FFlag::DebugLuauLogTypeFamilies)
printf("Flagged %s for reduction with guesser.\n", toString(subject, {true}).c_str());
TypeFamilyReductionGuesser guesser{ctx.arena, ctx.builtins, ctx.normalizer};
@ -305,14 +305,14 @@ struct FamilyReducer
if (guessed)
{
if (FFlag::DebugLuauLogSolver)
if (FFlag::DebugLuauLogTypeFamilies)
printf("Selected %s as the guessed result type.\n", toString(*guessed, {true}).c_str());
replace(subject, *guessed);
return true;
}
if (FFlag::DebugLuauLogSolver)
if (FFlag::DebugLuauLogTypeFamilies)
printf("Failed to produce a guess for the result of %s.\n", toString(subject, {true}).c_str());
}
@ -328,7 +328,7 @@ struct FamilyReducer
if (irreducible.contains(subject))
return;
if (FFlag::DebugLuauLogSolver)
if (FFlag::DebugLuauLogTypeFamilies)
printf("Trying to reduce %s\n", toString(subject, {true}).c_str());
if (const TypeFamilyInstanceType* tfit = get<TypeFamilyInstanceType>(subject))
@ -337,7 +337,7 @@ struct FamilyReducer
if (!testParameters(subject, tfit) && testCyclic != SkipTestResult::CyclicTypeFamily)
{
if (FFlag::DebugLuauLogSolver)
if (FFlag::DebugLuauLogTypeFamilies)
printf("Irreducible due to irreducible/pending and a non-cyclic family\n");
return;
@ -361,7 +361,7 @@ struct FamilyReducer
if (irreducible.contains(subject))
return;
if (FFlag::DebugLuauLogSolver)
if (FFlag::DebugLuauLogTypeFamilies)
printf("Trying to reduce %s\n", toString(subject, {true}).c_str());
if (const TypeFamilyInstanceTypePack* tfit = get<TypeFamilyInstanceTypePack>(subject))

View File

@ -144,8 +144,17 @@ using UniqueSharedCodeGenContext = std::unique_ptr<SharedCodeGenContext, SharedC
// SharedCodeGenContext must be destroyed before this function is called.
void destroySharedCodeGenContext(const SharedCodeGenContext* codeGenContext) noexcept;
void create(lua_State* L, AllocationCallback* allocationCallback, void* allocationCallbackContext);
// Initializes native code-gen on the provided Luau VM, using a VM-specific
// code-gen context and either the default allocator parameters or custom
// allocator parameters.
void create(lua_State* L);
void create(lua_State* L, AllocationCallback* allocationCallback, void* allocationCallbackContext);
void create(lua_State* L, size_t blockSize, size_t maxTotalSize, AllocationCallback* allocationCallback, void* allocationCallbackContext);
// Initializes native code-gen on the provided Luau VM, using the provided
// SharedCodeGenContext. Note that after this function is called, the
// SharedCodeGenContext must not be destroyed until after the Luau VM L is
// destroyed via lua_close.
void create(lua_State* L, SharedCodeGenContext* codeGenContext);
// Check if native execution is enabled

View File

@ -16,7 +16,7 @@ namespace CodeGen
{
// This value is used in 'finishFunction' to mark the function that spans to the end of the whole code block
static uint32_t kFullBlockFuncton = ~0u;
static uint32_t kFullBlockFunction = ~0u;
class UnwindBuilder
{
@ -52,11 +52,10 @@ public:
virtual void prologueX64(uint32_t prologueSize, uint32_t stackSize, bool setupFrame, std::initializer_list<X64::RegisterX64> gpr,
const std::vector<X64::RegisterX64>& simd) = 0;
virtual size_t getSize() const = 0;
virtual size_t getFunctionCount() const = 0;
virtual size_t getUnwindInfoSize(size_t blockSize) const = 0;
// This will place the unwinding data at the target address and might update values of some fields
virtual void finalize(char* target, size_t offset, void* funcAddress, size_t funcSize) const = 0;
virtual size_t finalize(char* target, size_t offset, void* funcAddress, size_t blockSize) const = 0;
};
} // namespace CodeGen

View File

@ -33,10 +33,9 @@ public:
void prologueX64(uint32_t prologueSize, uint32_t stackSize, bool setupFrame, std::initializer_list<X64::RegisterX64> gpr,
const std::vector<X64::RegisterX64>& simd) override;
size_t getSize() const override;
size_t getFunctionCount() const override;
size_t getUnwindInfoSize(size_t blockSize = 0) const override;
void finalize(char* target, size_t offset, void* funcAddress, size_t funcSize) const override;
size_t finalize(char* target, size_t offset, void* funcAddress, size_t blockSize) const override;
private:
size_t beginOffset = 0;

View File

@ -53,10 +53,9 @@ public:
void prologueX64(uint32_t prologueSize, uint32_t stackSize, bool setupFrame, std::initializer_list<X64::RegisterX64> gpr,
const std::vector<X64::RegisterX64>& simd) override;
size_t getSize() const override;
size_t getFunctionCount() const override;
size_t getUnwindInfoSize(size_t blockSize = 0) const override;
void finalize(char* target, size_t offset, void* funcAddress, size_t funcSize) const override;
size_t finalize(char* target, size_t offset, void* funcAddress, size_t blockSize) const override;
private:
size_t beginOffset = 0;

View File

@ -102,17 +102,17 @@ void* createBlockUnwindInfo(void* context, uint8_t* block, size_t blockSize, siz
UnwindBuilder* unwind = (UnwindBuilder*)context;
// All unwinding related data is placed together at the start of the block
size_t unwindSize = unwind->getSize();
size_t unwindSize = unwind->getUnwindInfoSize(blockSize);
unwindSize = (unwindSize + (kCodeAlignment - 1)) & ~(kCodeAlignment - 1); // Match code allocator alignment
CODEGEN_ASSERT(blockSize >= unwindSize);
char* unwindData = (char*)block;
unwind->finalize(unwindData, unwindSize, block, blockSize);
[[maybe_unused]] size_t functionCount = unwind->finalize(unwindData, unwindSize, block, blockSize);
#if defined(_WIN32) && defined(CODEGEN_TARGET_X64)
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM)
if (!RtlAddFunctionTable((RUNTIME_FUNCTION*)block, uint32_t(unwind->getFunctionCount()), uintptr_t(block)))
if (!RtlAddFunctionTable((RUNTIME_FUNCTION*)block, uint32_t(functionCount), uintptr_t(block)))
{
CODEGEN_ASSERT(!"Failed to allocate function table");
return nullptr;

View File

@ -95,10 +95,6 @@ std::string toString(const CodeGenCompilationResult& result)
return "";
}
void* gPerfLogContext = nullptr;
PerfLogFn gPerfLogFn = nullptr;
void onDisable(lua_State* L, Proto* proto)
{
// do nothing if proto already uses bytecode
@ -196,59 +192,5 @@ bool isSupported()
#endif
}
void create(lua_State* L, AllocationCallback* allocationCallback, void* allocationCallbackContext)
{
create_NEW(L, allocationCallback, allocationCallbackContext);
}
void create(lua_State* L)
{
create_NEW(L);
}
void create(lua_State* L, SharedCodeGenContext* codeGenContext)
{
create_NEW(L, codeGenContext);
}
[[nodiscard]] bool isNativeExecutionEnabled(lua_State* L)
{
return isNativeExecutionEnabled_NEW(L);
}
void setNativeExecutionEnabled(lua_State* L, bool enabled)
{
setNativeExecutionEnabled_NEW(L, enabled);
}
CompilationResult compile(lua_State* L, int idx, unsigned int flags, CompilationStats* stats)
{
Luau::CodeGen::CompilationOptions options{flags};
return compile_NEW(L, idx, options, stats);
}
CompilationResult compile(const ModuleId& moduleId, lua_State* L, int idx, unsigned int flags, CompilationStats* stats)
{
Luau::CodeGen::CompilationOptions options{flags};
return compile_NEW(moduleId, L, idx, options, stats);
}
CompilationResult compile(lua_State* L, int idx, const CompilationOptions& options, CompilationStats* stats)
{
return compile_NEW(L, idx, options, stats);
}
CompilationResult compile(const ModuleId& moduleId, lua_State* L, int idx, const CompilationOptions& options, CompilationStats* stats)
{
return compile_NEW(moduleId, L, idx, options, stats);
}
void setPerfLog(void* context, PerfLogFn logFn)
{
gPerfLogContext = context;
gPerfLogFn = logFn;
}
} // namespace CodeGen
} // namespace Luau

View File

@ -253,7 +253,7 @@ static EntryLocations buildEntryFunction(AssemblyBuilderA64& build, UnwindBuilde
// Our entry function is special, it spans the whole remaining code area
unwind.startFunction();
unwind.prologueA64(prologueSize, kStackSize, {x29, x30, x19, x20, x21, x22, x23, x24, x25});
unwind.finishFunction(build.getLabelOffset(locations.start), kFullBlockFuncton);
unwind.finishFunction(build.getLabelOffset(locations.start), kFullBlockFunction);
return locations;
}

View File

@ -25,11 +25,17 @@ namespace CodeGen
static const Instruction kCodeEntryInsn = LOP_NATIVECALL;
// From CodeGen.cpp
extern void* gPerfLogContext;
extern PerfLogFn gPerfLogFn;
static void* gPerfLogContext = nullptr;
static PerfLogFn gPerfLogFn = nullptr;
unsigned int getCpuFeaturesA64();
void setPerfLog(void* context, PerfLogFn logFn)
{
gPerfLogContext = context;
gPerfLogFn = logFn;
}
static void logPerfFunction(Proto* p, uintptr_t addr, unsigned size)
{
CODEGEN_ASSERT(p->source);
@ -365,17 +371,17 @@ static void initializeExecutionCallbacks(lua_State* L, BaseCodeGenContext* codeG
ecb->getmemorysize = getMemorySize;
}
void create_NEW(lua_State* L)
void create(lua_State* L)
{
return create_NEW(L, size_t(FInt::LuauCodeGenBlockSize), size_t(FInt::LuauCodeGenMaxTotalSize), nullptr, nullptr);
return create(L, size_t(FInt::LuauCodeGenBlockSize), size_t(FInt::LuauCodeGenMaxTotalSize), nullptr, nullptr);
}
void create_NEW(lua_State* L, AllocationCallback* allocationCallback, void* allocationCallbackContext)
void create(lua_State* L, AllocationCallback* allocationCallback, void* allocationCallbackContext)
{
return create_NEW(L, size_t(FInt::LuauCodeGenBlockSize), size_t(FInt::LuauCodeGenMaxTotalSize), allocationCallback, allocationCallbackContext);
return create(L, size_t(FInt::LuauCodeGenBlockSize), size_t(FInt::LuauCodeGenMaxTotalSize), allocationCallback, allocationCallbackContext);
}
void create_NEW(lua_State* L, size_t blockSize, size_t maxTotalSize, AllocationCallback* allocationCallback, void* allocationCallbackContext)
void create(lua_State* L, size_t blockSize, size_t maxTotalSize, AllocationCallback* allocationCallback, void* allocationCallbackContext)
{
std::unique_ptr<StandaloneCodeGenContext> codeGenContext =
std::make_unique<StandaloneCodeGenContext>(blockSize, maxTotalSize, allocationCallback, allocationCallbackContext);
@ -386,7 +392,7 @@ void create_NEW(lua_State* L, size_t blockSize, size_t maxTotalSize, AllocationC
initializeExecutionCallbacks(L, codeGenContext.release());
}
void create_NEW(lua_State* L, SharedCodeGenContext* codeGenContext)
void create(lua_State* L, SharedCodeGenContext* codeGenContext)
{
initializeExecutionCallbacks(L, codeGenContext);
}
@ -575,22 +581,32 @@ template<typename AssemblyBuilder>
return compilationResult;
}
CompilationResult compile_NEW(const ModuleId& moduleId, lua_State* L, int idx, const CompilationOptions& options, CompilationStats* stats)
CompilationResult compile(const ModuleId& moduleId, lua_State* L, int idx, const CompilationOptions& options, CompilationStats* stats)
{
return compileInternal(moduleId, L, idx, options, stats);
}
CompilationResult compile_NEW(lua_State* L, int idx, const CompilationOptions& options, CompilationStats* stats)
CompilationResult compile(lua_State* L, int idx, const CompilationOptions& options, CompilationStats* stats)
{
return compileInternal({}, L, idx, options, stats);
}
[[nodiscard]] bool isNativeExecutionEnabled_NEW(lua_State* L)
CompilationResult compile(lua_State* L, int idx, unsigned int flags, CompilationStats* stats)
{
return compileInternal({}, L, idx, CompilationOptions{flags}, stats);
}
CompilationResult compile(const ModuleId& moduleId, lua_State* L, int idx, unsigned int flags, CompilationStats* stats)
{
return compileInternal(moduleId, L, idx, CompilationOptions{flags}, stats);
}
[[nodiscard]] bool isNativeExecutionEnabled(lua_State* L)
{
return getCodeGenContext(L) != nullptr && L->global->ecb.enter == onEnter;
}
void setNativeExecutionEnabled_NEW(lua_State* L, bool enabled)
void setNativeExecutionEnabled(lua_State* L, bool enabled)
{
if (getCodeGenContext(L) != nullptr)
L->global->ecb.enter = enabled ? onEnter : onEnterDisabled;

View File

@ -88,33 +88,5 @@ private:
SharedCodeAllocator sharedAllocator;
};
// The following will become the public interface, and can be moved into
// CodeGen.h after the shared allocator work is complete. When the old
// implementation is removed, the _NEW suffix can be dropped from these
// functions.
// Initializes native code-gen on the provided Luau VM, using a VM-specific
// code-gen context and either the default allocator parameters or custom
// allocator parameters.
void create_NEW(lua_State* L);
void create_NEW(lua_State* L, AllocationCallback* allocationCallback, void* allocationCallbackContext);
void create_NEW(lua_State* L, size_t blockSize, size_t maxTotalSize, AllocationCallback* allocationCallback, void* allocationCallbackContext);
// Initializes native code-gen on the provided Luau VM, using the provided
// SharedCodeGenContext. Note that after this function is called, the
// SharedCodeGenContext must not be destroyed until after the Luau VM L is
// destroyed via lua_close.
void create_NEW(lua_State* L, SharedCodeGenContext* codeGenContext);
CompilationResult compile_NEW(lua_State* L, int idx, const CompilationOptions& options, CompilationStats* stats);
CompilationResult compile_NEW(const ModuleId& moduleId, lua_State* L, int idx, const CompilationOptions& options, CompilationStats* stats);
// Returns true if native execution is currently enabled for this VM
[[nodiscard]] bool isNativeExecutionEnabled_NEW(lua_State* L);
// Enables or disables native excution for this VM
void setNativeExecutionEnabled_NEW(lua_State* L, bool enabled);
} // namespace CodeGen
} // namespace Luau

View File

@ -181,7 +181,7 @@ static EntryLocations buildEntryFunction(AssemblyBuilderX64& build, UnwindBuilde
build.ret();
// Our entry function is special, it spans the whole remaining code area
unwind.finishFunction(build.getLabelOffset(locations.start), kFullBlockFuncton);
unwind.finishFunction(build.getLabelOffset(locations.start), kFullBlockFunction);
return locations;
}

View File

@ -14,6 +14,8 @@
#include <utility>
LUAU_FASTFLAGVARIABLE(LuauCodegenSplitDoarith, false)
namespace Luau
{
namespace CodeGen
@ -155,8 +157,45 @@ void callArithHelper(IrRegAllocX64& regs, AssemblyBuilderX64& build, int ra, Ope
callWrap.addArgument(SizeX64::qword, luauRegAddress(ra));
callWrap.addArgument(SizeX64::qword, b);
callWrap.addArgument(SizeX64::qword, c);
callWrap.addArgument(SizeX64::dword, tm);
callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaV_doarith)]);
if (FFlag::LuauCodegenSplitDoarith)
{
switch (tm)
{
case TM_ADD:
callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaV_doarithadd)]);
break;
case TM_SUB:
callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaV_doarithsub)]);
break;
case TM_MUL:
callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaV_doarithmul)]);
break;
case TM_DIV:
callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaV_doarithdiv)]);
break;
case TM_IDIV:
callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaV_doarithidiv)]);
break;
case TM_MOD:
callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaV_doarithmod)]);
break;
case TM_POW:
callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaV_doarithpow)]);
break;
case TM_UNM:
callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaV_doarithunm)]);
break;
default:
CODEGEN_ASSERT(!"Invalid doarith helper operation tag");
break;
}
}
else
{
callWrap.addArgument(SizeX64::dword, tm);
callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaV_doarith)]);
}
emitUpdateBase(build);
}

View File

@ -12,6 +12,7 @@
#include "lgc.h"
LUAU_FASTFLAG(LuauCodegenRemoveDeadStores5)
LUAU_FASTFLAG(LuauCodegenSplitDoarith)
namespace Luau
{
@ -1242,9 +1243,47 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next)
else
build.add(x3, rBase, uint16_t(vmRegOp(inst.c) * sizeof(TValue)));
build.mov(w4, TMS(intOp(inst.d)));
build.ldr(x5, mem(rNativeContext, offsetof(NativeContext, luaV_doarith)));
build.blr(x5);
if (FFlag::LuauCodegenSplitDoarith)
{
switch (TMS(intOp(inst.d)))
{
case TM_ADD:
build.ldr(x4, mem(rNativeContext, offsetof(NativeContext, luaV_doarithadd)));
break;
case TM_SUB:
build.ldr(x4, mem(rNativeContext, offsetof(NativeContext, luaV_doarithsub)));
break;
case TM_MUL:
build.ldr(x4, mem(rNativeContext, offsetof(NativeContext, luaV_doarithmul)));
break;
case TM_DIV:
build.ldr(x4, mem(rNativeContext, offsetof(NativeContext, luaV_doarithdiv)));
break;
case TM_IDIV:
build.ldr(x4, mem(rNativeContext, offsetof(NativeContext, luaV_doarithidiv)));
break;
case TM_MOD:
build.ldr(x4, mem(rNativeContext, offsetof(NativeContext, luaV_doarithmod)));
break;
case TM_POW:
build.ldr(x4, mem(rNativeContext, offsetof(NativeContext, luaV_doarithpow)));
break;
case TM_UNM:
build.ldr(x4, mem(rNativeContext, offsetof(NativeContext, luaV_doarithunm)));
break;
default:
CODEGEN_ASSERT(!"Invalid doarith helper operation tag");
break;
}
build.blr(x4);
}
else
{
build.mov(w4, TMS(intOp(inst.d)));
build.ldr(x5, mem(rNativeContext, offsetof(NativeContext, luaV_doarith)));
build.blr(x5);
}
emitUpdateBase(build);
break;

View File

@ -43,6 +43,16 @@ void initFunctions(NativeState& data)
data.context.luaV_lessequal = luaV_lessequal;
data.context.luaV_equalval = luaV_equalval;
data.context.luaV_doarith = luaV_doarith;
data.context.luaV_doarithadd = luaV_doarithimpl<TM_ADD>;
data.context.luaV_doarithsub = luaV_doarithimpl<TM_SUB>;
data.context.luaV_doarithmul = luaV_doarithimpl<TM_MUL>;
data.context.luaV_doarithdiv = luaV_doarithimpl<TM_DIV>;
data.context.luaV_doarithidiv = luaV_doarithimpl<TM_IDIV>;
data.context.luaV_doarithmod = luaV_doarithimpl<TM_MOD>;
data.context.luaV_doarithpow = luaV_doarithimpl<TM_POW>;
data.context.luaV_doarithunm = luaV_doarithimpl<TM_UNM>;
data.context.luaV_dolen = luaV_dolen;
data.context.luaV_gettable = luaV_gettable;
data.context.luaV_settable = luaV_settable;
@ -121,6 +131,16 @@ void initFunctions(NativeContext& context)
context.luaV_lessequal = luaV_lessequal;
context.luaV_equalval = luaV_equalval;
context.luaV_doarith = luaV_doarith;
context.luaV_doarithadd = luaV_doarithimpl<TM_ADD>;
context.luaV_doarithsub = luaV_doarithimpl<TM_SUB>;
context.luaV_doarithmul = luaV_doarithimpl<TM_MUL>;
context.luaV_doarithdiv = luaV_doarithimpl<TM_DIV>;
context.luaV_doarithidiv = luaV_doarithimpl<TM_IDIV>;
context.luaV_doarithmod = luaV_doarithimpl<TM_MOD>;
context.luaV_doarithpow = luaV_doarithimpl<TM_POW>;
context.luaV_doarithunm = luaV_doarithimpl<TM_UNM>;
context.luaV_dolen = luaV_dolen;
context.luaV_gettable = luaV_gettable;
context.luaV_settable = luaV_settable;

View File

@ -34,6 +34,14 @@ struct NativeContext
int (*luaV_lessequal)(lua_State* L, const TValue* l, const TValue* r) = nullptr;
int (*luaV_equalval)(lua_State* L, const TValue* t1, const TValue* t2) = nullptr;
void (*luaV_doarith)(lua_State* L, StkId ra, const TValue* rb, const TValue* rc, TMS op) = nullptr;
void (*luaV_doarithadd)(lua_State* L, StkId ra, const TValue* rb, const TValue* rc) = nullptr;
void (*luaV_doarithsub)(lua_State* L, StkId ra, const TValue* rb, const TValue* rc) = nullptr;
void (*luaV_doarithmul)(lua_State* L, StkId ra, const TValue* rb, const TValue* rc) = nullptr;
void (*luaV_doarithdiv)(lua_State* L, StkId ra, const TValue* rb, const TValue* rc) = nullptr;
void (*luaV_doarithidiv)(lua_State* L, StkId ra, const TValue* rb, const TValue* rc) = nullptr;
void (*luaV_doarithmod)(lua_State* L, StkId ra, const TValue* rb, const TValue* rc) = nullptr;
void (*luaV_doarithpow)(lua_State* L, StkId ra, const TValue* rb, const TValue* rc) = nullptr;
void (*luaV_doarithunm)(lua_State* L, StkId ra, const TValue* rb, const TValue* rc) = nullptr;
void (*luaV_dolen)(lua_State* L, StkId ra, const TValue* rb) = nullptr;
void (*luaV_gettable)(lua_State* L, const TValue* t, TValue* key, StkId val) = nullptr;
void (*luaV_settable)(lua_State* L, const TValue* t, TValue* key, StkId val) = nullptr;

View File

@ -202,7 +202,7 @@ void UnwindBuilderDwarf2::finishInfo()
// Terminate section
pos = writeu32(pos, 0);
CODEGEN_ASSERT(getSize() <= kRawDataLimit);
CODEGEN_ASSERT(getUnwindInfoSize() <= kRawDataLimit);
}
void UnwindBuilderDwarf2::prologueA64(uint32_t prologueSize, uint32_t stackSize, std::initializer_list<A64::RegisterA64> regs)
@ -271,19 +271,14 @@ void UnwindBuilderDwarf2::prologueX64(uint32_t prologueSize, uint32_t stackSize,
CODEGEN_ASSERT(prologueOffset == prologueSize);
}
size_t UnwindBuilderDwarf2::getSize() const
size_t UnwindBuilderDwarf2::getUnwindInfoSize(size_t blockSize) const
{
return size_t(pos - rawData);
}
size_t UnwindBuilderDwarf2::getFunctionCount() const
size_t UnwindBuilderDwarf2::finalize(char* target, size_t offset, void* funcAddress, size_t blockSize) const
{
return unwindFunctions.size();
}
void UnwindBuilderDwarf2::finalize(char* target, size_t offset, void* funcAddress, size_t funcSize) const
{
memcpy(target, rawData, getSize());
memcpy(target, rawData, getUnwindInfoSize());
for (const UnwindFunctionDwarf2& func : unwindFunctions)
{
@ -291,11 +286,13 @@ void UnwindBuilderDwarf2::finalize(char* target, size_t offset, void* funcAddres
writeu64(fdeEntry + kFdeInitialLocationOffset, uintptr_t(funcAddress) + offset + func.beginOffset);
if (func.endOffset == kFullBlockFuncton)
writeu64(fdeEntry + kFdeAddressRangeOffset, funcSize - offset);
if (func.endOffset == kFullBlockFunction)
writeu64(fdeEntry + kFdeAddressRangeOffset, blockSize - offset);
else
writeu64(fdeEntry + kFdeAddressRangeOffset, func.endOffset - func.beginOffset);
}
return unwindFunctions.size();
}
} // namespace CodeGen

View File

@ -194,17 +194,12 @@ void UnwindBuilderWin::prologueX64(uint32_t prologueSize, uint32_t stackSize, bo
this->prologSize = prologueSize;
}
size_t UnwindBuilderWin::getSize() const
size_t UnwindBuilderWin::getUnwindInfoSize(size_t blockSize) const
{
return sizeof(UnwindFunctionWin) * unwindFunctions.size() + size_t(rawDataPos - rawData);
}
size_t UnwindBuilderWin::getFunctionCount() const
{
return unwindFunctions.size();
}
void UnwindBuilderWin::finalize(char* target, size_t offset, void* funcAddress, size_t funcSize) const
size_t UnwindBuilderWin::finalize(char* target, size_t offset, void* funcAddress, size_t blockSize) const
{
// Copy adjusted function information
for (UnwindFunctionWin func : unwindFunctions)
@ -213,8 +208,8 @@ void UnwindBuilderWin::finalize(char* target, size_t offset, void* funcAddress,
func.beginOffset += uint32_t(offset);
// Whole block is a part of a 'single function'
if (func.endOffset == kFullBlockFuncton)
func.endOffset = uint32_t(funcSize);
if (func.endOffset == kFullBlockFunction)
func.endOffset = uint32_t(blockSize);
else
func.endOffset += uint32_t(offset);
@ -226,6 +221,8 @@ void UnwindBuilderWin::finalize(char* target, size_t offset, void* funcAddress,
// Copy unwind codes
memcpy(target, rawData, size_t(rawDataPos - rawData));
return unwindFunctions.size();
}
} // namespace CodeGen

View File

@ -16,6 +16,10 @@ LUAI_FUNC int luaV_lessthan(lua_State* L, const TValue* l, const TValue* r);
LUAI_FUNC int luaV_lessequal(lua_State* L, const TValue* l, const TValue* r);
LUAI_FUNC int luaV_equalval(lua_State* L, const TValue* t1, const TValue* t2);
LUAI_FUNC void luaV_doarith(lua_State* L, StkId ra, const TValue* rb, const TValue* rc, TMS op);
template<TMS op>
void luaV_doarithimpl(lua_State* L, StkId ra, const TValue* rb, const TValue* rc);
LUAI_FUNC void luaV_dolen(lua_State* L, StkId ra, const TValue* rb);
LUAI_FUNC const TValue* luaV_tonumber(const TValue* obj, TValue* n);
LUAI_FUNC const float* luaV_tovector(const TValue* obj);

View File

@ -16,6 +16,8 @@
#include <string.h>
LUAU_FASTFLAGVARIABLE(LuauVmSplitDoarith, false)
// Disable c99-designator to avoid the warning in CGOTO dispatch table
#ifdef __clang__
#if __has_warning("-Wc99-designator")
@ -1487,7 +1489,14 @@ reentry:
else
{
// slow-path, may invoke C/Lua via metamethods
VM_PROTECT(luaV_doarith(L, ra, rb, rc, TM_ADD));
if (FFlag::LuauVmSplitDoarith)
{
VM_PROTECT(luaV_doarithimpl<TM_ADD>(L, ra, rb, rc));
}
else
{
VM_PROTECT(luaV_doarith(L, ra, rb, rc, TM_ADD));
}
VM_NEXT();
}
}
@ -1533,7 +1542,14 @@ reentry:
else
{
// slow-path, may invoke C/Lua via metamethods
VM_PROTECT(luaV_doarith(L, ra, rb, rc, TM_SUB));
if (FFlag::LuauVmSplitDoarith)
{
VM_PROTECT(luaV_doarithimpl<TM_SUB>(L, ra, rb, rc));
}
else
{
VM_PROTECT(luaV_doarith(L, ra, rb, rc, TM_SUB));
}
VM_NEXT();
}
}
@ -1594,7 +1610,14 @@ reentry:
else
{
// slow-path, may invoke C/Lua via metamethods
VM_PROTECT(luaV_doarith(L, ra, rb, rc, TM_MUL));
if (FFlag::LuauVmSplitDoarith)
{
VM_PROTECT(luaV_doarithimpl<TM_MUL>(L, ra, rb, rc));
}
else
{
VM_PROTECT(luaV_doarith(L, ra, rb, rc, TM_MUL));
}
VM_NEXT();
}
}
@ -1655,7 +1678,14 @@ reentry:
else
{
// slow-path, may invoke C/Lua via metamethods
VM_PROTECT(luaV_doarith(L, ra, rb, rc, TM_DIV));
if (FFlag::LuauVmSplitDoarith)
{
VM_PROTECT(luaV_doarithimpl<TM_DIV>(L, ra, rb, rc));
}
else
{
VM_PROTECT(luaV_doarith(L, ra, rb, rc, TM_DIV));
}
VM_NEXT();
}
}
@ -1703,7 +1733,14 @@ reentry:
else
{
// slow-path, may invoke C/Lua via metamethods
VM_PROTECT(luaV_doarith(L, ra, rb, rc, TM_IDIV));
if (FFlag::LuauVmSplitDoarith)
{
VM_PROTECT(luaV_doarithimpl<TM_IDIV>(L, ra, rb, rc));
}
else
{
VM_PROTECT(luaV_doarith(L, ra, rb, rc, TM_IDIV));
}
VM_NEXT();
}
}
@ -1727,7 +1764,14 @@ reentry:
else
{
// slow-path, may invoke C/Lua via metamethods
VM_PROTECT(luaV_doarith(L, ra, rb, rc, TM_MOD));
if (FFlag::LuauVmSplitDoarith)
{
VM_PROTECT(luaV_doarithimpl<TM_MOD>(L, ra, rb, rc));
}
else
{
VM_PROTECT(luaV_doarith(L, ra, rb, rc, TM_MOD));
}
VM_NEXT();
}
}
@ -1748,7 +1792,14 @@ reentry:
else
{
// slow-path, may invoke C/Lua via metamethods
VM_PROTECT(luaV_doarith(L, ra, rb, rc, TM_POW));
if (FFlag::LuauVmSplitDoarith)
{
VM_PROTECT(luaV_doarithimpl<TM_POW>(L, ra, rb, rc));
}
else
{
VM_PROTECT(luaV_doarith(L, ra, rb, rc, TM_POW));
}
VM_NEXT();
}
}
@ -1769,7 +1820,14 @@ reentry:
else
{
// slow-path, may invoke C/Lua via metamethods
VM_PROTECT(luaV_doarith(L, ra, rb, kv, TM_ADD));
if (FFlag::LuauVmSplitDoarith)
{
VM_PROTECT(luaV_doarithimpl<TM_ADD>(L, ra, rb, kv));
}
else
{
VM_PROTECT(luaV_doarith(L, ra, rb, kv, TM_ADD));
}
VM_NEXT();
}
}
@ -1790,7 +1848,14 @@ reentry:
else
{
// slow-path, may invoke C/Lua via metamethods
VM_PROTECT(luaV_doarith(L, ra, rb, kv, TM_SUB));
if (FFlag::LuauVmSplitDoarith)
{
VM_PROTECT(luaV_doarithimpl<TM_SUB>(L, ra, rb, kv));
}
else
{
VM_PROTECT(luaV_doarith(L, ra, rb, kv, TM_SUB));
}
VM_NEXT();
}
}
@ -1835,7 +1900,14 @@ reentry:
else
{
// slow-path, may invoke C/Lua via metamethods
VM_PROTECT(luaV_doarith(L, ra, rb, kv, TM_MUL));
if (FFlag::LuauVmSplitDoarith)
{
VM_PROTECT(luaV_doarithimpl<TM_MUL>(L, ra, rb, kv));
}
else
{
VM_PROTECT(luaV_doarith(L, ra, rb, kv, TM_MUL));
}
VM_NEXT();
}
}
@ -1881,7 +1953,14 @@ reentry:
else
{
// slow-path, may invoke C/Lua via metamethods
VM_PROTECT(luaV_doarith(L, ra, rb, kv, TM_DIV));
if (FFlag::LuauVmSplitDoarith)
{
VM_PROTECT(luaV_doarithimpl<TM_DIV>(L, ra, rb, kv));
}
else
{
VM_PROTECT(luaV_doarith(L, ra, rb, kv, TM_DIV));
}
VM_NEXT();
}
}
@ -1928,7 +2007,14 @@ reentry:
else
{
// slow-path, may invoke C/Lua via metamethods
VM_PROTECT(luaV_doarith(L, ra, rb, kv, TM_IDIV));
if (FFlag::LuauVmSplitDoarith)
{
VM_PROTECT(luaV_doarithimpl<TM_IDIV>(L, ra, rb, kv));
}
else
{
VM_PROTECT(luaV_doarith(L, ra, rb, kv, TM_IDIV));
}
VM_NEXT();
}
}
@ -1952,7 +2038,14 @@ reentry:
else
{
// slow-path, may invoke C/Lua via metamethods
VM_PROTECT(luaV_doarith(L, ra, rb, kv, TM_MOD));
if (FFlag::LuauVmSplitDoarith)
{
VM_PROTECT(luaV_doarithimpl<TM_MOD>(L, ra, rb, kv));
}
else
{
VM_PROTECT(luaV_doarith(L, ra, rb, kv, TM_MOD));
}
VM_NEXT();
}
}
@ -1979,7 +2072,14 @@ reentry:
else
{
// slow-path, may invoke C/Lua via metamethods
VM_PROTECT(luaV_doarith(L, ra, rb, kv, TM_POW));
if (FFlag::LuauVmSplitDoarith)
{
VM_PROTECT(luaV_doarithimpl<TM_POW>(L, ra, rb, kv));
}
else
{
VM_PROTECT(luaV_doarith(L, ra, rb, kv, TM_POW));
}
VM_NEXT();
}
}
@ -2092,7 +2192,14 @@ reentry:
else
{
// slow-path, may invoke C/Lua via metamethods
VM_PROTECT(luaV_doarith(L, ra, rb, rb, TM_UNM));
if (FFlag::LuauVmSplitDoarith)
{
VM_PROTECT(luaV_doarithimpl<TM_UNM>(L, ra, rb, rb));
}
else
{
VM_PROTECT(luaV_doarith(L, ra, rb, rb, TM_UNM));
}
VM_NEXT();
}
}
@ -2711,7 +2818,14 @@ reentry:
else
{
// slow-path, may invoke C/Lua via metamethods
VM_PROTECT(luaV_doarith(L, ra, kv, rc, TM_SUB));
if (FFlag::LuauVmSplitDoarith)
{
VM_PROTECT(luaV_doarithimpl<TM_SUB>(L, ra, kv, rc));
}
else
{
VM_PROTECT(luaV_doarith(L, ra, kv, rc, TM_SUB));
}
VM_NEXT();
}
}
@ -2739,7 +2853,14 @@ reentry:
else
{
// slow-path, may invoke C/Lua via metamethods
VM_PROTECT(luaV_doarith(L, ra, kv, rc, TM_DIV));
if (FFlag::LuauVmSplitDoarith)
{
VM_PROTECT(luaV_doarithimpl<TM_DIV>(L, ra, kv, rc));
}
else
{
VM_PROTECT(luaV_doarith(L, ra, kv, rc, TM_DIV));
}
VM_NEXT();
}
}

View File

@ -373,6 +373,152 @@ void luaV_concat(lua_State* L, int total, int last)
} while (total > 1); // repeat until only 1 result left
}
template<TMS op>
void luaV_doarithimpl(lua_State* L, StkId ra, const TValue* rb, const TValue* rc)
{
TValue tempb, tempc;
const TValue *b, *c;
// vector operations that we support:
// v+v v-v -v (add/sub/neg)
// v*v s*v v*s (mul)
// v/v s/v v/s (div)
// v//v s//v v//s (floor div)
const float* vb = ttisvector(rb) ? vvalue(rb) : nullptr;
const float* vc = ttisvector(rc) ? vvalue(rc) : nullptr;
if (vb && vc)
{
switch (op)
{
case TM_ADD:
setvvalue(ra, vb[0] + vc[0], vb[1] + vc[1], vb[2] + vc[2], vb[3] + vc[3]);
return;
case TM_SUB:
setvvalue(ra, vb[0] - vc[0], vb[1] - vc[1], vb[2] - vc[2], vb[3] - vc[3]);
return;
case TM_MUL:
setvvalue(ra, vb[0] * vc[0], vb[1] * vc[1], vb[2] * vc[2], vb[3] * vc[3]);
return;
case TM_DIV:
setvvalue(ra, vb[0] / vc[0], vb[1] / vc[1], vb[2] / vc[2], vb[3] / vc[3]);
return;
case TM_IDIV:
setvvalue(ra, float(luai_numidiv(vb[0], vc[0])), float(luai_numidiv(vb[1], vc[1])), float(luai_numidiv(vb[2], vc[2])),
float(luai_numidiv(vb[3], vc[3])));
return;
case TM_UNM:
setvvalue(ra, -vb[0], -vb[1], -vb[2], -vb[3]);
return;
default:
break;
}
}
else if (vb)
{
c = ttisnumber(rc) ? rc : luaV_tonumber(rc, &tempc);
if (c)
{
float nc = cast_to(float, nvalue(c));
switch (op)
{
case TM_MUL:
setvvalue(ra, vb[0] * nc, vb[1] * nc, vb[2] * nc, vb[3] * nc);
return;
case TM_DIV:
setvvalue(ra, vb[0] / nc, vb[1] / nc, vb[2] / nc, vb[3] / nc);
return;
case TM_IDIV:
setvvalue(ra, float(luai_numidiv(vb[0], nc)), float(luai_numidiv(vb[1], nc)), float(luai_numidiv(vb[2], nc)),
float(luai_numidiv(vb[3], nc)));
return;
default:
break;
}
}
}
else if (vc)
{
b = ttisnumber(rb) ? rb : luaV_tonumber(rb, &tempb);
if (b)
{
float nb = cast_to(float, nvalue(b));
switch (op)
{
case TM_MUL:
setvvalue(ra, nb * vc[0], nb * vc[1], nb * vc[2], nb * vc[3]);
return;
case TM_DIV:
setvvalue(ra, nb / vc[0], nb / vc[1], nb / vc[2], nb / vc[3]);
return;
case TM_IDIV:
setvvalue(ra, float(luai_numidiv(nb, vc[0])), float(luai_numidiv(nb, vc[1])), float(luai_numidiv(nb, vc[2])),
float(luai_numidiv(nb, vc[3])));
return;
default:
break;
}
}
}
if ((b = luaV_tonumber(rb, &tempb)) != NULL && (c = luaV_tonumber(rc, &tempc)) != NULL)
{
double nb = nvalue(b), nc = nvalue(c);
switch (op)
{
case TM_ADD:
setnvalue(ra, luai_numadd(nb, nc));
break;
case TM_SUB:
setnvalue(ra, luai_numsub(nb, nc));
break;
case TM_MUL:
setnvalue(ra, luai_nummul(nb, nc));
break;
case TM_DIV:
setnvalue(ra, luai_numdiv(nb, nc));
break;
case TM_IDIV:
setnvalue(ra, luai_numidiv(nb, nc));
break;
case TM_MOD:
setnvalue(ra, luai_nummod(nb, nc));
break;
case TM_POW:
setnvalue(ra, luai_numpow(nb, nc));
break;
case TM_UNM:
setnvalue(ra, luai_numunm(nb));
break;
default:
LUAU_ASSERT(0);
break;
}
}
else
{
if (!call_binTM(L, rb, rc, ra, op))
{
luaG_aritherror(L, rb, rc, op);
}
}
}
// instantiate private template implementation for external callers
template void luaV_doarithimpl<TM_ADD>(lua_State* L, StkId ra, const TValue* rb, const TValue* rc);
template void luaV_doarithimpl<TM_SUB>(lua_State* L, StkId ra, const TValue* rb, const TValue* rc);
template void luaV_doarithimpl<TM_MUL>(lua_State* L, StkId ra, const TValue* rb, const TValue* rc);
template void luaV_doarithimpl<TM_DIV>(lua_State* L, StkId ra, const TValue* rb, const TValue* rc);
template void luaV_doarithimpl<TM_IDIV>(lua_State* L, StkId ra, const TValue* rb, const TValue* rc);
template void luaV_doarithimpl<TM_MOD>(lua_State* L, StkId ra, const TValue* rb, const TValue* rc);
template void luaV_doarithimpl<TM_POW>(lua_State* L, StkId ra, const TValue* rb, const TValue* rc);
template void luaV_doarithimpl<TM_UNM>(lua_State* L, StkId ra, const TValue* rb, const TValue* rc);
void luaV_doarith(lua_State* L, StkId ra, const TValue* rb, const TValue* rc, TMS op)
{
TValue tempb, tempc;

View File

@ -191,7 +191,7 @@ TEST_CASE("WindowsUnwindCodesX64")
unwind.finishInfo();
std::vector<char> data;
data.resize(unwind.getSize());
data.resize(unwind.getUnwindInfoSize());
unwind.finalize(data.data(), 0, nullptr, 0);
std::vector<uint8_t> expected{0x44, 0x33, 0x22, 0x11, 0x22, 0x33, 0x44, 0x55, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x17, 0x0a, 0x05, 0x17, 0x82, 0x13,
@ -215,7 +215,7 @@ TEST_CASE("Dwarf2UnwindCodesX64")
unwind.finishInfo();
std::vector<char> data;
data.resize(unwind.getSize());
data.resize(unwind.getUnwindInfoSize());
unwind.finalize(data.data(), 0, nullptr, 0);
std::vector<uint8_t> expected{0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x78, 0x10, 0x0c, 0x07, 0x08, 0x90, 0x01, 0x00,
@ -241,7 +241,7 @@ TEST_CASE("Dwarf2UnwindCodesA64")
unwind.finishInfo();
std::vector<char> data;
data.resize(unwind.getSize());
data.resize(unwind.getUnwindInfoSize());
unwind.finalize(data.data(), 0, nullptr, 0);
std::vector<uint8_t> expected{0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x78, 0x1e, 0x0c, 0x1f, 0x00, 0x2c, 0x00, 0x00,

View File

@ -6,6 +6,8 @@
using namespace Luau;
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
TEST_SUITE_BEGIN("ErrorTests");
TEST_CASE("TypeError_code_should_return_nonzero_code")
@ -34,4 +36,44 @@ local x: Account = 5
CHECK_EQ("Type 'number' could not be converted into 'Account'", toString(result.errors[0]));
}
TEST_CASE_FIXTURE(BuiltinsFixture, "binary_op_type_family_errors")
{
frontend.options.retainFullTypeGraphs = false;
CheckResult result = check(R"(
--!strict
local x = 1 + "foo"
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ("Operator '+' could not be applied to operands of types number and string; there is no corresponding overload for __add", toString(result.errors[0]));
else
CHECK_EQ("Type 'string' could not be converted into 'number'", toString(result.errors[0]));
}
TEST_CASE_FIXTURE(BuiltinsFixture, "unary_op_type_family_errors")
{
frontend.options.retainFullTypeGraphs = false;
CheckResult result = check(R"(
--!strict
local x = -"foo"
)");
if (FFlag::DebugLuauDeferredConstraintResolution)
{
LUAU_REQUIRE_ERROR_COUNT(2, result);
CHECK_EQ("Operator '-' could not be applied to operand of type string; there is no corresponding overload for __unm", toString(result.errors[0]));
CHECK_EQ("Type 'string' could not be converted into 'number'", toString(result.errors[1]));
}
else
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ("Type 'string' could not be converted into 'number'", toString(result.errors[0]));
}
}
TEST_SUITE_END();

View File

@ -214,6 +214,14 @@ TEST_CASE_FIXTURE(SimplifyFixture, "any_and_indeterminate_types")
CHECK(errorTy == anyLhsPending->options[1]);
}
TEST_CASE_FIXTURE(SimplifyFixture, "union_where_lhs_elements_are_a_subset_of_the_rhs")
{
TypeId lhs = union_(numberTy, stringTy);
TypeId rhs = union_(stringTy, numberTy);
CHECK("number | string" == toString(union_(lhs, rhs)));
}
TEST_CASE_FIXTURE(SimplifyFixture, "unknown_and_indeterminate_types")
{
CHECK(freeTy == intersect(unknownTy, freeTy));

View File

@ -391,8 +391,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_type_family_errors_if_it_has_nontable_
// FIXME(CLI-95289): we should actually only report the type family being uninhabited error at its first use, I think?
LUAU_REQUIRE_ERROR_COUNT(2, result);
CHECK(toString(result.errors[0]) == "Type family instance keyof<MyObject | boolean> is uninhabited");
CHECK(toString(result.errors[1]) == "Type family instance keyof<MyObject | boolean> is uninhabited");
CHECK(toString(result.errors[0]) == "Type 'MyObject | boolean' does not have keys, so 'keyof<MyObject | boolean>' is invalid");
CHECK(toString(result.errors[1]) == "Type 'MyObject | boolean' does not have keys, so 'keyof<MyObject | boolean>' is invalid");
}
TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_type_family_string_indexer")
@ -517,8 +517,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "rawkeyof_type_family_errors_if_it_has_nontab
// FIXME(CLI-95289): we should actually only report the type family being uninhabited error at its first use, I think?
LUAU_REQUIRE_ERROR_COUNT(2, result);
CHECK(toString(result.errors[0]) == "Type family instance rawkeyof<MyObject | boolean> is uninhabited");
CHECK(toString(result.errors[1]) == "Type family instance rawkeyof<MyObject | boolean> is uninhabited");
CHECK(toString(result.errors[0]) == "Type 'MyObject | boolean' does not have keys, so 'rawkeyof<MyObject | boolean>' is invalid");
CHECK(toString(result.errors[1]) == "Type 'MyObject | boolean' does not have keys, so 'rawkeyof<MyObject | boolean>' is invalid");
}
TEST_CASE_FIXTURE(BuiltinsFixture, "rawkeyof_type_family_common_subset_if_union_of_differing_tables")
@ -590,8 +590,8 @@ TEST_CASE_FIXTURE(ClassFixture, "keyof_type_family_errors_if_it_has_nonclass_par
// FIXME(CLI-95289): we should actually only report the type family being uninhabited error at its first use, I think?
LUAU_REQUIRE_ERROR_COUNT(2, result);
CHECK(toString(result.errors[0]) == "Type family instance keyof<BaseClass | boolean> is uninhabited");
CHECK(toString(result.errors[1]) == "Type family instance keyof<BaseClass | boolean> is uninhabited");
CHECK(toString(result.errors[0]) == "Type 'BaseClass | boolean' does not have keys, so 'keyof<BaseClass | boolean>' is invalid");
CHECK(toString(result.errors[1]) == "Type 'BaseClass | boolean' does not have keys, so 'keyof<BaseClass | boolean>' is invalid");
}
TEST_CASE_FIXTURE(ClassFixture, "keyof_type_family_common_subset_if_union_of_differing_classes")

View File

@ -2298,10 +2298,10 @@ end
if (FFlag::DebugLuauDeferredConstraintResolution)
{
LUAU_REQUIRE_ERROR_COUNT(4, result);
CHECK(toString(result.errors[0]) == "Type family instance sub<unknown, number> is uninhabited");
CHECK(toString(result.errors[1]) == "Type family instance sub<unknown, number> is uninhabited");
CHECK(toString(result.errors[2]) == "Type family instance sub<unknown, number> is uninhabited");
CHECK(toString(result.errors[3]) == "Type family instance sub<unknown, number> is uninhabited");
CHECK(toString(result.errors[0]) == "Operator '-' could not be applied to operands of types unknown and number; there is no corresponding overload for __sub");
CHECK(toString(result.errors[1]) == "Operator '-' could not be applied to operands of types unknown and number; there is no corresponding overload for __sub");
CHECK(toString(result.errors[2]) == "Operator '-' could not be applied to operands of types unknown and number; there is no corresponding overload for __sub");
CHECK(toString(result.errors[3]) == "Operator '-' could not be applied to operands of types unknown and number; there is no corresponding overload for __sub");
}
else
{

View File

@ -4479,4 +4479,31 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "index_results_compare_to_nil")
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "fuzzer_normalization_preserves_tbl_scopes")
{
CheckResult result = check(R"(
Module 'l0':
do end
Module 'l1':
local _ = {n0=nil,}
if if nil then _ then
if nil and (_)._ ~= (_)._ then
do end
while _ do
_ = _
do end
end
end
do end
end
local l0
while _ do
_ = nil
(_[_])._ %= `{# _}{bit32.extract(# _,1)}`
end
)");
}
TEST_SUITE_END();

View File

@ -219,7 +219,6 @@ TableTests.setmetatable_has_a_side_effect
TableTests.shared_selfs
TableTests.shared_selfs_from_free_param
TableTests.shared_selfs_through_metatables
TableTests.should_not_unblock_table_type_twice
TableTests.table_call_metamethod_basic
TableTests.table_call_metamethod_must_be_callable
TableTests.table_param_width_subtyping_2
@ -288,7 +287,6 @@ TypeInfer.unify_nearly_identical_recursive_types
TypeInferAnyError.can_subscript_any
TypeInferAnyError.for_in_loop_iterator_is_error
TypeInferAnyError.for_in_loop_iterator_is_error2
TypeInferAnyError.metatable_of_any_can_be_a_table
TypeInferAnyError.replace_every_free_type_when_unifying_a_complex_function_with_any
TypeInferClasses.callable_classes
TypeInferClasses.cannot_unify_class_instance_with_primitive