Thread overview
The compiler swallows opDispatch errors
Aug 30, 2021
bauss
Aug 30, 2021
Adam D Ruppe
Aug 30, 2021
H. S. Teoh
Aug 31, 2021
Nicholas Wilson
August 27, 2021

This bit me again:

struct S
{
   void opDispatch(string s)() {
     writeln("you called " ~ s);
   }
}

void main()
{
   S s;
   s.foo();
}

The result?

onlineapp.d(11): Error: no property `foo` for type `onlineapp.S`
onlineapp.d(11):        potentially malformed `opDispatch`. Use an explicit instantiation to get a better error message

What? I have to tell the compiler to explicitly instantiate opDispatch in order for it to tell me the actual message?

What I expected is something like:

Error: no symbol writeln, please import std.stdio

What is happening here is that if opDispatch doesn't compile for any reason, it's not considered a valid instantiation.

This is not how any other functions templates work. If you call a function or template, and it doesn't compile, it tells you why it didn't compile and gives an error. With opDispatch, it implicitly is adding one of the worst template constraints if (__traits(compiles, <function body>)). This hides so much stuff, and makes it really hard to find out why something doesn't work, or results in calling very surprising UFCS functions.

Let's make it even more obscure!

s.get();
onlineapp.d(11): Error: template `object.get` cannot deduce function from argument types `!()(S)`, candidates are:
/dlang/dmd/linux/bin64/../../src/druntime/import/object.d(3077): `get(K, V)(inout(V[K]) aa, K key, lazy inout(V) defaultValue)`
/dlang/dmd/linux/bin64/../../src/druntime/import/object.d(3084): `get(K, V)(inout(V[K])* aa, K key, lazy inout(V) defaultValue)`

Yeah, can we please fix this? opDispatch should use the same rules as any other function template -- if it matches, compile. No implicit "compilation must succeed" BS.

-Steve

August 30, 2021

On Friday, 27 August 2021 at 16:08:37 UTC, Steven Schveighoffer wrote:

>

This bit me again:

struct S
{
   void opDispatch(string s)() {
     writeln("you called " ~ s);
   }
}

void main()
{
   S s;
   s.foo();
}

The result?

onlineapp.d(11): Error: no property `foo` for type `onlineapp.S`
onlineapp.d(11):        potentially malformed `opDispatch`. Use an explicit instantiation to get a better error message

What? I have to tell the compiler to explicitly instantiate opDispatch in order for it to tell me the actual message?

What I expected is something like:

Error: no symbol writeln, please import std.stdio

What is happening here is that if opDispatch doesn't compile for any reason, it's not considered a valid instantiation.

This is not how any other functions templates work. If you call a function or template, and it doesn't compile, it tells you why it didn't compile and gives an error. With opDispatch, it implicitly is adding one of the worst template constraints if (__traits(compiles, <function body>)). This hides so much stuff, and makes it really hard to find out why something doesn't work, or results in calling very surprising UFCS functions.

Let's make it even more obscure!

s.get();
onlineapp.d(11): Error: template `object.get` cannot deduce function from argument types `!()(S)`, candidates are:
/dlang/dmd/linux/bin64/../../src/druntime/import/object.d(3077): `get(K, V)(inout(V[K]) aa, K key, lazy inout(V) defaultValue)`
/dlang/dmd/linux/bin64/../../src/druntime/import/object.d(3084): `get(K, V)(inout(V[K])* aa, K key, lazy inout(V) defaultValue)`

Yeah, can we please fix this? opDispatch should use the same rules as any other function template -- if it matches, compile. No implicit "compilation must succeed" BS.

-Steve

I'd argue that opDispatch shouldn't have any constraints on it.

It's a function that's rewritten by the compiler and so any error checking should be done solely by the compiler and without any attributing such as __traits(compiles)

If an opDispatch is declared that cannot be compiled then arguably it should stop the entire compilation as something is clearly wrong.

It's almost equivalent to a try/catch without handling an exception, but this time in the compiler.

August 30, 2021
On Monday, 30 August 2021 at 06:13:03 UTC, bauss wrote:
> I'd argue that opDispatch shouldn't have any constraints on it.

Well my rule would be if opDispatch is considered, the error shoudl be printed.

But you can prevent opDispatch from being considered by putting a constraint on it.

So

struct A {
  void opDispatch(string s)() {
        dfsdfsdf;
  }
}

A a;
a.whatever; // REPORT THE FULL ERROR OMG


BUT


struct A {
  void opDispatch(string s)() if(s != "whatever") {
        dfsdfsdf;
  }
}

