diff --git a/Analysis/include/Luau/ToString.h b/Analysis/include/Luau/ToString.h index efe82b12..a256e12f 100644 --- a/Analysis/include/Luau/ToString.h +++ b/Analysis/include/Luau/ToString.h @@ -11,6 +11,7 @@ LUAU_FASTINT(LuauTableTypeMaximumStringifierLength) LUAU_FASTINT(LuauTypeMaximumStringifierLength) +LUAU_FASTFLAG(LuauToStringSimpleCompositeTypesSingleLine) namespace Luau { @@ -39,6 +40,12 @@ struct ToStringNameMap struct ToStringOptions { + ToStringOptions(bool exhaustive = false) + : exhaustive(exhaustive) + , compositeTypesSingleLineLimit(FFlag::LuauToStringSimpleCompositeTypesSingleLine ? 5 : 0) + { + } + bool exhaustive = false; // If true, we produce complete output rather than comprehensible output bool useLineBreaks = false; // If true, we insert new lines to separate long results such as table entries/metatable. bool functionTypeArguments = false; // If true, output function type argument names when they are available @@ -47,6 +54,7 @@ struct ToStringOptions bool hideFunctionSelfArgument = false; // If true, `self: X` will be omitted from the function signature if the function has self size_t maxTableLength = size_t(FInt::LuauTableTypeMaximumStringifierLength); // Only applied to TableTypes size_t maxTypeLength = size_t(FInt::LuauTypeMaximumStringifierLength); + size_t compositeTypesSingleLineLimit = 5; // The number of type elements permitted on a single line when printing type unions/intersections ToStringNameMap nameMap; std::shared_ptr scope; // If present, module names will be added and types that are not available in scope will be marked as 'invalid' std::vector namedFunctionOverrideArgNames; // If present, named function argument names will be overridden diff --git a/Analysis/src/ToString.cpp b/Analysis/src/ToString.cpp index 10fe440e..8123e09f 100644 --- a/Analysis/src/ToString.cpp +++ b/Analysis/src/ToString.cpp @@ -17,6 +17,7 @@ LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution) LUAU_FASTFLAGVARIABLE(LuauToStringPrettifyLocation, false) +LUAU_FASTFLAGVARIABLE(LuauToStringSimpleCompositeTypesSingleLine, false) /* * Enables increasing levels of verbosity for Luau type names when stringifying. @@ -878,11 +879,15 @@ struct TypeStringifier state.emit("("); bool first = true; + bool shouldPlaceOnNewlines = results.size() > state.opts.compositeTypesSingleLineLimit; for (std::string& ss : results) { if (!first) { - state.newline(); + if (shouldPlaceOnNewlines) + state.newline(); + else + state.emit(" "); state.emit("| "); } state.emit(ss); @@ -902,7 +907,7 @@ struct TypeStringifier } } - void operator()(TypeId, const IntersectionType& uv) + void operator()(TypeId ty, const IntersectionType& uv) { if (state.hasSeen(&uv)) { @@ -938,11 +943,15 @@ struct TypeStringifier std::sort(results.begin(), results.end()); bool first = true; + bool shouldPlaceOnNewlines = results.size() > state.opts.compositeTypesSingleLineLimit || isOverloadedFunction(ty); for (std::string& ss : results) { if (!first) { - state.newline(); + if (shouldPlaceOnNewlines) + state.newline(); + else + state.emit(" "); state.emit("& "); } state.emit(ss); diff --git a/tests/ToString.test.cpp b/tests/ToString.test.cpp index 06fc1b8f..32e10709 100644 --- a/tests/ToString.test.cpp +++ b/tests/ToString.test.cpp @@ -234,8 +234,41 @@ TEST_CASE_FIXTURE(Fixture, "functions_are_always_parenthesized_in_unions_or_inte CHECK_EQ(toString(&itv), "((number, string) -> (string, number)) & ((string, number) -> (number, string))"); } -TEST_CASE_FIXTURE(Fixture, "intersections_respects_use_line_breaks") +TEST_CASE_FIXTURE(Fixture, "simple_intersections_printed_on_one_line") { + ScopedFastFlag sff{"LuauToStringSimpleCompositeTypesSingleLine", true}; + CheckResult result = check(R"( + local a: string & number + )"); + + ToStringOptions opts; + opts.useLineBreaks = true; + + CHECK_EQ("number & string", toString(requireType("a"), opts)); +} + +TEST_CASE_FIXTURE(Fixture, "complex_intersections_printed_on_multiple_lines") +{ + ScopedFastFlag sff{"LuauToStringSimpleCompositeTypesSingleLine", true}; + CheckResult result = check(R"( + local a: string & number & boolean + )"); + + ToStringOptions opts; + opts.useLineBreaks = true; + opts.compositeTypesSingleLineLimit = 2; + + //clang-format off + CHECK_EQ("boolean\n" + "& number\n" + "& string", + toString(requireType("a"), opts)); + //clang-format on +} + +TEST_CASE_FIXTURE(Fixture, "overloaded_functions_always_printed_on_multiple_lines") +{ + ScopedFastFlag sff{"LuauToStringSimpleCompositeTypesSingleLine", true}; CheckResult result = check(R"( local a: ((string) -> string) & ((number) -> number) )"); @@ -250,13 +283,28 @@ TEST_CASE_FIXTURE(Fixture, "intersections_respects_use_line_breaks") //clang-format on } -TEST_CASE_FIXTURE(Fixture, "unions_respects_use_line_breaks") +TEST_CASE_FIXTURE(Fixture, "simple_unions_printed_on_one_line") { + ScopedFastFlag sff{"LuauToStringSimpleCompositeTypesSingleLine", true}; + CheckResult result = check(R"( + local a: number | boolean + )"); + + ToStringOptions opts; + opts.useLineBreaks = true; + + CHECK_EQ("boolean | number", toString(requireType("a"), opts)); +} + +TEST_CASE_FIXTURE(Fixture, "complex_unions_printed_on_multiple_lines") +{ + ScopedFastFlag sff{"LuauToStringSimpleCompositeTypesSingleLine", true}; CheckResult result = check(R"( local a: string | number | boolean )"); ToStringOptions opts; + opts.compositeTypesSingleLineLimit = 2; opts.useLineBreaks = true; //clang-format off