Skip to content

Commit c8fe77c

Browse files
aatxealexmccordandyfriesenVighnesh-VAviral Goel
authored
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>
1 parent 0dbe1a5 commit c8fe77c

33 files changed

+784
-345
lines changed

Analysis/include/Luau/Scope.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,4 +102,12 @@ bool subsumesStrict(Scope* left, Scope* right);
102102
// outermost-possible scope.
103103
bool subsumes(Scope* left, Scope* right);
104104

105+
inline Scope* max(Scope* left, Scope* right)
106+
{
107+
if (subsumes(left, right))
108+
return right;
109+
else
110+
return left;
111+
}
112+
105113
} // namespace Luau

Analysis/src/BuiltinDefinitions.cpp

Lines changed: 81 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
*/
2525

2626
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
27-
LUAU_FASTFLAGVARIABLE(LuauMakeStringMethodsChecked, false);
2827

2928
namespace Luau
3029
{
@@ -773,153 +772,87 @@ TypeId makeStringMetatable(NotNull<BuiltinTypes> builtinTypes)
773772
const TypePackId numberVariadicList = arena->addTypePack(TypePackVar{VariadicTypePack{numberType}});
774773

775774

776-
if (FFlag::LuauMakeStringMethodsChecked)
777-
{
778-
FunctionType formatFTV{arena->addTypePack(TypePack{{stringType}, variadicTailPack}), oneStringPack};
779-
formatFTV.magicFunction = &magicFunctionFormat;
780-
formatFTV.isCheckedFunction = true;
781-
const TypeId formatFn = arena->addType(formatFTV);
782-
attachDcrMagicFunction(formatFn, dcrMagicFunctionFormat);
783-
784-
785-
const TypeId stringToStringType = makeFunction(*arena, std::nullopt, {}, {}, {stringType}, {}, {stringType}, /* checked */ true);
786-
787-
const TypeId replArgType = arena->addType(
788-
UnionType{{stringType, arena->addType(TableType({}, TableIndexer(stringType, stringType), TypeLevel{}, TableState::Generic)),
789-
makeFunction(*arena, std::nullopt, {}, {}, {stringType}, {}, {stringType}, /* checked */ false)}});
790-
const TypeId gsubFunc =
791-
makeFunction(*arena, stringType, {}, {}, {stringType, replArgType, optionalNumber}, {}, {stringType, numberType}, /* checked */ false);
792-
const TypeId gmatchFunc = makeFunction(
793-
*arena, stringType, {}, {}, {stringType}, {}, {arena->addType(FunctionType{emptyPack, stringVariadicList})}, /* checked */ true);
794-
attachMagicFunction(gmatchFunc, magicFunctionGmatch);
795-
attachDcrMagicFunction(gmatchFunc, dcrMagicFunctionGmatch);
796-
797-
FunctionType matchFuncTy{
798-
arena->addTypePack({stringType, stringType, optionalNumber}), arena->addTypePack(TypePackVar{VariadicTypePack{stringType}})};
799-
matchFuncTy.isCheckedFunction = true;
800-
const TypeId matchFunc = arena->addType(matchFuncTy);
801-
attachMagicFunction(matchFunc, magicFunctionMatch);
802-
attachDcrMagicFunction(matchFunc, dcrMagicFunctionMatch);
803-
804-
FunctionType findFuncTy{arena->addTypePack({stringType, stringType, optionalNumber, optionalBoolean}),
805-
arena->addTypePack(TypePack{{optionalNumber, optionalNumber}, stringVariadicList})};
806-
findFuncTy.isCheckedFunction = true;
807-
const TypeId findFunc = arena->addType(findFuncTy);
808-
attachMagicFunction(findFunc, magicFunctionFind);
809-
attachDcrMagicFunction(findFunc, dcrMagicFunctionFind);
810-
811-
// string.byte : string -> number? -> number? -> ...number
812-
FunctionType stringDotByte{arena->addTypePack({stringType, optionalNumber, optionalNumber}), numberVariadicList};
813-
stringDotByte.isCheckedFunction = true;
814-
815-
// string.char : .... number -> string
816-
FunctionType stringDotChar{numberVariadicList, arena->addTypePack({stringType})};
817-
stringDotChar.isCheckedFunction = true;
818-
819-
// string.unpack : string -> string -> number? -> ...any
820-
FunctionType stringDotUnpack{
821-
arena->addTypePack(TypePack{{stringType, stringType, optionalNumber}}),
822-
variadicTailPack,
823-
};
824-
stringDotUnpack.isCheckedFunction = true;
825-
826-
TableType::Props stringLib = {
827-
{"byte", {arena->addType(stringDotByte)}},
828-
{"char", {arena->addType(stringDotChar)}},
829-
{"find", {findFunc}},
830-
{"format", {formatFn}}, // FIXME
831-
{"gmatch", {gmatchFunc}},
832-
{"gsub", {gsubFunc}},
833-
{"len", {makeFunction(*arena, stringType, {}, {}, {}, {}, {numberType}, /* checked */ true)}},
834-
{"lower", {stringToStringType}},
835-
{"match", {matchFunc}},
836-
{"rep", {makeFunction(*arena, stringType, {}, {}, {numberType}, {}, {stringType}, /* checked */ true)}},
837-
{"reverse", {stringToStringType}},
838-
{"sub", {makeFunction(*arena, stringType, {}, {}, {numberType, optionalNumber}, {}, {stringType}, /* checked */ true)}},
839-
{"upper", {stringToStringType}},
840-
{"split", {makeFunction(*arena, stringType, {}, {}, {optionalString}, {},
841-
{arena->addType(TableType{{}, TableIndexer{numberType, stringType}, TypeLevel{}, TableState::Sealed})},
842-
/* checked */ true)}},
843-
{"pack", {arena->addType(FunctionType{
844-
arena->addTypePack(TypePack{{stringType}, variadicTailPack}),
845-
oneStringPack,
846-
})}},
847-
{"packsize", {makeFunction(*arena, stringType, {}, {}, {}, {}, {numberType}, /* checked */ true)}},
848-
{"unpack", {arena->addType(stringDotUnpack)}},
849-
};
850-
assignPropDocumentationSymbols(stringLib, "@luau/global/string");
851-
852-
TypeId tableType = arena->addType(TableType{std::move(stringLib), std::nullopt, TypeLevel{}, TableState::Sealed});
853-
854-
if (TableType* ttv = getMutable<TableType>(tableType))
855-
ttv->name = "typeof(string)";
856-
857-
return arena->addType(TableType{{{{"__index", {tableType}}}}, std::nullopt, TypeLevel{}, TableState::Sealed});
858-
}
859-
else
860-
{
861-
FunctionType formatFTV{arena->addTypePack(TypePack{{stringType}, variadicTailPack}), oneStringPack};
862-
formatFTV.magicFunction = &magicFunctionFormat;
863-
const TypeId formatFn = arena->addType(formatFTV);
864-
attachDcrMagicFunction(formatFn, dcrMagicFunctionFormat);
865-
866-
const TypeId stringToStringType = makeFunction(*arena, std::nullopt, {}, {}, {stringType}, {}, {stringType});
867-
868-
const TypeId replArgType = arena->addType(
869-
UnionType{{stringType, arena->addType(TableType({}, TableIndexer(stringType, stringType), TypeLevel{}, TableState::Generic)),
870-
makeFunction(*arena, std::nullopt, {}, {}, {stringType}, {}, {stringType})}});
871-
const TypeId gsubFunc = makeFunction(*arena, stringType, {}, {}, {stringType, replArgType, optionalNumber}, {}, {stringType, numberType});
872-
const TypeId gmatchFunc =
873-
makeFunction(*arena, stringType, {}, {}, {stringType}, {}, {arena->addType(FunctionType{emptyPack, stringVariadicList})});
874-
attachMagicFunction(gmatchFunc, magicFunctionGmatch);
875-
attachDcrMagicFunction(gmatchFunc, dcrMagicFunctionGmatch);
876-
877-
const TypeId matchFunc = arena->addType(FunctionType{
878-
arena->addTypePack({stringType, stringType, optionalNumber}), arena->addTypePack(TypePackVar{VariadicTypePack{stringType}})});
879-
attachMagicFunction(matchFunc, magicFunctionMatch);
880-
attachDcrMagicFunction(matchFunc, dcrMagicFunctionMatch);
881-
882-
const TypeId findFunc = arena->addType(FunctionType{arena->addTypePack({stringType, stringType, optionalNumber, optionalBoolean}),
883-
arena->addTypePack(TypePack{{optionalNumber, optionalNumber}, stringVariadicList})});
884-
attachMagicFunction(findFunc, magicFunctionFind);
885-
attachDcrMagicFunction(findFunc, dcrMagicFunctionFind);
886-
887-
TableType::Props stringLib = {
888-
{"byte", {arena->addType(FunctionType{arena->addTypePack({stringType, optionalNumber, optionalNumber}), numberVariadicList})}},
889-
{"char", {arena->addType(FunctionType{numberVariadicList, arena->addTypePack({stringType})})}},
890-
{"find", {findFunc}},
891-
{"format", {formatFn}}, // FIXME
892-
{"gmatch", {gmatchFunc}},
893-
{"gsub", {gsubFunc}},
894-
{"len", {makeFunction(*arena, stringType, {}, {}, {}, {}, {numberType})}},
895-
{"lower", {stringToStringType}},
896-
{"match", {matchFunc}},
897-
{"rep", {makeFunction(*arena, stringType, {}, {}, {numberType}, {}, {stringType})}},
898-
{"reverse", {stringToStringType}},
899-
{"sub", {makeFunction(*arena, stringType, {}, {}, {numberType, optionalNumber}, {}, {stringType})}},
900-
{"upper", {stringToStringType}},
901-
{"split", {makeFunction(*arena, stringType, {}, {}, {optionalString}, {},
902-
{arena->addType(TableType{{}, TableIndexer{numberType, stringType}, TypeLevel{}, TableState::Sealed})})}},
903-
{"pack", {arena->addType(FunctionType{
904-
arena->addTypePack(TypePack{{stringType}, variadicTailPack}),
905-
oneStringPack,
906-
})}},
907-
{"packsize", {makeFunction(*arena, stringType, {}, {}, {}, {}, {numberType})}},
908-
{"unpack", {arena->addType(FunctionType{
909-
arena->addTypePack(TypePack{{stringType, stringType, optionalNumber}}),
910-
variadicTailPack,
911-
})}},
912-
};
913-
914-
assignPropDocumentationSymbols(stringLib, "@luau/global/string");
915-
916-
TypeId tableType = arena->addType(TableType{std::move(stringLib), std::nullopt, TypeLevel{}, TableState::Sealed});
917-
918-
if (TableType* ttv = getMutable<TableType>(tableType))
919-
ttv->name = "typeof(string)";
920-
921-
return arena->addType(TableType{{{{"__index", {tableType}}}}, std::nullopt, TypeLevel{}, TableState::Sealed});
922-
}
775+
FunctionType formatFTV{arena->addTypePack(TypePack{{stringType}, variadicTailPack}), oneStringPack};
776+
formatFTV.magicFunction = &magicFunctionFormat;
777+
formatFTV.isCheckedFunction = true;
778+
const TypeId formatFn = arena->addType(formatFTV);
779+
attachDcrMagicFunction(formatFn, dcrMagicFunctionFormat);
780+
781+
782+
const TypeId stringToStringType = makeFunction(*arena, std::nullopt, {}, {}, {stringType}, {}, {stringType}, /* checked */ true);
783+
784+
const TypeId replArgType =
785+
arena->addType(UnionType{{stringType, arena->addType(TableType({}, TableIndexer(stringType, stringType), TypeLevel{}, TableState::Generic)),
786+
makeFunction(*arena, std::nullopt, {}, {}, {stringType}, {}, {stringType}, /* checked */ false)}});
787+
const TypeId gsubFunc =
788+
makeFunction(*arena, stringType, {}, {}, {stringType, replArgType, optionalNumber}, {}, {stringType, numberType}, /* checked */ false);
789+
const TypeId gmatchFunc =
790+
makeFunction(*arena, stringType, {}, {}, {stringType}, {}, {arena->addType(FunctionType{emptyPack, stringVariadicList})}, /* checked */ true);
791+
attachMagicFunction(gmatchFunc, magicFunctionGmatch);
792+
attachDcrMagicFunction(gmatchFunc, dcrMagicFunctionGmatch);
793+
794+
FunctionType matchFuncTy{
795+
arena->addTypePack({stringType, stringType, optionalNumber}), arena->addTypePack(TypePackVar{VariadicTypePack{stringType}})};
796+
matchFuncTy.isCheckedFunction = true;
797+
const TypeId matchFunc = arena->addType(matchFuncTy);
798+
attachMagicFunction(matchFunc, magicFunctionMatch);
799+
attachDcrMagicFunction(matchFunc, dcrMagicFunctionMatch);
800+
801+
FunctionType findFuncTy{arena->addTypePack({stringType, stringType, optionalNumber, optionalBoolean}),
802+
arena->addTypePack(TypePack{{optionalNumber, optionalNumber}, stringVariadicList})};
803+
findFuncTy.isCheckedFunction = true;
804+
const TypeId findFunc = arena->addType(findFuncTy);
805+
attachMagicFunction(findFunc, magicFunctionFind);
806+
attachDcrMagicFunction(findFunc, dcrMagicFunctionFind);
807+
808+
// string.byte : string -> number? -> number? -> ...number
809+
FunctionType stringDotByte{arena->addTypePack({stringType, optionalNumber, optionalNumber}), numberVariadicList};
810+
stringDotByte.isCheckedFunction = true;
811+
812+
// string.char : .... number -> string
813+
FunctionType stringDotChar{numberVariadicList, arena->addTypePack({stringType})};
814+
stringDotChar.isCheckedFunction = true;
815+
816+
// string.unpack : string -> string -> number? -> ...any
817+
FunctionType stringDotUnpack{
818+
arena->addTypePack(TypePack{{stringType, stringType, optionalNumber}}),
819+
variadicTailPack,
820+
};
821+
stringDotUnpack.isCheckedFunction = true;
822+
823+
TableType::Props stringLib = {
824+
{"byte", {arena->addType(stringDotByte)}},
825+
{"char", {arena->addType(stringDotChar)}},
826+
{"find", {findFunc}},
827+
{"format", {formatFn}}, // FIXME
828+
{"gmatch", {gmatchFunc}},
829+
{"gsub", {gsubFunc}},
830+
{"len", {makeFunction(*arena, stringType, {}, {}, {}, {}, {numberType}, /* checked */ true)}},
831+
{"lower", {stringToStringType}},
832+
{"match", {matchFunc}},
833+
{"rep", {makeFunction(*arena, stringType, {}, {}, {numberType}, {}, {stringType}, /* checked */ true)}},
834+
{"reverse", {stringToStringType}},
835+
{"sub", {makeFunction(*arena, stringType, {}, {}, {numberType, optionalNumber}, {}, {stringType}, /* checked */ true)}},
836+
{"upper", {stringToStringType}},
837+
{"split", {makeFunction(*arena, stringType, {}, {}, {optionalString}, {},
838+
{arena->addType(TableType{{}, TableIndexer{numberType, stringType}, TypeLevel{}, TableState::Sealed})},
839+
/* checked */ true)}},
840+
{"pack", {arena->addType(FunctionType{
841+
arena->addTypePack(TypePack{{stringType}, variadicTailPack}),
842+
oneStringPack,
843+
})}},
844+
{"packsize", {makeFunction(*arena, stringType, {}, {}, {}, {}, {numberType}, /* checked */ true)}},
845+
{"unpack", {arena->addType(stringDotUnpack)}},
846+
};
847+
848+
assignPropDocumentationSymbols(stringLib, "@luau/global/string");
849+
850+
TypeId tableType = arena->addType(TableType{std::move(stringLib), std::nullopt, TypeLevel{}, TableState::Sealed});
851+
852+
if (TableType* ttv = getMutable<TableType>(tableType))
853+
ttv->name = "typeof(string)";
854+
855+
return arena->addType(TableType{{{{"__index", {tableType}}}}, std::nullopt, TypeLevel{}, TableState::Sealed});
923856
}
924857