A a;
a.whatever; // "no such property: whatver"


so you only get "no such property" if the opDispatch is not considered at all because the outer constraint filtered it out.
August 30, 2021

On 8/30/21 2:13 AM, bauss wrote:

>

I'd argue that opDispatch shouldn't have any constraints on it.

If you mean implicit constraints, then yes. But not allowing constraints would be insanely awkward. We might be saying the same thing, but I'm not sure.

>

It's a function that's rewritten by the compiler and so any error checking should be done solely by the compiler and without any attributing such as __traits(compiles)

I'm not sure how the compiler implements the check, but the effect is the same as adding a __traits(compiles, <function body>) constraint implicitly.

>

If an opDispatch is declared that cannot be compiled then arguably it should stop the entire compilation as something is clearly wrong.

Determining the compilability of a declared template is complex -- usually you need an instantiation.

And opDispatch doesn't have to necessarily compile for all possible instantiations. For instance, opDispatch won't be used if there is already a member of that name.

I think the point of using non-compilation as an implicit constraint is so you can do things like:

auto opDispatch(string n, Args...)(Args args) { return mixin("something.", n, "(args)"); }

and not have to repeat the callability of that in the constraint. However, the cure is worse than the disease here IMO.

For sure, if this were to be "fixed", we'd have to deprecate for a while as I'm sure some libraries utilize this "feature".

As a mitigating feature, perhaps something like __traits(compiles, template) could be added as a constraint possibility which will mean "the thing I'm constraining here compiles as instantiated".

-Steve

August 30, 2021
On Mon, Aug 30, 2021 at 12:19:21PM +0000, Adam D Ruppe via Digitalmars-d wrote:
> On Monday, 30 August 2021 at 06:13:03 UTC, bauss wrote:
> > I'd argue that opDispatch shouldn't have any constraints on it.
> 
> Well my rule would be if opDispatch is considered, the error shoudl be printed.
> 
> But you can prevent opDispatch from being considered by putting a constraint on it.
> 
> So
> 
> struct A {
>   void opDispatch(string s)() {
>         dfsdfsdf;
>   }
> }
> 
> A a;
> a.whatever; // REPORT THE FULL ERROR OMG
> 
> 
> BUT
> 
> 
> struct A {
>   void opDispatch(string s)() if(s != "whatever") {
>         dfsdfsdf;
>   }
> }
> 
> A a;
> a.whatever; // "no such property: whatver"
> 
> 
> so you only get "no such property" if the opDispatch is not considered at all because the outer constraint filtered it out.

But what about this then:

	struct A {
		void opDispatch(string s)() if (s0934hjslfadfAaarrgghh)
		{
			return;
		}
	}

Should this generate an error or not?


T

-- 
Why are you blatanly misspelling "blatant"? -- Branden Robinson
August 30, 2021

On 8/30/21 8:48 AM, H. S. Teoh wrote:

>

On Mon, Aug 30, 2021 at 12:19:21PM +0000, Adam D Ruppe via Digitalmars-d wrote:

>

On Monday, 30 August 2021 at 06:13:03 UTC, bauss wrote:

>

I'd argue that opDispatch shouldn't have any constraints on it.

Well my rule would be if opDispatch is considered, the error shoudl be
printed.

But you can prevent opDispatch from being considered by putting a
constraint on it.

So

struct A {
void opDispatch(string s)() {
dfsdfsdf;
}
}

A a;
a.whatever; // REPORT THE FULL ERROR OMG

BUT

struct A {
void opDispatch(string s)() if(s != "whatever") {
dfsdfsdf;
}
}

A a;
a.whatever; // "no such property: whatver"

so you only get "no such property" if the opDispatch is not considered
at all because the outer constraint filtered it out.

But what about this then:

struct A {
void opDispatch(string s)() if (s0934hjslfadfAaarrgghh)
{
return;
}
}

Should this generate an error or not?

Let's see:

void foo()() if (s0934hjslfadfAaarrgghh) {}


void main()
{
   foo();
}
onlineapp.d(1): Error: undefined identifier `s0934hjslfadfAaarrgghh`
onlineapp.d(7): Error: `foo()() if (s0934hjslfadfAaarrgghh)` has no effect

Yes, it should, if considered.

-Steve

August 31, 2021

On Friday, 27 August 2021 at 16:08:37 UTC, Steven Schveighoffer wrote:

>

This bit me again:
What? I have to tell the compiler to explicitly instantiate opDispatch in order for it to tell me the actual message?

https://github.com/dlang/dmd/pull/12288#issuecomment-802852873

because I couldn't figure out how to do it properly.