Skip to content

Commit

Permalink
Sync to upstream/release/657 (#1619)
Browse files Browse the repository at this point in the history
## General
- Fix a parsing bug related to the starting position of function names.
- Rename Luau's `Table` struct to `LuaTable`.

## New Solver
- Add support for generics in user-defined type functions
([RFC](https://rfcs.luau.org/support-for-generic-function-types-in-user-defined-type-functions.html)).
- Provide a definition of `math.lerp` to the typechecker.
- Implement error suppression in `string.format`.
  - Fixes #1587.
- Ensure function call discriminant types are always filled when
resolving `FunctionCallConstraint`.

---

Co-authored-by: Ariel Weiss <[email protected]>
Co-authored-by: Hunter Goldstein <[email protected]>
Co-authored-by: Talha Pathan <[email protected]>
Co-authored-by: Vyacheslav Egorov <[email protected]>
  • Loading branch information
5 people authored Jan 17, 2025
1 parent 67e9d85 commit 2904750
Show file tree
Hide file tree
Showing 64 changed files with 2,209 additions and 675 deletions.
5 changes: 5 additions & 0 deletions Analysis/include/Luau/ConstraintSolver.h
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,11 @@ struct ConstraintSolver
void throwUserCancelError() const;

ToStringOptions opts;

void fillInDiscriminantTypes(
NotNull<const Constraint> constraint,
const std::vector<std::optional<TypeId>>& discriminantTypes
);
};

void dump(NotNull<Scope> rootScope, struct ToStringOptions& opts);
Expand Down
23 changes: 21 additions & 2 deletions Analysis/include/Luau/TypeFunctionRuntime.h
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,14 @@ struct TypeFunctionVariadicTypePack
TypeFunctionTypeId type;
};

using TypeFunctionTypePackVariant = Variant<TypeFunctionTypePack, TypeFunctionVariadicTypePack>;
struct TypeFunctionGenericTypePack
{
bool isNamed = false;

std::string name;
};

using TypeFunctionTypePackVariant = Variant<TypeFunctionTypePack, TypeFunctionVariadicTypePack, TypeFunctionGenericTypePack>;

struct TypeFunctionTypePackVar
{
Expand All @@ -135,6 +142,9 @@ struct TypeFunctionTypePackVar

struct TypeFunctionFunctionType
{
std::vector<TypeFunctionTypeId> generics;
std::vector<TypeFunctionTypePackId> genericPacks;

TypeFunctionTypePackId argTypes;
TypeFunctionTypePackId retTypes;
};
Expand Down Expand Up @@ -210,6 +220,14 @@ struct TypeFunctionClassType
std::string name;
};

struct TypeFunctionGenericType
{
bool isNamed = false;
bool isPack = false;

std::string name;
};

using TypeFunctionTypeVariant = Luau::Variant<
TypeFunctionPrimitiveType,
TypeFunctionAnyType,
Expand All @@ -221,7 +239,8 @@ using TypeFunctionTypeVariant = Luau::Variant<
TypeFunctionNegationType,
TypeFunctionFunctionType,
TypeFunctionTableType,
TypeFunctionClassType>;
TypeFunctionClassType,
TypeFunctionGenericType>;

struct TypeFunctionType
{
Expand Down
36 changes: 6 additions & 30 deletions Analysis/src/AstQuery.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@

LUAU_FASTFLAG(LuauSolverV2)

LUAU_FASTFLAGVARIABLE(LuauDocumentationAtPosition)

namespace Luau
{

Expand Down Expand Up @@ -518,7 +516,6 @@ static std::optional<DocumentationSymbol> getMetatableDocumentation(
const AstName& index
)
{
LUAU_ASSERT(FFlag::LuauDocumentationAtPosition);
auto indexIt = mtable->props.find("__index");
if (indexIt == mtable->props.end())
return std::nullopt;
Expand Down Expand Up @@ -575,26 +572,7 @@ std::optional<DocumentationSymbol> getDocumentationSymbolAtPosition(const Source
}
else if (const ClassType* ctv = get<ClassType>(parentTy))
{
if (FFlag::LuauDocumentationAtPosition)
{
while (ctv)
{
if (auto propIt = ctv->props.find(indexName->index.value); propIt != ctv->props.end())
{
if (FFlag::LuauSolverV2)
{
if (auto ty = propIt->second.readTy)
return checkOverloadedDocumentationSymbol(module, *ty, parentExpr, propIt->second.documentationSymbol);
}
else
return checkOverloadedDocumentationSymbol(
module, propIt->second.type(), parentExpr, propIt->second.documentationSymbol
);
}
ctv = ctv->parent ? Luau::get<Luau::ClassType>(*ctv->parent) : nullptr;
}
}
else
while (ctv)
{
if (auto propIt = ctv->props.find(indexName->index.value); propIt != ctv->props.end())
{
Expand All @@ -608,17 +586,15 @@ std::optional<DocumentationSymbol> getDocumentationSymbolAtPosition(const Source
module, propIt->second.type(), parentExpr, propIt->second.documentationSymbol
);
}
ctv = ctv->parent ? Luau::get<Luau::ClassType>(*ctv->parent) : nullptr;
}
}
else if (FFlag::LuauDocumentationAtPosition)
else if (const PrimitiveType* ptv = get<PrimitiveType>(parentTy); ptv && ptv->metatable)
{
if (const PrimitiveType* ptv = get<PrimitiveType>(parentTy); ptv && ptv->metatable)
if (auto mtable = get<TableType>(*ptv->metatable))
{
if (auto mtable = get<TableType>(*ptv->metatable))
{
if (std::optional<std::string> docSymbol = getMetatableDocumentation(module, parentExpr, mtable, indexName->index))
return docSymbol;
}
if (std::optional<std::string> docSymbol = getMetatableDocumentation(module, parentExpr, mtable, indexName->index))
return docSymbol;
}
}
}
Expand Down
24 changes: 22 additions & 2 deletions Analysis/src/BuiltinDefinitions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauTypestateBuiltins2)
LUAU_FASTFLAGVARIABLE(LuauStringFormatArityFix)
LUAU_FASTFLAGVARIABLE(LuauStringFormatErrorSuppression)
LUAU_FASTFLAG(AutocompleteRequirePathSuggestions2)
LUAU_FASTFLAG(LuauVectorDefinitionsExtra)

Expand Down Expand Up @@ -631,10 +632,29 @@ static void dcrMagicFunctionTypeCheckFormat(MagicFunctionTypeCheckContext contex
Location location = context.callSite->args.data[i + (calledWithSelf ? 0 : paramOffset)]->location;
// use subtyping instead here
SubtypingResult result = context.typechecker->subtyping->isSubtype(actualTy, expectedTy, context.checkScope);

if (!result.isSubtype)
{
Reasonings reasonings = context.typechecker->explainReasonings(actualTy, expectedTy, location, result);
context.typechecker->reportError(TypeMismatch{expectedTy, actualTy, reasonings.toString()}, location);
if (FFlag::LuauStringFormatErrorSuppression)
{
switch (shouldSuppressErrors(NotNull{&context.typechecker->normalizer}, actualTy))
{
case ErrorSuppression::Suppress:
break;
case ErrorSuppression::NormalizationFailed:
break;
case ErrorSuppression::DoNotSuppress:
Reasonings reasonings = context.typechecker->explainReasonings(actualTy, expectedTy, location, result);

if (!reasonings.suppressed)
context.typechecker->reportError(TypeMismatch{expectedTy, actualTy, reasonings.toString()}, location);
}
}
else
{
Reasonings reasonings = context.typechecker->explainReasonings(actualTy, expectedTy, location, result);
context.typechecker->reportError(TypeMismatch{expectedTy, actualTy, reasonings.toString()}, location);
}
}
}
}
Expand Down
100 changes: 76 additions & 24 deletions Analysis/src/ConstraintSolver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ LUAU_FASTFLAG(LuauNewSolverPopulateTableLocations)
LUAU_FASTFLAGVARIABLE(LuauAllowNilAssignmentToIndexer)
LUAU_FASTFLAG(LuauUserTypeFunNoExtraConstraint)
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
LUAU_FASTFLAGVARIABLE(LuauAlwaysFillInFunctionCallDiscriminantTypes)

namespace Luau
{
Expand Down Expand Up @@ -1153,6 +1154,42 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
return true;
}

void ConstraintSolver::fillInDiscriminantTypes(
NotNull<const Constraint> constraint,
const std::vector<std::optional<TypeId>>& discriminantTypes
)
{
for (std::optional<TypeId> ty : discriminantTypes)
{
if (!ty)
continue;

// If the discriminant type has been transmuted, we need to unblock them.
if (!isBlocked(*ty))
{
unblock(*ty, constraint->location);
continue;
}

if (FFlag::LuauRemoveNotAnyHack)
{
// We bind any unused discriminants to the `*no-refine*` type indicating that it can be safely ignored.
emplaceType<BoundType>(asMutable(follow(*ty)), builtinTypes->noRefineType);
}
else
{
// We use `any` here because the discriminant type may be pointed at by both branches,
// where the discriminant type is not negated, and the other where it is negated, i.e.
// `unknown ~ unknown` and `~unknown ~ never`, so `T & unknown ~ T` and `T & ~unknown ~ never`
// v.s.
// `any ~ any` and `~any ~ any`, so `T & any ~ T` and `T & ~any ~ T`
//
// In practice, users cannot negate `any`, so this is an implementation detail we can always change.
emplaceType<BoundType>(asMutable(follow(*ty)), builtinTypes->anyType);
}
}
}

bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<const Constraint> constraint)
{
TypeId fn = follow(c.fn);
Expand All @@ -1168,19 +1205,25 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
{
emplaceTypePack<BoundTypePack>(asMutable(c.result), builtinTypes->anyTypePack);
unblock(c.result, constraint->location);
if (FFlag::LuauAlwaysFillInFunctionCallDiscriminantTypes)
fillInDiscriminantTypes(constraint, c.discriminantTypes);
return true;
}

// if we're calling an error type, the result is an error type, and that's that.
if (get<ErrorType>(fn))
{
bind(constraint, c.result, builtinTypes->errorRecoveryTypePack());
if (FFlag::LuauAlwaysFillInFunctionCallDiscriminantTypes)
fillInDiscriminantTypes(constraint, c.discriminantTypes);
return true;
}

if (get<NeverType>(fn))
{
bind(constraint, c.result, builtinTypes->neverTypePack);
if (FFlag::LuauAlwaysFillInFunctionCallDiscriminantTypes)
fillInDiscriminantTypes(constraint, c.discriminantTypes);
return true;
}

Expand Down Expand Up @@ -1261,36 +1304,45 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
emplace<FreeTypePack>(constraint, c.result, constraint->scope);
}

