Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,6 @@
__pycache__
.cache
.clangd
compile_commands.json
flake.nix
flake.lock
1 change: 1 addition & 0 deletions Analysis/include/Luau/ToString.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ struct ToStringOptions
bool hideFunctionSelfArgument = false; // If true, `self: X` will be omitted from the function signature if the function has self
bool hideTableAliasExpansions = false; // If true, all table aliases will not be expanded
bool useQuestionMarks = true; // If true, use a postfix ? for options, else write them out as unions that include nil.
bool useTruthyFalsy = true; // If true, `~(false?)` and `false?` will emit `truthy` and `falsy` respectively.
bool ignoreSyntheticName = false; // If true, ignore synthetic names on table types.
size_t maxTableLength = size_t(FInt::LuauTableTypeMaximumStringifierLength); // Only applied to TableTypes
size_t maxTypeLength = size_t(FInt::LuauTypeMaximumStringifierLength);
Expand Down
23 changes: 20 additions & 3 deletions Analysis/src/ToString.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "Luau/TypePack.h"
#include "Luau/Type.h"
#include "Luau/TypeFunction.h"
#include "Luau/TypeUtils.h"
#include "Luau/VisitType.h"
#include "Luau/TypeOrPack.h"

Expand Down Expand Up @@ -39,6 +40,7 @@ LUAU_FASTFLAG(LuauIntegerType2)
*/
LUAU_FASTINTVARIABLE(DebugLuauVerboseTypeNames, 0)
LUAU_FASTFLAGVARIABLE(DebugLuauToStringNoLexicalSort)
LUAU_FASTFLAGVARIABLE(LuauToStringTruthyFalsy)

namespace Luau
{
Expand Down Expand Up @@ -884,8 +886,14 @@ struct TypeStringifier
state.emit("*no-refine*");
}

