diff --git a/Analysis/include/Luau/ToString.h b/Analysis/include/Luau/ToString.h index efe82b12..b0ab0cd5 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 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 96492a39..2f0f38e1 100644 --- a/Analysis/src/ToString.cpp +++ b/Analysis/src/ToString.cpp @@ -851,11 +851,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); @@ -875,7 +879,7 @@ struct TypeStringifier } } - void operator()(TypeId, const IntersectionType& uv) + void operator()(TypeId ty, const IntersectionType& uv) { if (state.hasSeen(&uv)) { @@ -911,11 +915,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 cb1576f8..25c09092 100644 --- a/tests/ToString.test.cpp +++ b/tests/ToString.test.cpp @@ -214,7 +214,37 @@ 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") +{ + 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") +{ + 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") { CheckResult result = check(R"( local a: ((string) -> string) & ((number) -> number) @@ -230,13 +260,26 @@ 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") +{ + 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") { CheckResult result = check(R"( local a: string | number | boolean )"); ToStringOptions opts; + opts.compositeTypesSingleLineLimit = 2; opts.useLineBreaks = true; //clang-format off