www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Prevention of UFCS hijacking

reply Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
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.
Aug 27 2013
next sibling parent reply "Maxim Fomin" <maxim maxim-fomin.ru> writes:
On Tuesday, 27 August 2013 at 18:02:27 UTC, Andrej Mitrovic wrote:
 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.

But commenting out "text" property is not order to compiler to ignore all UFCS involving "text". What commenting out has to do with UFCS?
 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.

Then classes should implement member to disable UFCS hijacktion. If not, this is an allowance for such name hijacktion. This is by design.
 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.

disable is a way to block this feature
 ---

 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.

Let's start from basics: writeln = 42; and this is also by design.
Aug 27 2013
parent Jacob Carlborg <doob me.com> writes:
On 2013-08-27 20:40, Maxim Fomin wrote:

 Let's start from basics:

 writeln = 42;

 and this is also by design.

I see no reasons for why this should be allowed. I say: * Getters marked with property: call without parentheses * Getters not marked with property: call with or without parentheses * Setters marked with property: require assignment syntax * Setters not marked with property: disallow assignment syntax -- /Jacob Carlborg
Aug 27 2013
prev sibling next sibling parent Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
On 8/27/13, Maxim Fomin <maxim maxim-fomin.ru> wrote:
 Let's start from basics

You've just repeated everything I've already said.
Aug 27 2013
prev sibling next sibling parent "Maxim Fomin" <maxim maxim-fomin.ru> writes:
On Tuesday, 27 August 2013 at 19:39:34 UTC, Andrej Mitrovic wrote:
 On 8/27/13, Maxim Fomin <maxim maxim-fomin.ru> wrote:
 Let's start from basics

You've just repeated everything I've already said.

What should I tell except you are surprising of current state of things?
Aug 27 2013
prev sibling next sibling parent Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
On 8/27/13, Maxim Fomin <maxim maxim-fomin.ru> wrote:
 What should I tell except you are surprising of current state of
 things?

I'm just raising awareness of the issue. And it's surprising when you're coding and not expecting it. Imagine adding an import to a 3rd party library and no longer getting errors at compile time when you assign to a field that doesn't exist. Anyone could be caught off-guard by this.
Aug 28 2013
prev sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Wed, Aug 28, 2013 at 08:41:39AM +0200, Jacob Carlborg wrote:
 On 2013-08-27 20:40, Maxim Fomin wrote:
 
Let's start from basics:

writeln = 42;

and this is also by design.

I see no reasons for why this should be allowed. I say: * Getters marked with property: call without parentheses * Getters not marked with property: call with or without parentheses * Setters marked with property: require assignment syntax * Setters not marked with property: disallow assignment syntax

+1, I agree with all of the above. T -- Старый друг лучше новых двух.
Aug 28 2013