Skip to content

Commit 4445949

Browse files
committed
Modify std.format to recognize output range using toString
1 parent f318982 commit 4445949

File tree

1 file changed

+129
-10
lines changed

1 file changed

+129
-10
lines changed

std/format.d

Lines changed: 129 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1735,10 +1735,22 @@ FormatSpec!Char singleSpec(Char)(Char[] fmt)
17351735
* `toString` should have one of the following signatures:
17361736
*
17371737
* ---
1738-
* const void toString(scope void delegate(const(char)[]) sink, FormatSpec fmt);
1739-
* const void toString(scope void delegate(const(char)[]) sink, string fmt);
1740-
* const void toString(scope void delegate(const(char)[]) sink);
1741-
* const string toString();
1738+
* void toString(W)(ref W w, const ref FormatSpec fmt)
1739+
* void toString(W)(ref W w)
1740+
* string toString();
1741+
* ---
1742+
*
1743+
* Where `W` is an $(REF_ALTTEXT output range, isOutputRange, std,_range,primitives)
1744+
* which accepts characters. The template type does not have to be called `W`.
1745+
*
1746+
* The following overloads are also accepted for legacy reasons or for use in virtual
1747+
* functions. It's recommended that any new code forgo these overloads if possible for
1748+
* speed and attribute acceptance reasons.
1749+
*
1750+
* ---
1751+
* void toString(scope void delegate(const(char)[]) sink, const ref FormatSpec fmt);
1752+
* void toString(scope void delegate(const(char)[]) sink, string fmt);
1753+
* void toString(scope void delegate(const(char)[]) sink);
17421754
* ---
17431755
*
17441756
* For the class objects which have input range interface,
@@ -1917,13 +1929,53 @@ void formatValue(Writer, T, Char)(auto ref Writer w, auto ref T val, const ref F
19171929
}
19181930