void operator()(TypeId, const UnionType& uv)
void operator()(TypeId ty, const UnionType& uv)
{
if (FFlag::LuauToStringTruthyFalsy && state.opts.useTruthyFalsy && isApproximatelyFalsyType(ty))
{
state.emit("falsy");
return;
}

if (state.hasSeen(&uv))
{
state.result.cycle = true;
Expand Down Expand Up @@ -1016,7 +1024,10 @@ struct TypeStringifier
std::string saved = std::move(state.result.name);
size_t savedSpansSize = state.result.typeSpans.size();

bool needParens = !state.cycleNames.contains(el) && (get<UnionType>(el) != nullptr || get<FunctionType>(el) != nullptr);
// soooo 'falsy' is technically a UnionType :(
bool isNonFalsyUnion =
get<UnionType>(el) != nullptr && (FFlag::LuauToStringTruthyFalsy && state.opts.useTruthyFalsy ? !isApproximatelyFalsyType(el) : true);
bool needParens = !state.cycleNames.contains(el) && (isNonFalsyUnion || get<FunctionType>(el) != nullptr);

if (needParens)
state.emit("(");
Expand Down Expand Up @@ -1115,8 +1126,14 @@ struct TypeStringifier
state.emit("never");
}

void operator()(TypeId, const NegationType& ntv)
void operator()(TypeId ty, const NegationType& ntv)
{
if (FFlag::LuauToStringTruthyFalsy && state.opts.useTruthyFalsy && isApproximatelyTruthyType(ty))
{
state.emit("truthy");
return;
}

state.emit("~");

// The precedence of `~` should be less than `|` and `&`.
Expand Down
6 changes: 4 additions & 2 deletions tests/Normalize.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauIntegerType2)
LUAU_FASTFLAG(DebugLuauForceOldSolver)
LUAU_FASTFLAG(LuauToStringTruthyFalsy)

using namespace Luau;

Expand Down Expand Up @@ -1111,8 +1112,9 @@ TEST_CASE_FIXTURE(NormalizeFixture, "truthy_table_property_and_optional_table_wi

TEST_CASE_FIXTURE(NormalizeFixture, "free_type_and_not_truthy")
{
ScopedFastFlag sff[] = {
ScopedFastFlag sffs[] = {
{FFlag::DebugLuauForceOldSolver, false}, // Only because it affects the stringification of free types
{FFlag::LuauToStringTruthyFalsy, true},
};

TypeId freeTy = arena.freshType(getBuiltins(), getGlobalScope());
Expand All @@ -1125,7 +1127,7 @@ TEST_CASE_FIXTURE(NormalizeFixture, "free_type_and_not_truthy")

TypeId result = typeFromNormal(*norm);

CHECK("'a & (false?)" == toString(result));
CHECK("'a & falsy" == toString(result));
}

TEST_CASE_FIXTURE(NormalizeFixture, "free_type_intersection_ordering")
Expand Down
4 changes: 3 additions & 1 deletion tests/Simplify.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using namespace Luau;

LUAU_FASTFLAG(DebugLuauForceOldSolver)
LUAU_FASTFLAG(LuauToStringTruthyFalsy)
LUAU_DYNAMIC_FASTINT(LuauSimplificationComplexityLimit)

namespace
Expand Down Expand Up @@ -482,10 +483,11 @@ TEST_CASE_FIXTURE(SimplifyFixture, "union")

TEST_CASE_FIXTURE(SimplifyFixture, "two_unions")
{
ScopedFastFlag sff{FFlag::LuauToStringTruthyFalsy, true};
ScopedFastInt sfi{DFInt::LuauSimplificationComplexityLimit, 10};
TypeId t1 = arena->addType(UnionType{{numberTy, booleanTy, stringTy, nilTy, tableTy}});

CHECK("false?" == intersectStr(t1, falsyTy));
CHECK("falsy" == intersectStr(t1, falsyTy));
}

TEST_CASE_FIXTURE(SimplifyFixture, "curious_union")
Expand Down
66 changes: 66 additions & 0 deletions tests/ToString.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using namespace Luau;

LUAU_FASTFLAG(DebugLuauForceOldSolver)
LUAU_FASTFLAG(LuauToStringTruthyFalsy)

TEST_SUITE_BEGIN("ToString");

Expand Down Expand Up @@ -1062,4 +1063,69 @@ TEST_CASE_FIXTURE(Fixture, "record_type_compositions_generic")
CHECK_EQ(recordedTyObject, requireTypeAlias("Object"));
}

TEST_CASE_FIXTURE(BuiltinsFixture, "tostring_truthy_falsy")
{
ScopedFastFlag sffs[] = {
{FFlag::DebugLuauForceOldSolver, false},
{FFlag::LuauToStringTruthyFalsy, true},
};

CheckResult result = check(R"(
type function negate(t: type)
return types.negationof(t)
end
type meow = false?
type mrrp = negate<false?>
)");

LUAU_REQUIRE_NO_ERRORS(result);

CHECK_EQ(toString(requireTypeAlias("meow")), "falsy");
CHECK_EQ(toString(requireTypeAlias("mrrp")), "truthy");
}

TEST_CASE_FIXTURE(BuiltinsFixture, "tostring_truthy_falsy_expanded")
{
ScopedFastFlag sffs[] = {
{FFlag::DebugLuauForceOldSolver, false},
{FFlag::LuauToStringTruthyFalsy, true},
};

CheckResult result = check(R"(
type function negate(t: type)
return types.negationof(t)
end
type kya = (false | nil) | false
type purr = negate<(false | nil) | false>
)");

LUAU_REQUIRE_NO_ERRORS(result);

CHECK_EQ(toString(requireTypeAlias("kya")), "falsy");
CHECK_EQ(toString(requireTypeAlias("purr")), "truthy");
}

TEST_CASE_FIXTURE(BuiltinsFixture, "tostring_truthy_falsy_no_parenthesis")
{
ScopedFastFlag sffs[] = {
{FFlag::DebugLuauForceOldSolver, false},
{FFlag::LuauToStringTruthyFalsy, true},
};

ToStringOptions opts;

CheckResult result = check(R"(
type function negate(t: type)
return types.negationof(t)
end
type hiss = unknown & (false?)
type scratch = unknown & negate<false?>
)");

LUAU_REQUIRE_NO_ERRORS(result);

CHECK_EQ(toString(requireTypeAlias("hiss")), "falsy & unknown");
CHECK_EQ(toString(requireTypeAlias("scratch")), "truthy & unknown");
}

TEST_SUITE_END();
4 changes: 3 additions & 1 deletion tests/TypeInfer.functions.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ LUAU_FASTFLAG(LuauBidirectionalInferenceVariadics)
LUAU_FASTFLAG(LuauConstraintGraph)
LUAU_FASTFLAG(LuauBidirectionalInferenceBetterLambdaHandling)
LUAU_FASTFLAG(LuauHigherOrderGenericInference)
LUAU_FASTFLAG(LuauToStringTruthyFalsy)

TEST_SUITE_BEGIN("TypeInferFunctions");

Expand Down Expand Up @@ -2888,6 +2889,7 @@ TEST_CASE_FIXTURE(Fixture, "unifier_should_not_bind_free_types")
ScopedFastFlag sffs[] = {
{FFlag::LuauRemovePrimitiveTypeConstraintAndSubtypingUnifier, true},
{FFlag::LuauConstraintGraph, true},
{FFlag::LuauToStringTruthyFalsy, true},
};

CheckResult result = check(R"(
Expand Down Expand Up @@ -2922,7 +2924,7 @@ TEST_CASE_FIXTURE(Fixture, "unifier_should_not_bind_free_types")
auto tm2 = get<TypeMismatch>(result.errors[1]);
REQUIRE(tm2);
CHECK(toString(tm2->wantedType) == "string");
CHECK(toString(tm2->givenType) == "unknown & ~(false?)");
CHECK(toString(tm2->givenType) == "truthy & unknown");
}
}

Expand Down
8 changes: 6 additions & 2 deletions tests/TypeInfer.intersectionTypes.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ LUAU_FASTFLAG(LuauCheckFunctionStatementTypes)
LUAU_FASTFLAG(DebugLuauForceOldSolver)
LUAU_FASTFLAG(LuauPropagateFreeTypesIntoUnionAndIntersectionBounds)
LUAU_FASTFLAG(LuauDropUnionSubtypeReasoning)
LUAU_FASTFLAG(LuauToStringTruthyFalsy)

TEST_SUITE_BEGIN("IntersectionTypes");

Expand Down Expand Up @@ -1498,7 +1499,10 @@ TEST_CASE_FIXTURE(Fixture, "cli_80596_simplify_more_realistic_intersections")

TEST_CASE_FIXTURE(BuiltinsFixture, "narrow_intersection_nevers")
{
ScopedFastFlag sffs{FFlag::DebugLuauForceOldSolver, false};
ScopedFastFlag sffs[] = {
{FFlag::DebugLuauForceOldSolver, false},
{FFlag::LuauToStringTruthyFalsy, true},
};

loadDefinition(R"(
declare extern type Player with
Expand All @@ -1513,7 +1517,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "narrow_intersection_nevers")
end
)"));

CHECK_EQ("Player & { read Character: ~(false?) }", toString(requireTypeAtPosition({3, 23})));
CHECK_EQ("Player & { read Character: truthy }", toString(requireTypeAtPosition({3, 23})));
}

