It should be possible to use UFCS on an opaque struct.
----
struct State;
void foo(State*) {};
void main() {
State* s;
foo(s); // ok
s.foo(); // compile error
}
----
Error: struct State is forward referenced when looking for 'foo'
Error: struct State is forward referenced when looking for 'opDot'
Error: struct State is forward referenced when looking for 'opDispatch'
Comment #1 by john — 2012-05-15T19:54:46Z
I scanned the dmd UFCS implementation. Apparently expressions are blindly translated from e.g. foo.bar() to bar(foo) when the normal searches on foo fail. In other words, we don't know at the time of the normal searches whether there is a matching free function.
I propose resolving this by, in all cases, not considering a search on an opaque struct to be an error. That is, foo.bar() will not yield a "forward referenced" error regardless of whether a matching free function exists.
For the case where no free function exists, the compile error would then be "undefined identifier 'bar'"-- i.e. the same as for a non-opaque struct.
Comment #2 by john — 2012-05-18T18:36:34Z
Appears to be a one-line fix. Assuming I can figure out the build and unit test framework I'll prepare a pull request.
(In reply to comment #0)
> It should be possible to use UFCS on an opaque struct.
>
> ----
> struct State;
>
> void foo(State*) {};
>
> void main() {
> State* s;
> foo(s); // ok
> s.foo(); // compile error
> }
>
> ----
> Error: struct State is forward referenced when looking for 'foo'
> Error: struct State is forward referenced when looking for 'opDot'
> Error: struct State is forward referenced when looking for 'opDispatch'
I think this is expected behavior. s.foo() should look up Stete's members, but it is impossible, then errors occur.
UFCS should be a lookup for fall-back. If properly lookup fails, UFCS should not hide the error.
Comment #5 by john — 2012-05-20T06:44:31Z
@Kenji: When brought up on the forum there seemed to be consensus that this was a bug. http://forum.dlang.org/thread/[email protected]
Why should the act of function member lookup on an opaque struct necessarily be an error? It should only be an error if the member cannot be resolved. Prior to UFCS, there was no way to resolve function members outside of the struct itself, so it made sense to raise an error directly from the struct's search call.
At http://dlang.org/struct.html there is a section "Opaque Structs and Unions". It says that the members of such a struct are hidden from the user. It's not clear on what "hidden" implies though. With respect to function members this documentation should be clarified: an opaque struct behaves as if it has no function members.
Note that all the errors regarding "forward reference" are misleading. These structs not a forward reference, there are opaque. It's illegal to redefine an opaque struct:
struct S;
struct S { int foo; } // Error: struct S conflicts with struct S
Given that, what are you trying to protect the user from by disallowing UFCS on these structs?
Comment #6 by k.hara.pg — 2012-05-20T08:31:49Z
(In reply to comment #5)
> @Kenji: When brought up on the forum there seemed to be consensus that this was
> a bug. http://forum.dlang.org/thread/[email protected]
>
> Why should the act of function member lookup on an opaque struct necessarily be
> an error? It should only be an error if the member cannot be resolved. Prior
> to UFCS, there was no way to resolve function members outside of the struct
> itself, so it made sense to raise an error directly from the struct's search
> call.
Because of function hijacking.
> At http://dlang.org/struct.html there is a section "Opaque Structs and Unions".
> It says that the members of such a struct are hidden from the user. It's not
> clear on what "hidden" implies though. With respect to function members this
> documentation should be clarified: an opaque struct behaves as if it has no
> function members.
No. opaque struct means "may or may not have members", not "has no members".
They are different.
> Note that all the errors regarding "forward reference" are misleading. These
> structs not a forward reference, there are opaque. It's illegal to redefine an
> opaque struct:
>
> struct S;
> struct S { int foo; } // Error: struct S conflicts with struct S
>
> Given that, what are you trying to protect the user from by disallowing UFCS on
> these structs?
I'd like to explain a use case.
---
1. module m_a has a struct S with members. module m_b imports m_a and use S.
module m_a;
struct S { void foo() { ... } ... }
S* makeS() { ... }
module m_b;
void foo(S* ps) { ... }
void main(){ auto ps = makeS(); ps.foo(); /* call S.foo */ }
2. S is changed to 'opaque struct'.
(e.g. m_a is provided by hand-made di file.)
module m_a;
struct S;
S* makeS();
3. In module m_b, now ps.foo() accidently calls m_b.foo.
---
Additionaly, current UFCS lookup mechanism is not completely defined/implemented.
It's debated recently (See http://forum.dlang.org/thread/[email protected]), so I wouldn't like to change/add lookup mechanism yet.
Comment #7 by john — 2012-05-20T10:39:38Z
(In reply to comment #6)
> 1. module m_a has a struct S with members. module m_b imports m_a and use S.
>
> module m_a;
> struct S { void foo() { ... } ... }
> S* makeS() { ... }
>
> module m_b;
> void foo(S* ps) { ... }
> void main(){ auto ps = makeS(); ps.foo(); /* call S.foo */ }
>
> 2. S is changed to 'opaque struct'.
> (e.g. m_a is provided by hand-made di file.)
>
> module m_a;
> struct S;
> S* makeS();
>
> 3. In module m_b, now ps.foo() accidently calls m_b.foo.
I may be misunderstanding function hijacking, because it doesn't seem to apply to your example. The public interface of S was changed-- the formerly public member function foo was removed by declaring an opaque struct in the di file-- so all bets are off.
How is this example any different than removing a public function from a class? You'd have the same problem, and it's allowed today.
Walter Bright defines hijacking in http://www.digitalmars.com/d/archives/digitalmars/D/Hijacking_56458.html#N56458:
"Hijacking is when code A depends on B, and then when seemingly unrelated
code C is modified, then A's behavior silently changes."
In your example, there is no C. Rather it's self-inflicted folly between A and B. B made a change to his public interface and A got a self-inflicted silent change in behavior.
Comment #8 by bugzilla — 2012-06-28T12:21:33Z
I believe this is invalid.
Code must not change behavior depending on whether a struct's members are there or are opaque, hence if there is a possible dependency on what those members are then it should fail to compile.