for (std::optional<TypeId> ty : c.discriminantTypes)
if (FFlag::LuauAlwaysFillInFunctionCallDiscriminantTypes)
{
if (!ty)
continue;

// If the discriminant type has been transmuted, we need to unblock them.
if (!isBlocked(*ty))
fillInDiscriminantTypes(constraint, c.discriminantTypes);
}
else
{
// NOTE: This is the body of the `fillInDiscriminantTypes` helper.
for (std::optional<TypeId> ty : c.discriminantTypes)
{
unblock(*ty, constraint->location);
continue;
}
if (!ty)
continue;

if (FFlag::LuauRemoveNotAnyHack)
{
// We bind any unused discriminants to the `*no-refine*` type indicating that it can be safely ignored.
emplaceType<BoundType>(asMutable(follow(*ty)), builtinTypes->noRefineType);
}
else
{
// We use `any` here because the discriminant type may be pointed at by both branches,
// where the discriminant type is not negated, and the other where it is negated, i.e.
// `unknown ~ unknown` and `~unknown ~ never`, so `T & unknown ~ T` and `T & ~unknown ~ never`
// v.s.
// `any ~ any` and `~any ~ any`, so `T & any ~ T` and `T & ~any ~ T`
//
// In practice, users cannot negate `any`, so this is an implementation detail we can always change.
emplaceType<BoundType>(asMutable(follow(*ty)), builtinTypes->anyType);
// If the discriminant type has been transmuted, we need to unblock them.
if (!isBlocked(*ty))
{
unblock(*ty, constraint->location);
continue;
}

if (FFlag::LuauRemoveNotAnyHack)
{
// We bind any unused discriminants to the `*no-refine*` type indicating that it can be safely ignored.
emplaceType<BoundType>(asMutable(follow(*ty)), builtinTypes->noRefineType);
}
else
{
// We use `any` here because the discriminant type may be pointed at by both branches,
// where the discriminant type is not negated, and the other where it is negated, i.e.
// `unknown ~ unknown` and `~unknown ~ never`, so `T & unknown ~ T` and `T & ~unknown ~ never`
// v.s.
// `any ~ any` and `~any ~ any`, so `T & any ~ T` and `T & ~any ~ T`
//
// In practice, users cannot negate `any`, so this is an implementation detail we can always change.
emplaceType<BoundType>(asMutable(follow(*ty)), builtinTypes->anyType);
}
}
}


OverloadResolver resolver{
builtinTypes,
NotNull{arena},
Expand Down
Loading

0 comments on commit 2904750

Please sign in to comment.