TEST_CASE_FIXTURE(BuiltinsFixture, "bounds_propagate_into_free_intersection_bounds")
Expand Down
13 changes: 10 additions & 3 deletions tests/TypeInfer.operators.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,14 @@ using namespace Luau;
LUAU_FASTFLAG(DebugLuauForceOldSolver)
LUAU_FASTFLAG(LuauSolverAgnosticStringification)
LUAU_FASTFLAG(LuauConcatDoesntAlwaysReturnString)
LUAU_FASTFLAG(LuauToStringTruthyFalsy)

TEST_SUITE_BEGIN("TypeInferOperators");

TEST_CASE_FIXTURE(Fixture, "or_joins_types")
{
ScopedFastFlag sff{FFlag::LuauToStringTruthyFalsy, true};

CheckResult result = check(R"(
local s = "a" or 10
local x:string|number = s
Expand All @@ -34,7 +37,7 @@ TEST_CASE_FIXTURE(Fixture, "or_joins_types")
if (!FFlag::DebugLuauForceOldSolver)
{
// FIXME: Regression
CHECK("(string & ~(false?)) | number" == toString(*requireType("s")));
CHECK("(string & truthy) | number" == toString(*requireType("s")));
CHECK("number | string" == toString(*requireType("x")));
}
else
Expand All @@ -46,6 +49,8 @@ TEST_CASE_FIXTURE(Fixture, "or_joins_types")

TEST_CASE_FIXTURE(Fixture, "or_joins_types_with_no_extras")
{
ScopedFastFlag sff{FFlag::LuauToStringTruthyFalsy, true};

CheckResult result = check(R"(
local s = "a" or 10
local x:number|string = s
Expand All @@ -56,7 +61,7 @@ TEST_CASE_FIXTURE(Fixture, "or_joins_types_with_no_extras")
if (!FFlag::DebugLuauForceOldSolver)
{
// FIXME: Regression.
CHECK("(string & ~(false?)) | number" == toString(*requireType("s")));
CHECK("(string & truthy) | number" == toString(*requireType("s")));
CHECK("number | string" == toString(*requireType("y")));
}
else
Expand All @@ -68,6 +73,8 @@ TEST_CASE_FIXTURE(Fixture, "or_joins_types_with_no_extras")

TEST_CASE_FIXTURE(Fixture, "or_joins_types_with_no_superfluous_union")
{
ScopedFastFlag sff{FFlag::LuauToStringTruthyFalsy, true};

CheckResult result = check(R"(
local s = "a" or "b"
local x:string = s
Expand All @@ -77,7 +84,7 @@ TEST_CASE_FIXTURE(Fixture, "or_joins_types_with_no_superfluous_union")
if (!FFlag::DebugLuauForceOldSolver)
{
// FIXME: Regression
CHECK("(string & ~(false?)) | string" == toString(requireType("s")));
CHECK("(string & truthy) | string" == toString(requireType("s")));
}
else
CHECK("string" == toString(requireType("s")));
Expand Down
14 changes: 10 additions & 4 deletions tests/TypeInfer.refinements.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ LUAU_FASTFLAG(DebugLuauForceOldSolver)
LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint)
LUAU_FASTFLAG(LuauRemovePrimitiveTypeConstraintAndSubtypingUnifier)
LUAU_FASTFLAG(LuauIndexingIntoErrorGivesError);
LUAU_FASTFLAG(LuauToStringTruthyFalsy)

using namespace Luau;

Expand Down Expand Up @@ -1035,6 +1036,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "either_number_or_string")

TEST_CASE_FIXTURE(Fixture, "not_t_or_some_prop_of_t")
{
ScopedFastFlag sff{FFlag::LuauToStringTruthyFalsy, true};

CheckResult result = check(R"(
local function f(t: {x: boolean}?)
if not t or t.x then
Expand All @@ -1054,7 +1057,7 @@ TEST_CASE_FIXTURE(Fixture, "not_t_or_some_prop_of_t")
// ... which we can't _quite_ refine into the type it ought to be:
//
// { write x: boolean, read x: true } | nil
CHECK_EQ("({ read x: ~(false?) } & { x: boolean })?", toString(requireTypeAtPosition({3, 28})));
CHECK_EQ("({ read x: truthy } & { x: boolean })?", toString(requireTypeAtPosition({3, 28})));
}
else
CHECK_EQ("{ x: boolean }?", toString(requireTypeAtPosition({3, 28})));
Expand Down Expand Up @@ -1663,6 +1666,7 @@ TEST_CASE_FIXTURE(RefinementExternTypeFixture, "isa_type_refinement_must_be_know

TEST_CASE_FIXTURE(RefinementExternTypeFixture, "asserting_optional_properties_should_not_refine_extern_types_to_never")
{
ScopedFastFlag sff{FFlag::LuauToStringTruthyFalsy, true};

CheckResult result = check(R"(
local weld: WeldConstraint = nil :: any
Expand All @@ -1676,7 +1680,7 @@ TEST_CASE_FIXTURE(RefinementExternTypeFixture, "asserting_optional_properties_sh
LUAU_REQUIRE_NO_ERRORS(result);

if (!FFlag::DebugLuauForceOldSolver)
CHECK_EQ("WeldConstraint & { read Part1: ~(false?) }", toString(requireTypeAtPosition({3, 15})));
CHECK_EQ("WeldConstraint & { read Part1: truthy }", toString(requireTypeAtPosition({3, 15})));
else
CHECK_EQ("WeldConstraint", toString(requireTypeAtPosition({3, 15})));
CHECK_EQ("Vector3", toString(requireTypeAtPosition({6, 29})));
Expand Down Expand Up @@ -2566,6 +2570,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "nonnil_refinement_on_generic")

TEST_CASE_FIXTURE(BuiltinsFixture, "truthy_refinement_on_generic")
{
ScopedFastFlag sff{FFlag::LuauToStringTruthyFalsy, true};
CheckResult result = check(R"(
local function printOptional<T>(item: T?, printer: (T) -> string): string
if item then
Expand All @@ -2578,7 +2583,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "truthy_refinement_on_generic")

LUAU_REQUIRE_NO_ERRORS(result);
if (!FFlag::DebugLuauForceOldSolver)
CHECK_EQ("T & ~(false?)", toString(requireTypeAtPosition({3, 31})));
CHECK_EQ("T & truthy", toString(requireTypeAtPosition({3, 31})));
else
CHECK_EQ("T", toString(requireTypeAtPosition({3, 31})));
}
Expand Down Expand Up @@ -2883,6 +2888,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "refinements_from_and_should_not_refine_to_ne
{
ScopedFastFlag sffs[] = {
{FFlag::DebugLuauForceOldSolver, false},
{FFlag::LuauToStringTruthyFalsy, true},
};

loadDefinition(R"(
Expand All @@ -2906,7 +2912,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "refinements_from_and_should_not_refine_to_ne

LUAU_REQUIRE_NO_ERRORS(results);

CHECK_EQ("(Config & { read KeyboardEnabled: false? }) | (Config & { read MouseEnabled: false? })", toString(requireTypeAtPosition({6, 24})));
CHECK_EQ("(Config & { read KeyboardEnabled: falsy }) | (Config & { read MouseEnabled: falsy })", toString(requireTypeAtPosition({6, 24})));
}

TEST_CASE_FIXTURE(Fixture, "force_simplify_constraint_doesnt_drop_blocked_type")
Expand Down
Loading
Loading