Prevention of UFCS hijacking

Andrej Mitrovic andrej.mitrovich at gmail.com
Tue Aug 27 11:02:18 PDT 2013


I've run into a bit of an issue with UFCS today, take a look at the
reduced test-case:

-----
import std.conv;

class Label
{
    // @property string text() {  }
    // @property void text(string nText) {  }
}

void main()
{
    auto label = new Label;

    label.text = "My label";
    assert(label.text == "My label");  // fails at runtime
}
-----

Because of the UFCS feature std.conv.text is called even though I
expected compilation to fail since I've commented out the .text
properties in my class.

This is a problem since some of my classes might have a .text field,
while others might not. If the user imports std.conv he could easily
end up calling std.conv.text by mistake when a class object doesn't
implement such a property.

Anyway, there is a cure. I can work around this using @disabled fields
in the base class. For example:

-----
import std.conv;

class Widget
{
    @disable string text;
}

class Label : Widget
{
    @property string text() { return "My label"; }
    @property void text(string nText) { }
}

class Image : Widget
{
}

void main()
{
    auto frame = new Label;
    frame.text = "My label";
    assert(frame.text == "My label");

    auto image = new Image;
    image.text = "foobar";  // ok, fails to compile
}

-----

Note that I've specifically made the base class declare a public
variable rather than a @property, otherwise the derived class would
have to override a virtual @property function.

Maybe this code might even become a case of accepts-invalid at some
point, since the properties do actually shadow the base class
variable. I'm not sure how long this workaround will work.

Has anyone else run into this sort of issue? I'm wondering whether we
should have some language support (through __traits, @disable, or
something else), to enable writing APIs which are safer to use, where
you can't by mistake call a UFCS function like that if there's an
equally named function introduced in another derived class.

---

Btw, why exactly is the following allowed to compile?:

-----
import std.conv;
import std.stdio;

struct S { }

void main()
{
    S s;
    writeln(s.text = "foo");
}
-----

This translates into std.conv.text(s, "foo"). I'm not a fan,
especially since 'text' is not a @property. But we've already reached
the consensus that all functions can be called like properties (for
some reason..), so I guess it's too late to change this.


More information about the Digitalmars-d mailing list