19191931
/**
1920-
* Formatting of a `struct` with a defined `toString`.
1932+
* Formatting a struct by defining a method `toString`, which takes an output
1933+
* range.
1934+
*
1935+
* It's recommended that any `toString` using $(REF_ALTTEXT output _rangew, isOutputRange, std,_range,primitives)
1936+
* use $(REF put, std,_range,primitives) rather than use the `put` method of the range
1937+
* directly.
1938+
*/
1939+
@safe unittest
1940+
{
1941+
import std.array : appender;
1942+
1943+
static struct Point
1944+
{
1945+
int x, y;
1946+
1947+
void toString(W)(ref W writer, const ref FormatSpec!char f) if (isOutputRange!(W, char))
1948+
{
1949+
// make sure to use std.range.primitives.put and not writer.put directly
1950+
// see put's docs for more details
1951+
put(writer, "(");
1952+
formatValue(writer, x, f);
1953+
put(writer, ",");
1954+
formatValue(writer, y, f);
1955+
put(writer, ")");
1956+
}
1957+
}
1958+
1959+
auto w = appender!string();
1960+
auto spec = singleSpec("%s");
1961+
auto p = Point(16, 11);
1962+
1963+
formatValue(w, p, spec);
1964+
assert(w.data == "(16,11)");
1965+
}
1966+
1967+
/**
1968+
* Another example of formatting a `struct` with a defined `toString`,
1969+
* this time using the `scope delegate` method.
19211970
*
1922-
* `formatValue` also allows to reuse existing format specifiers:
1971+
* This method is now discouraged. Please use the output range method
1972+
* instead.
1973+
*
1974+
* `formatValue` also allows to reuse existing format specifiers:
19231975
*/
19241976
@safe unittest
19251977
{
1926-
struct Point
1978+
static struct Point
19271979
{
19281980
int x, y;
19291981

@@ -3455,13 +3507,30 @@ if (is(AssocArrayTypeOf!T) && !is(T == enum) && !hasToString!(T, Char))
34553507
formatTest(e2, "[A, B, C]");
34563508
}
34573509

3458-
template hasToString(T, Char)
3510+
private template hasToString(T, Char)
34593511
{
34603512
static if (isPointer!T && !isAggregateType!T)
34613513
{
34623514
// X* does not have toString, even if X is aggregate type has toString.
34633515
enum hasToString = 0;
34643516
}
3517+
else static if (is(typeof(
3518+
{T val = void; FormatSpec!Char f; static struct S {void put(Char s){}}
3519+
S s; val.toString(s, f);
3520+
// force toString to take output range by ref
3521+
static assert(ParameterStorageClassTuple!(T.toString!(S))[0] == ParameterStorageClass.ref_);}
3522+
)))
3523+
{
3524+
enum hasToString = 6;
3525+
}
3526+
else static if (is(typeof(
3527+
{T val = void; static struct S {void put(Char s){}}
3528+
S s; val.toString(s);
3529+
static assert(ParameterStorageClassTuple!(T.toString!(S))[0] == ParameterStorageClass.ref_);}
3530+
)))
3531+
{
3532+
enum hasToString = 5;
3533+
}
34653534
else static if (is(typeof({ T val = void; FormatSpec!Char f; val.toString((const(char)[] s){}, f); })))
34663535
{
34673536
enum hasToString = 4;
@@ -3484,11 +3553,59 @@ template hasToString(T, Char)
34843553
}
34853554
}
34863555

3556+
@safe unittest
3557+
{
3558+
static struct A
3559+
{
3560+
void toString(Writer)(ref Writer w) if (isOutputRange!(Writer, string)) {}
3561+
}
3562+
static struct B
3563+
{
3564+
void toString(scope void delegate(const(char)[]) sink, FormatSpec!char fmt) {}
3565+
}
3566+
static struct C
3567+
{
3568+
void toString(scope void delegate(const(char)[]) sink, string fmt) {}
3569+
}
3570+
static struct D
3571+
{
3572+
void toString(scope void delegate(const(char)[]) sink) {}
3573+
}
3574+
static struct E
3575+
{
3576+
string toString() {return "";}
3577+
}
3578+
static struct F
3579+
{
3580+
void toString(Writer)(ref Writer w, const ref FormatSpec!char fmt) if (isOutputRange!(Writer, string)) {}
3581+
}
3582+
3583+
static assert(hasToString!(A, char) == 5);
3584+
static assert(hasToString!(B, char) == 4);
3585+
static assert(hasToString!(C, char) == 3);
3586+
static assert(hasToString!(D, char) == 2);
3587+
static assert(hasToString!(E, char) == 1);
3588+
static assert(hasToString!(F, char) == 6);
3589+
}
3590+
34873591
// object formatting with toString
34883592
private void formatObject(Writer, T, Char)(ref Writer w, ref T val, const ref FormatSpec!Char f)
34893593
if (hasToString!(T, Char))
34903594
{
3491-
static if (is(typeof(val.toString((const(char)[] s){}, f))))
3595+
enum overload = hasToString!(T, Char);
3596+
3597+
static if (overload == 6)
3598+
{
3599+
val.toString(w, f);
3600+
}
3601+
else static if (overload == 5)
3602+
{
3603+
val.toString(w);
3604+
}
3605+
// not using the overload enum to not break badly defined toString overloads
3606+
// e.g. defining the FormatSpec as ref and not const ref led this function
3607+
// to ignore that toString overload
3608+
else static if (is(typeof(val.toString((const(char)[] s){}, f))))
34923609
{
34933610
val.toString((const(char)[] s) { put(w, s); }, f);
34943611
}
@@ -3505,12 +3622,14 @@ if (hasToString!(T, Char))
35053622
put(w, val.toString());
35063623
}
35073624
else
3625+
{
35083626
static assert(0);
3627+
}
35093628
}
35103629

35113630
void enforceValidFormatSpec(T, Char)(const ref FormatSpec!Char f)
35123631
{
3513-
static if (!isInputRange!T && hasToString!(T, Char) != 4)
3632+
static if (!isInputRange!T && hasToString!(T, Char) < 4)
35143633
{
35153634
enforceFmt(f.spec == 's',
35163635
"Expected '%s' format specifier for type '" ~ T.stringof ~ "'");

0 commit comments

Comments
 (0)