www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.bugs - [Issue 10487] New: Bad error message with assignment of const tuple

reply d-bugmail puremagic.com writes:
http://d.puremagic.com/issues/show_bug.cgi?id=10487

           Summary: Bad error message with assignment of const tuple
           Product: D
           Version: D2
          Platform: All
        OS/Version: All
            Status: NEW
          Keywords: diagnostic
          Severity: enhancement
          Priority: P2
         Component: DMD
        AssignedTo: nobody puremagic.com
        ReportedBy: bearophile_hugs eml.cc



I tag this as enhancement request, but it's borderline with being a bug report.

This is a common programming mistake in D; using "auto" y becomes "const int"
and not just an int, so later you can't assign to it the result of bar():


int bar() {
    return 0;
}
void spam(in int x) {
    auto y = x;
    // ...
    y = bar();
}
void main() {}


The error message is quite clear and easy to understand:

test.d(7): Error: cannot modify const expression y



But if I replace the type int with a Tuple:

import std.typecons: Tuple;
alias Foo = Tuple!int;
Foo bar() {
    return Foo();
}
void spam(in Foo x) {
    auto y = x;
    // ...
    y = bar();
}
void main() {}


DMD 2.064alpha gives me:

test.d(9): Error: template std.typecons.Tuple!int.Tuple.opAssign does not match
any function template declaration. Candidates are:
...\dmd2\src\phobos\std\typecons.d(504):       
std.typecons.Tuple!int.Tuple.opAssign(R)(auto ref R rhs) if
(areCompatibleTuples!(typeof(this), R, "="))
test.d(9): Error: template std.typecons.Tuple!int.Tuple.opAssign(R)(auto ref R
rhs) if (areCompatibleTuples!(typeof(this), R, "=")) cannot deduce template
function from argument types !()(Tuple!int)


The error messages are too much long, and they don't even talk about const
tuples.

I think those error messages should be improved.

I expect future built-in D tuples to give an error message like the int case:

test.d(7): Error: cannot modify const expression y

-- 
Configure issuemail: http://d.puremagic.com/issues/userprefs.cgi?tab=email
------- You are receiving this mail because: -------
Jun 27 2013
next sibling parent d-bugmail puremagic.com writes:
http://d.puremagic.com/issues/show_bug.cgi?id=10487


Andrej Mitrovic <andrej.mitrovich gmail.com> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
                 CC|                            |andrej.mitrovich gmail.com



06:40:57 PDT ---
Hmm, this is really quite strange. If we take a simple example:

-----
import std.typecons;

alias Foo = Tuple!int;

void main()
{
    const(Foo) f;
    f = f;
}
-----

We get the big template instance error you've posted.

But if we copy-paste the Tuple implementation inline into the current module:

-----
import std.array;
import std.typetuple;
import std.string;
import std.format;

private template Identity(alias T)
{
    alias T Identity;
}