925858
static std::optional<WithPredicate<TypePackId>> magicFunctionSelect(

Analysis/src/Error.cpp

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@
77
#include "Luau/NotNull.h"
88
#include "Luau/StringUtils.h"
99
#include "Luau/ToString.h"
10+
#include "Luau/TypeFamily.h"
1011

1112
#include <optional>
1213
#include <stdexcept>
1314
#include <string>
1415
#include <type_traits>
16+
#include <unordered_set>
1517

1618
LUAU_FASTINTVARIABLE(LuauIndentTypeMismatchMaxTypeLength, 10)
1719

@@ -61,6 +63,23 @@ static std::string wrongNumberOfArgsString(
6163
namespace Luau
6264
{
6365

66+
// this list of binary operator type families is used for better stringification of type families errors
67+
static const std::unordered_map<std::string, const char*> kBinaryOps{
68+
{"add", "+"}, {"sub", "-"}, {"mul", "*"}, {"div", "/"}, {"idiv", "//"}, {"pow", "^"}, {"mod", "%"}, {"concat", ".."}, {"and", "and"},
69+
{"or", "or"}, {"lt", "< or >="}, {"le", "<= or >"}, {"eq", "== or ~="}
70+
};
71+
72+
// this list of unary operator type families is used for better stringification of type families errors
73+
static const std::unordered_map<std::string, const char*> kUnaryOps{
74+
{"unm", "-"}, {"len", "#"}, {"not", "not"}
75+
};
76+
77+
// this list of type families will receive a special error indicating that the user should file a bug on the GitHub repository
78+
// putting a type family in this list indicates that it is expected to _always_ reduce
79+
static const std::unordered_set<std::string> kUnreachableTypeFamilies{
80+
"refine", "singleton", "union", "intersect"
81+
};
82+
6483
struct ErrorConverter
6584
{
6685
FileResolver* fileResolver = nullptr;
@@ -565,6 +584,96 @@ struct ErrorConverter
565584

566585
std::string operator()(const UninhabitedTypeFamily& e) const
567586
{
587+
auto tfit = get<TypeFamilyInstanceType>(e.ty);
588+
LUAU_ASSERT(tfit); // Luau analysis has actually done something wrong if this type is not a type family.
589+
if (!tfit)
590+
return "Unexpected type " + Luau::toString(e.ty) + " flagged as an uninhabited type family.";
591+
592+
// unary operators
593+
if (auto unaryString = kUnaryOps.find(tfit->family->name); unaryString != kUnaryOps.end())
594+
{
595+
std::string result = "Operator '" + std::string(unaryString->second) + "' could not be applied to ";
596+
597+
if (tfit->typeArguments.size() == 1 && tfit->packArguments.empty())
598+
{
599+
result += "operand of type " + Luau::toString(tfit->typeArguments[0]);
600+
601+
if (tfit->family->name != "not")
602+
result += "; there is no corresponding overload for __" + tfit->family->name;
603+
}
604+
else
605+
{
606+
// if it's not the expected case, we ought to add a specialization later, but this is a sane default.
607+
result += "operands of types ";
608+
609+
bool isFirst = true;
610+
for (auto arg : tfit->typeArguments)
611+
{
612+
if (!isFirst)
613+
result += ", ";
614+
615+
result += Luau::toString(arg);
616+
isFirst = false;
617+
}
618+
619+
for (auto packArg : tfit->packArguments)
620+
result += ", " + Luau::toString(packArg);
621+
}
622+
623+
return result;
624+
}
625+
626+
// binary operators
627+
if (auto binaryString = kBinaryOps.find(tfit->family->name); binaryString != kBinaryOps.end())
628+
{
629+
std::string result = "Operator '" + std::string(binaryString->second) + "' could not be applied to operands of types ";
630+
631+
if (tfit->typeArguments.size() == 2 && tfit->packArguments.empty())
632+
{
633+
// this is the expected case.
634+
result += Luau::toString(tfit->typeArguments[0]) + " and " + Luau::toString(tfit->typeArguments[1]);
635+
}
636+
else
637+
{
638+
// if it's not the expected case, we ought to add a specialization later, but this is a sane default.
639+
640+
bool isFirst = true;
641+
for (auto arg : tfit->typeArguments)
642+
{
643+
if (!isFirst)
644+
result += ", ";
645+
646+
result += Luau::toString(arg);
647+
isFirst = false;
648+
}
649+
650+
for (auto packArg : tfit->packArguments)
651+
result += ", " + Luau::toString(packArg);
652+
}
653+
654+
result += "; there is no corresponding overload for __" + tfit->family->name;
655+
656+
return result;
657+
}
658+
659+
// miscellaneous
660+
661+
if ("keyof" == tfit->family->name || "rawkeyof" == tfit->family->name)
662+
{
663+
if (tfit->typeArguments.size() == 1 && tfit->packArguments.empty())
664+
return "Type '" + toString(tfit->typeArguments[0]) + "' does not have keys, so '" + Luau::toString(e.ty) + "' is invalid";
665+
else
666+
return "Type family instance " + Luau::toString(e.ty) + " is ill-formed, and thus invalid";
667+
}
668+
669+
if (kUnreachableTypeFamilies.count(tfit->family->name))
670+
{
671+
return "Type family instance " + Luau::toString(e.ty) + " is uninhabited\n" +
672+
"This is likely to be a bug, please report it at https://github.com/luau-lang/luau/issues";
673+
}
674+
675+
// Everything should be specialized above to report a more descriptive error that hopefully does not mention "type families" explicitly.
676+
// If we produce this message, it's an indication that we've missed a specialization and it should be fixed!
568677
return "Type family instance " + Luau::toString(e.ty) + " is uninhabited";
569678
}
570679

0 commit comments

Comments
 (0)