From 2dc896740a3c42689748c3329635064297fa04d0 Mon Sep 17 00:00:00 2001 From: nnullcolumn <264514568+nnullcolumn@users.noreply.github.com> Date: Sun, 12 Jul 2026 16:35:37 +0000 Subject: [PATCH 1/8] update gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 5852b330c3..fdc25f3bea 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ __pycache__ .cache .clangd +compile_commands.json From 840b18d08f2fe1898f345ca8e53354db8526b369 Mon Sep 17 00:00:00 2001 From: nnullcolumn <264514568+nnullcolumn@users.noreply.github.com> Date: Sun, 12 Jul 2026 16:36:06 +0000 Subject: [PATCH 2/8] update gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index fdc25f3bea..366cf8f0c5 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,5 @@ __pycache__ .cache .clangd compile_commands.json +flake.nix +flake.lock From d13e8d30ef6b43ac9cdc07e426d8005634ba8322 Mon Sep 17 00:00:00 2001 From: nnullcolumn <264514568+nnullcolumn@users.noreply.github.com> Date: Sun, 12 Jul 2026 18:55:09 +0000 Subject: [PATCH 3/8] implementation --- Analysis/include/Luau/ToString.h | 1 + Analysis/src/ToString.cpp | 22 +++++++++++++++++++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/Analysis/include/Luau/ToString.h b/Analysis/include/Luau/ToString.h index 25d89de95a..069bd7a074 100644 --- a/Analysis/include/Luau/ToString.h +++ b/Analysis/include/Luau/ToString.h @@ -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); diff --git a/Analysis/src/ToString.cpp b/Analysis/src/ToString.cpp index fc88b6a945..b7d725e819 100644 --- a/Analysis/src/ToString.cpp +++ b/Analysis/src/ToString.cpp @@ -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" @@ -39,6 +40,7 @@ LUAU_FASTFLAG(LuauIntegerType2) */ LUAU_FASTINTVARIABLE(DebugLuauVerboseTypeNames, 0) LUAU_FASTFLAGVARIABLE(DebugLuauToStringNoLexicalSort) +LUAU_FASTFLAGVARIABLE(DebugLuauToStringTruthyFalsy) namespace Luau { @@ -884,7 +886,7 @@ struct TypeStringifier state.emit("*no-refine*"); } - void operator()(TypeId, const UnionType& uv) + void operator()(TypeId ty, const UnionType& uv) { if (state.hasSeen(&uv)) { @@ -895,6 +897,12 @@ struct TypeStringifier LUAU_ASSERT(uv.options.size() > 1); + if (FFlag::DebugLuauToStringTruthyFalsy && state.opts.useTruthyFalsy && isApproximatelyFalsyType(ty)) + { + state.emit("falsy"); + return; + } + bool optional = false; bool hasNonNilDisjunct = false; @@ -1016,7 +1024,9 @@ struct TypeStringifier std::string saved = std::move(state.result.name); size_t savedSpansSize = state.result.typeSpans.size(); - bool needParens = !state.cycleNames.contains(el) && (get(el) != nullptr || get(el) != nullptr); + // soooo 'falsy' is technically a UnionType :( + bool isNonFalsyUnion = get(el) != nullptr && (FFlag::DebugLuauToStringTruthyFalsy && state.opts.useTruthyFalsy ? !isApproximatelyFalsyType(el) : true); + bool needParens = !state.cycleNames.contains(el) && (isNonFalsyUnion || get(el) != nullptr); if (needParens) state.emit("("); @@ -1115,8 +1125,14 @@ struct TypeStringifier state.emit("never"); } - void operator()(TypeId, const NegationType& ntv) + void operator()(TypeId ty, const NegationType& ntv) { + if (FFlag::DebugLuauToStringTruthyFalsy && state.opts.useTruthyFalsy && isApproximatelyTruthyType(ty)) + { + state.emit("truthy"); + return; + } + state.emit("~"); // The precedence of `~` should be less than `|` and `&`. From e4b2cabc1932a3f5ef833af15eb9b8399881eca1 Mon Sep 17 00:00:00 2001 From: nnullcolumn <264514568+nnullcolumn@users.noreply.github.com> Date: Sun, 12 Jul 2026 18:55:17 +0000 Subject: [PATCH 4/8] tests --- tests/ToString.test.cpp | 66 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/tests/ToString.test.cpp b/tests/ToString.test.cpp index f337b46611..94bda3c0d4 100644 --- a/tests/ToString.test.cpp +++ b/tests/ToString.test.cpp @@ -13,6 +13,7 @@ using namespace Luau; LUAU_FASTFLAG(DebugLuauForceOldSolver) +LUAU_FASTFLAG(DebugLuauToStringTruthyFalsy) TEST_SUITE_BEGIN("ToString"); @@ -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::DebugLuauToStringTruthyFalsy, true}, + }; + + CheckResult result = check(R"( + type function negate(t: type) + return types.negationof(t) + end + type meow = false? + type mrrp = negate + )"); + + 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::DebugLuauToStringTruthyFalsy, 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::DebugLuauToStringTruthyFalsy, 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 + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + + CHECK_EQ(toString(requireTypeAlias("hiss")), "falsy & unknown"); + CHECK_EQ(toString(requireTypeAlias("scratch")), "truthy & unknown"); +} + TEST_SUITE_END(); From 31bf9e1798dbdb2a0b76323970878cfc7416bf79 Mon Sep 17 00:00:00 2001 From: nnullcolumn <264514568+nnullcolumn@users.noreply.github.com> Date: Sun, 12 Jul 2026 19:41:36 +0000 Subject: [PATCH 5/8] fix name --- Analysis/src/ToString.cpp | 8 ++++---- tests/ToString.test.cpp | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Analysis/src/ToString.cpp b/Analysis/src/ToString.cpp index b7d725e819..2b0f3b7c00 100644 --- a/Analysis/src/ToString.cpp +++ b/Analysis/src/ToString.cpp @@ -40,7 +40,7 @@ LUAU_FASTFLAG(LuauIntegerType2) */ LUAU_FASTINTVARIABLE(DebugLuauVerboseTypeNames, 0) LUAU_FASTFLAGVARIABLE(DebugLuauToStringNoLexicalSort) -LUAU_FASTFLAGVARIABLE(DebugLuauToStringTruthyFalsy) +LUAU_FASTFLAGVARIABLE(LuauToStringTruthyFalsy) namespace Luau { @@ -897,7 +897,7 @@ struct TypeStringifier LUAU_ASSERT(uv.options.size() > 1); - if (FFlag::DebugLuauToStringTruthyFalsy && state.opts.useTruthyFalsy && isApproximatelyFalsyType(ty)) + if (FFlag::LuauToStringTruthyFalsy && state.opts.useTruthyFalsy && isApproximatelyFalsyType(ty)) { state.emit("falsy"); return; @@ -1025,7 +1025,7 @@ struct TypeStringifier size_t savedSpansSize = state.result.typeSpans.size(); // soooo 'falsy' is technically a UnionType :( - bool isNonFalsyUnion = get(el) != nullptr && (FFlag::DebugLuauToStringTruthyFalsy && state.opts.useTruthyFalsy ? !isApproximatelyFalsyType(el) : true); + bool isNonFalsyUnion = get(el) != nullptr && (FFlag::LuauToStringTruthyFalsy && state.opts.useTruthyFalsy ? !isApproximatelyFalsyType(el) : true); bool needParens = !state.cycleNames.contains(el) && (isNonFalsyUnion || get(el) != nullptr); if (needParens) @@ -1127,7 +1127,7 @@ struct TypeStringifier void operator()(TypeId ty, const NegationType& ntv) { - if (FFlag::DebugLuauToStringTruthyFalsy && state.opts.useTruthyFalsy && isApproximatelyTruthyType(ty)) + if (FFlag::LuauToStringTruthyFalsy && state.opts.useTruthyFalsy && isApproximatelyTruthyType(ty)) { state.emit("truthy"); return; diff --git a/tests/ToString.test.cpp b/tests/ToString.test.cpp index 94bda3c0d4..5dfdc557b3 100644 --- a/tests/ToString.test.cpp +++ b/tests/ToString.test.cpp @@ -13,7 +13,7 @@ using namespace Luau; LUAU_FASTFLAG(DebugLuauForceOldSolver) -LUAU_FASTFLAG(DebugLuauToStringTruthyFalsy) +LUAU_FASTFLAG(LuauToStringTruthyFalsy) TEST_SUITE_BEGIN("ToString"); @@ -1067,7 +1067,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tostring_truthy_falsy") { ScopedFastFlag sffs[] = { {FFlag::DebugLuauForceOldSolver, false}, - {FFlag::DebugLuauToStringTruthyFalsy, true}, + {FFlag::LuauToStringTruthyFalsy, true}, }; CheckResult result = check(R"( @@ -1088,7 +1088,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tostring_truthy_falsy_expanded") { ScopedFastFlag sffs[] = { {FFlag::DebugLuauForceOldSolver, false}, - {FFlag::DebugLuauToStringTruthyFalsy, true}, + {FFlag::LuauToStringTruthyFalsy, true}, }; CheckResult result = check(R"( @@ -1109,7 +1109,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tostring_truthy_falsy_no_parenthesis") { ScopedFastFlag sffs[] = { {FFlag::DebugLuauForceOldSolver, false}, - {FFlag::DebugLuauToStringTruthyFalsy, true}, + {FFlag::LuauToStringTruthyFalsy, true}, }; ToStringOptions opts; From a46b682def6fad1388bee1a7a8d72da85eab25dd Mon Sep 17 00:00:00 2001 From: nnullcolumn <264514568+nnullcolumn@users.noreply.github.com> Date: Sun, 12 Jul 2026 21:38:54 +0000 Subject: [PATCH 6/8] fix cycle bug --- Analysis/src/ToString.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Analysis/src/ToString.cpp b/Analysis/src/ToString.cpp index 2b0f3b7c00..cb74956d69 100644 --- a/Analysis/src/ToString.cpp +++ b/Analysis/src/ToString.cpp @@ -888,6 +888,12 @@ struct TypeStringifier 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; @@ -897,12 +903,6 @@ struct TypeStringifier LUAU_ASSERT(uv.options.size() > 1); - if (FFlag::LuauToStringTruthyFalsy && state.opts.useTruthyFalsy && isApproximatelyFalsyType(ty)) - { - state.emit("falsy"); - return; - } - bool optional = false; bool hasNonNilDisjunct = false; From d4954d623ce53e24a760338b21fe368eb3d73694 Mon Sep 17 00:00:00 2001 From: nnullcolumn <264514568+nnullcolumn@users.noreply.github.com> Date: Sun, 12 Jul 2026 21:40:26 +0000 Subject: [PATCH 7/8] update tests w/ fflag (!!AI COMMIT!!) --- tests/Normalize.test.cpp | 6 ++++-- tests/Simplify.test.cpp | 4 +++- tests/TypeInfer.functions.test.cpp | 4 +++- tests/TypeInfer.intersectionTypes.test.cpp | 8 ++++++-- tests/TypeInfer.operators.test.cpp | 13 ++++++++++--- tests/TypeInfer.refinements.test.cpp | 14 ++++++++++---- tests/TypeInfer.tables.test.cpp | 9 ++++++--- 7 files changed, 42 insertions(+), 16 deletions(-) diff --git a/tests/Normalize.test.cpp b/tests/Normalize.test.cpp index 9394028a79..f165548c3b 100644 --- a/tests/Normalize.test.cpp +++ b/tests/Normalize.test.cpp @@ -14,6 +14,7 @@ LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTFLAG(LuauIntegerType2) LUAU_FASTFLAG(DebugLuauForceOldSolver) +LUAU_FASTFLAG(LuauToStringTruthyFalsy) using namespace Luau; @@ -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()); @@ -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") diff --git a/tests/Simplify.test.cpp b/tests/Simplify.test.cpp index 458fab4430..49d13d1c98 100644 --- a/tests/Simplify.test.cpp +++ b/tests/Simplify.test.cpp @@ -9,6 +9,7 @@ using namespace Luau; LUAU_FASTFLAG(DebugLuauForceOldSolver) +LUAU_FASTFLAG(LuauToStringTruthyFalsy) LUAU_DYNAMIC_FASTINT(LuauSimplificationComplexityLimit) namespace @@ -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") diff --git a/tests/TypeInfer.functions.test.cpp b/tests/TypeInfer.functions.test.cpp index e3f1f05fd7..7fc3129cde 100644 --- a/tests/TypeInfer.functions.test.cpp +++ b/tests/TypeInfer.functions.test.cpp @@ -25,6 +25,7 @@ LUAU_FASTFLAG(LuauBidirectionalInferenceVariadics) LUAU_FASTFLAG(LuauConstraintGraph) LUAU_FASTFLAG(LuauBidirectionalInferenceBetterLambdaHandling) LUAU_FASTFLAG(LuauHigherOrderGenericInference) +LUAU_FASTFLAG(LuauToStringTruthyFalsy) TEST_SUITE_BEGIN("TypeInferFunctions"); @@ -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"( @@ -2922,7 +2924,7 @@ TEST_CASE_FIXTURE(Fixture, "unifier_should_not_bind_free_types") auto tm2 = get(result.errors[1]); REQUIRE(tm2); CHECK(toString(tm2->wantedType) == "string"); - CHECK(toString(tm2->givenType) == "unknown & ~(false?)"); + CHECK(toString(tm2->givenType) == "truthy & unknown"); } } diff --git a/tests/TypeInfer.intersectionTypes.test.cpp b/tests/TypeInfer.intersectionTypes.test.cpp index 607e9944a9..3daf18446e 100644 --- a/tests/TypeInfer.intersectionTypes.test.cpp +++ b/tests/TypeInfer.intersectionTypes.test.cpp @@ -13,6 +13,7 @@ LUAU_FASTFLAG(LuauCheckFunctionStatementTypes) LUAU_FASTFLAG(DebugLuauForceOldSolver) LUAU_FASTFLAG(LuauPropagateFreeTypesIntoUnionAndIntersectionBounds) LUAU_FASTFLAG(LuauDropUnionSubtypeReasoning) +LUAU_FASTFLAG(LuauToStringTruthyFalsy) TEST_SUITE_BEGIN("IntersectionTypes"); @@ -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 @@ -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") diff --git a/tests/TypeInfer.operators.test.cpp b/tests/TypeInfer.operators.test.cpp index b3d288ad3c..e2ffbb70aa 100644 --- a/tests/TypeInfer.operators.test.cpp +++ b/tests/TypeInfer.operators.test.cpp @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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"))); diff --git a/tests/TypeInfer.refinements.test.cpp b/tests/TypeInfer.refinements.test.cpp index b65e40b2a0..202aae3e50 100644 --- a/tests/TypeInfer.refinements.test.cpp +++ b/tests/TypeInfer.refinements.test.cpp @@ -12,6 +12,7 @@ LUAU_FASTFLAG(DebugLuauForceOldSolver) LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint) LUAU_FASTFLAG(LuauRemovePrimitiveTypeConstraintAndSubtypingUnifier) LUAU_FASTFLAG(LuauIndexingIntoErrorGivesError); +LUAU_FASTFLAG(LuauToStringTruthyFalsy) using namespace Luau; @@ -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 @@ -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}))); @@ -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 @@ -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}))); @@ -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(item: T?, printer: (T) -> string): string if item then @@ -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}))); } @@ -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"( @@ -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") diff --git a/tests/TypeInfer.tables.test.cpp b/tests/TypeInfer.tables.test.cpp index b9e79a5eb4..621c036945 100644 --- a/tests/TypeInfer.tables.test.cpp +++ b/tests/TypeInfer.tables.test.cpp @@ -29,6 +29,7 @@ LUAU_FASTFLAG(LuauPropertyModifierMismatchErrors) LUAU_FASTFLAG(LuauReadOnlyIndexers) LUAU_FASTFLAG(LuauRemoveConstraintSolverEmplace) LUAU_FASTFLAG(LuauRemovePrimitiveTypeConstraintAndSubtypingUnifier) +LUAU_FASTFLAG(LuauToStringTruthyFalsy) TEST_SUITE_BEGIN("TableTests"); @@ -5263,8 +5264,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "subtyping_with_a_metatable_table_path") TEST_CASE_FIXTURE(BuiltinsFixture, "metatable_union_type") { - - ScopedFastFlag _{FFlag::DebugLuauForceOldSolver, false}; + ScopedFastFlag sffs[] = { + {FFlag::DebugLuauForceOldSolver, false}, + {FFlag::LuauToStringTruthyFalsy, true}, + }; // This will have one (legitimate) error but previously would crash. auto result = check(R"( @@ -5281,7 +5284,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "metatable_union_type") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); CHECK_EQ( - "Cannot add indexer to table '{ @metatable t1, (nil & ~(false?)) | { } } where t1 = { new: (a) -> { @metatable t1, (a & ~(false?)) | { " + "Cannot add indexer to table '{ @metatable t1, (nil & truthy) | { } } where t1 = { new: (a) -> { @metatable t1, (a & truthy) | { " "} } }'", toString(result.errors[0]) ); From 2818d7e64b4910fb90a45c00be1a64ba7fb9e85d Mon Sep 17 00:00:00 2001 From: nnullcolumn <264514568+nnullcolumn@users.noreply.github.com> Date: Tue, 14 Jul 2026 02:24:32 +0000 Subject: [PATCH 8/8] run clang-format --- Analysis/src/ToString.cpp | 3 ++- tests/ToString.test.cpp | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/Analysis/src/ToString.cpp b/Analysis/src/ToString.cpp index cb74956d69..5cb98fa3e8 100644 --- a/Analysis/src/ToString.cpp +++ b/Analysis/src/ToString.cpp @@ -1025,7 +1025,8 @@ struct TypeStringifier size_t savedSpansSize = state.result.typeSpans.size(); // soooo 'falsy' is technically a UnionType :( - bool isNonFalsyUnion = get(el) != nullptr && (FFlag::LuauToStringTruthyFalsy && state.opts.useTruthyFalsy ? !isApproximatelyFalsyType(el) : true); + bool isNonFalsyUnion = + get(el) != nullptr && (FFlag::LuauToStringTruthyFalsy && state.opts.useTruthyFalsy ? !isApproximatelyFalsyType(el) : true); bool needParens = !state.cycleNames.contains(el) && (isNonFalsyUnion || get(el) != nullptr); if (needParens) diff --git a/tests/ToString.test.cpp b/tests/ToString.test.cpp index 5dfdc557b3..5870d11dae 100644 --- a/tests/ToString.test.cpp +++ b/tests/ToString.test.cpp @@ -1086,7 +1086,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tostring_truthy_falsy") TEST_CASE_FIXTURE(BuiltinsFixture, "tostring_truthy_falsy_expanded") { - ScopedFastFlag sffs[] = { + ScopedFastFlag sffs[] = { {FFlag::DebugLuauForceOldSolver, false}, {FFlag::LuauToStringTruthyFalsy, true}, }; @@ -1107,14 +1107,14 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tostring_truthy_falsy_expanded") TEST_CASE_FIXTURE(BuiltinsFixture, "tostring_truthy_falsy_no_parenthesis") { - ScopedFastFlag sffs[] = { - {FFlag::DebugLuauForceOldSolver, false}, - {FFlag::LuauToStringTruthyFalsy, true}, - }; + ScopedFastFlag sffs[] = { + {FFlag::DebugLuauForceOldSolver, false}, + {FFlag::LuauToStringTruthyFalsy, true}, + }; - ToStringOptions opts; + ToStringOptions opts; - CheckResult result = check(R"( + CheckResult result = check(R"( type function negate(t: type) return types.negationof(t) end @@ -1122,10 +1122,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tostring_truthy_falsy_no_parenthesis") type scratch = unknown & negate )"); - LUAU_REQUIRE_NO_ERRORS(result); + LUAU_REQUIRE_NO_ERRORS(result); - CHECK_EQ(toString(requireTypeAlias("hiss")), "falsy & unknown"); - CHECK_EQ(toString(requireTypeAlias("scratch")), "truthy & unknown"); + CHECK_EQ(toString(requireTypeAlias("hiss")), "falsy & unknown"); + CHECK_EQ(toString(requireTypeAlias("scratch")), "truthy & unknown"); } TEST_SUITE_END();