template Tuple(Specs...)
{
    // Parse (type,name) pairs (FieldSpecs) out of the specified
    // arguments. Some fields would have name, others not.
    template parseSpecs(Specs...)
    {
        static if (Specs.length == 0)
        {
            alias TypeTuple!() parseSpecs;
        }
        else static if (is(Specs[0]))
        {
            static if (is(typeof(Specs[1]) : string))
            {
                alias TypeTuple!(FieldSpec!(Specs[0 .. 2]),
                                 parseSpecs!(Specs[2 .. $])) parseSpecs;
            }
            else
            {
                alias TypeTuple!(FieldSpec!(Specs[0]),
                                 parseSpecs!(Specs[1 .. $])) parseSpecs;
            }
        }
        else
        {
            static assert(0, "Attempted to instantiate Tuple with an "
                            ~"invalid argument: "~ Specs[0].stringof);
        }
    }

    template FieldSpec(T, string s = "")
    {
        alias T Type;
        alias s name;
    }

    alias parseSpecs!Specs fieldSpecs;

    // Used with staticMap.
    template extractType(alias spec) { alias spec.Type extractType; }
    template extractName(alias spec) { alias spec.name extractName; }

    // Generates named fields as follows:
    //    alias Identity!(field[0]) name_0;
    //    alias Identity!(field[1]) name_1;
    //      :
    // NOTE: field[k] is an expression (which yields a symbol of a
    //       variable) and can't be aliased directly.
    string injectNamedFields()
    {
        string decl = "";
        foreach (i, name; staticMap!(extractName, fieldSpecs))
        {
            decl ~= format("alias Identity!(field[%s]) _%s;", i, i);
            if (name.length != 0)
            {
                decl ~= format("alias _%s %s;", i, name);
            }
        }
        return decl;
    }

    // Returns Specs for a subtuple this[from .. to] preserving field
    // names if any.
    template sliceSpecs(size_t from, size_t to)
    {
        alias staticMap!(expandSpec,
                         fieldSpecs[from .. to]) sliceSpecs;
    }

    template expandSpec(alias spec)
    {
        static if (spec.name.length == 0)
        {
            alias TypeTuple!(spec.Type) expandSpec;
        }
        else
        {
            alias TypeTuple!(spec.Type, spec.name) expandSpec;
        }
    }

    template areCompatibleTuples(Tup1, Tup2, string op)
    {
        enum areCompatibleTuples = isTuple!Tup2 && is(typeof(
        {
            Tup1 tup1 = void;
            Tup2 tup2 = void;
            static assert(tup1.field.length == tup2.field.length);
            foreach (i, _; Tup1.Types)
            {
                auto lhs = typeof(tup1.field[i]).init;
                auto rhs = typeof(tup2.field[i]).init;
                auto result = mixin("lhs "~op~" rhs");
            }
        }));
    }

    struct Tuple
    {
        /**
         * The type of the tuple's components.
         */
        alias staticMap!(extractType, fieldSpecs) Types;

        /**
         * Use $(D t.expand) for a tuple $(D t) to expand it into its
         * components. The result of $(D expand) acts as if the tuple
components
         * were listed as a list of values. (Ordinarily, a $(D Tuple) acts as a
         * single value.)
         *
         * Examples:
         * ----
         * auto t = tuple(1, " hello ", 2.3);
         * writeln(t);        // Tuple!(int, string, double)(1, " hello ", 2.3)
         * writeln(t.expand); // 1 hello 2.3
         * ----
         */
        Types expand;
        mixin(injectNamedFields());

        static if (is(Specs))
        {
            // This is mostly to make t[n] work.
            alias expand this;
        }
        else
        {
             property
            ref Tuple!Types _Tuple_super()  trusted
            {
                foreach (i, _; Types)   // Rely on the field layout
                {
                    static assert(typeof(return).init.tupleof[i].offsetof ==
                                                       expand[i].offsetof);
                }
                return *cast(typeof(return)*) &(field[0]);
            }
            // This is mostly to make t[n] work.
            alias _Tuple_super this;
        }

        // backwards compatibility
        alias field = expand;

        /**
         * Constructor taking one value for each field. Each argument must be
         * implicitly assignable to the respective element of the target.
         */
        this()(Types values)
        {
            field[] = values[];
        }

        /**
         * Constructor taking a compatible array. The array element type must
         * be implicitly assignable to each element of the target.
         *
         * Examples:
         * ----
         * int[2] ints;
         * Tuple!(int, int) t = ints;
         * ----
         */
        this(U, size_t n)(U[n] values)
        if (n == Types.length &&
            is(typeof({ foreach (i, _; Types) field[i] = values[i]; })))
        {
            foreach (i, _; Types)
            {
                field[i] = values[i];
            }
        }

        /**
         * Constructor taking a compatible tuple. Each element of the source
         * must be implicitly assignable to the respective element of the
         * target.
         */
        this(U)(U another)
        if (areCompatibleTuples!(typeof(this), U, "="))
        {
            field[] = another.field[];
        }

        /**
         * Comparison for equality.
         */
        bool opEquals(R)(R rhs)
        if (areCompatibleTuples!(typeof(this), R, "=="))
        {
            return field[] == rhs.field[];
        }
        /// ditto
        bool opEquals(R)(R rhs) const
        if (areCompatibleTuples!(typeof(this), R, "=="))
        {
            return field[] == rhs.field[];
        }

        /**
         * Comparison for ordering.
         */
        int opCmp(R)(R rhs)
        if (areCompatibleTuples!(typeof(this), R, "<"))
        {
            foreach (i, Unused; Types)
            {
                if (field[i] != rhs.field[i])
                {
                    return field[i] < rhs.field[i] ? -1 : 1;
                }
            }
            return 0;
        }
        /// ditto
        int opCmp(R)(R rhs) const
        if (areCompatibleTuples!(typeof(this), R, "<"))
        {
            foreach (i, Unused; Types)
            {
                if (field[i] != rhs.field[i])
                {
                    return field[i] < rhs.field[i] ? -1 : 1;
                }
            }
            return 0;
        }

        /**
         * Assignment from another tuple. Each element of the source must be
         * implicitly assignable to the respective element of the target.
         */
        void opAssign(R)(auto ref R rhs)
        if (areCompatibleTuples!(typeof(this), R, "="))
        {
            static if (is(R : Tuple!Types) && !__traits(isRef, rhs))
            {
                if (__ctfe)
                {
                    // Cannot use swap at compile time
                    field[] = rhs.field[];
                }
                else
                {
                    // Use swap-and-destroy to optimize rvalue assignment
                    swap!(Tuple!Types)(this, rhs);
                }
            }
            else
            {
                // Do not swap; opAssign should be called on the fields.
                field[] = rhs.field[];
            }
        }

        /**
         * Takes a slice of the tuple.
         *
         * Examples:
         * ----
         * Tuple!(int, string, float, double) a;
         * a[1] = "abc";
         * a[2] = 4.5;
         * auto s = a.slice!(1, 3);
         * static assert(is(typeof(s) == Tuple!(string, float)));
         * assert(s[0] == "abc" && s[1] == 4.5);
         * ----
         */
         property
        ref Tuple!(sliceSpecs!(from, to)) slice(size_t from, size_t to)()
 trusted
        if (from <= to && to <= Types.length)
        {
            return *cast(typeof(return)*) &(field[from]);
        }

        /**
         * Converts to string.
         */
        string toString()
        {
            enum header = typeof(this).stringof ~ "(",
                 footer = ")",
                 separator = ", ";

            Appender!string w;
            w.put(header);
            foreach (i, Unused; Types)
            {
                static if (i > 0)
                {
                    w.put(separator);
                }
                // TODO: Change this once toString() works for shared objects.
                static if (is(Unused == class) && is(Unused == shared))
                    formattedWrite(w, "%s", field[i].stringof);
                else
                {
                    FormatSpec!char f;  // "%s"
                    formatElement(w, field[i], f);
                }
            }
            w.put(footer);
            return w.data;
        }
    }
}

