Repository: luau-lang/luau (C++)

Issue:
Revert the use of line breaks when tostringing Union types
A while ago, I submitted https://github.com/Roblox/luau/pull/487 which would stringify union / interesection types onto multiple lines. The motivating example was a long list of function overload intersections, where this type of formatting would be useful.

In practice though, the union types aren't typically too long, and this formatting "looks off" in many cases.

<details>
<summary>Some examples:</summary>

![image](https://github.com/Roblox/luau/assets/19635171/22c86872-4781-4103-8734-f2bd34450b81)
![image](https://github.com/Roblox/luau/assets/19635171/9829f31d-3a0a-4a42-8636-e37e3add7ed6)
![image](https://github.com/Roblox/luau/assets/19635171/f728bf57-50fa-4359-9157-ecbd7d57c46b)

</details>

I'm thinking that it may be best to revert this change. Either
1) Do not stringify union types on multiple lines at all
2) Only stringify on multiple lines if there are more than X types in the union (e.g. X = 5?)

Maybe this should be extended to intersections as well? Only format intersections if it contains function types, i.e. the original motivating example?

Of course, this opinion is subjective, so I opened this issue to gather thoughts


Discussion: Yeah it feels like we should just have some cutoff. Two-element unions probably should not be broken up. Alternatively we can restrict this to cases where all union elements are function types.
A good heuristic might be to break lines if any of the union options are tables or functions.



Fix (patch):
diff --git a/Analysis/include/Luau/ToString.h b/Analysis/include/Luau/ToString.h
index efe82b124..a256e12f2 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> scope; // If present, module names will be added and types that are not available in scope will be marked as 'invalid'
     std::vector<std::string> namedFunctionOverrideArgNames; // If present, named function argument names will be overridden
diff --git a/Analysis/src/ToString.cpp b/Analysis/src/ToString.cpp
index 96492a397..67350438f 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.
@@ -851,11 +852,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 +880,7 @@ struct TypeStringifier
         }
     }
 
-    void operator()(TypeId, const IntersectionType& uv)
+    void operator()(TypeId ty, const IntersectionType& uv)
     {
         if (state.hasSeen(&uv))
         {
@@ -911,11 +916,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);