alias Foo = Tuple!int;

void main()
{
    const(Foo) f;
    f = f;
}
-----

Then we get the simple error: 
 Error: cannot modify const expression f
It looks like an implementation bug. -- Configure issuemail: http://d.puremagic.com/issues/userprefs.cgi?tab=email ------- You are receiving this mail because: -------
Jun 27 2013
prev sibling next sibling parent d-bugmail puremagic.com writes:
http://d.puremagic.com/issues/show_bug.cgi?id=10487


bearophile_hugs eml.cc changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
           Severity|enhancement                 |normal



Thank you Andrej. Now it's tagged as diagnostic dmd bug.

-- 
Configure issuemail: http://d.puremagic.com/issues/userprefs.cgi?tab=email
------- You are receiving this mail because: -------
Jun 27 2013
prev sibling parent d-bugmail puremagic.com writes:
http://d.puremagic.com/issues/show_bug.cgi?id=10487


Kenji Hara <k.hara.pg gmail.com> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
          Component|DMD                         |Phobos
           Severity|normal                      |enhancement




 But if we copy-paste the Tuple implementation inline into the current module:
 
[snip]
 
 Then we get the simple error: 
 Error: cannot modify const expression f
It looks like an implementation bug.
No. It's not a compiler bug. Following code missing in copy-paste code: import std.traits; template isTuple(T) { static if (is(Unqual!T Unused : Tuple!Specs, Specs...)) { enum isTuple = true; } else { enum isTuple = false; } } ------------- std.typecons.Tuple defines opAssign method, so compiler always try to call it for assignment operation. So current error message is completely intended (even if it looks a little verbose). I change this to Phobos enhancement. -- Configure issuemail: http://d.puremagic.com/issues/userprefs.cgi?tab=email ------- You are receiving this mail because: -------
Jun 28 2013