dmd icon indicating copy to clipboard operation
dmd copied to clipboard

Non-eponymous template instances have a "type" (void)

Open dlangBugzillaToGithub opened this issue 2 years ago • 10 comments

Vladimir Panteleev (@CyberShadow) reported this on 2023-05-12T17:17:07Z

Transferred from https://issues.dlang.org/show_bug.cgi?id=23916

CC List

  • Nick Treleaven (@ntrel)

Description

This code is meaningless, and should not compile:

//// test.d ////
template X() {}

void fun()
{
    return X!();
}
////////////////

Template instances having a type when they shouldn't can be misleading and confusing to diagnose. For example, consider:

///// test.d /////
template X()
{
}

void fun(int i) {}

void main()
{
    fun(X!());
}
//////////////////

Currently, this produces:

test.d(9): Error: function `test.fun(int i)` is not callable using argument types `(void)`
test.d(9):        cannot pass argument `X!()` of type `void` to parameter `int i`

A better error would be:

test.d(9): Error: cannot pass template instance `X!()` as a function argument

dlangBugzillaToGithub avatar May 12 '23 17:05 dlangBugzillaToGithub

nick (@ntrel) commented on 2023-05-12T18:10:30Z

template X() {}

It was decided that typeof(X) is void, that's in the spec. I'm not sure if the type of a template instance is defined in the spec. A template instance seems similar to a module. However `typeof(std.math)` is a compile error.

void fun()
{
    return X!();
}

Note: You are allowed to return an expression of type void, this is useful in generic code.

dlangBugzillaToGithub avatar May 12 '23 18:05 dlangBugzillaToGithub

dlang-bugzilla (@CyberShadow) commented on 2023-05-12T18:20:24Z

(In reply to Nick Treleaven from comment #1)
> template X() {}
> 
> It was decided that typeof(X) is void, that's in the spec. I'm not sure if
> the type of a template instance is defined in the spec.

Any reason not to revise and deprecate this?

> Note: You are allowed to return an expression of type void, this is useful
> in generic code.

Yes. Although, it is an accidental, half-finished feature. You cannot have void variables or function parameters. Ironically however, you can have noreturn variables and function parameters.

dlangBugzillaToGithub avatar May 12 '23 18:05 dlangBugzillaToGithub

nick (@ntrel) commented on 2023-05-13T10:32:57Z

> Any reason not to revise and deprecate this?

It would break code like this:

import std.stdio;

template t()
{
	int i;
}

void f(alias a)() if (is(typeof(a)))
{
	writeln(a.i);
}

void main()
{
	alias a = t!();
	f!a();
}

dlangBugzillaToGithub avatar May 13 '23 10:05 dlangBugzillaToGithub

dlang-bugzilla (@CyberShadow) commented on 2023-05-13T10:36:32Z

(In reply to Nick Treleaven from comment #3)
> void f(alias a)() if (is(typeof(a)))

This looks meaningless to me. Why would you want to write code like this - what is the constraint even supposed to check? Why should we not deprecate then remove taking the type of a template / template instance?

dlangBugzillaToGithub avatar May 13 '23 10:05 dlangBugzillaToGithub

nick (@ntrel) commented on 2023-05-13T12:55:47Z

It checks if `a` has a type. It tells the reader that `a` is not a module or a type itself.

Why is it a problem if a template instance has a type?

dlangBugzillaToGithub avatar May 13 '23 12:05 dlangBugzillaToGithub

dlang-bugzilla (@CyberShadow) commented on 2023-05-13T13:00:07Z

(In reply to Nick Treleaven from comment #5)
> It checks if `a` has a type. It tells the reader that `a` is not a module or
> a type itself.

Am I missing something? How does it tell that to the reader, unless the reader is aware of this weird quirk in the language? Wouldn't something more explicit be much better?

What is the real use case? Do you have any real-world code which illustrates why this rule in the language is useful?
 
> Why is it a problem if a template instance has a type?

It allows (accidentally, or maliciously) writing working but confusing code, and allows accidentally writing non-working code which is difficult to diagnose why it's not working.

dlangBugzillaToGithub avatar May 13 '23 13:05 dlangBugzillaToGithub

nick (@ntrel) commented on 2023-05-13T13:08:50Z

Can you give examples of confusing code?

> How does it tell that to the reader, unless the reader is aware of this weird quirk in the language? 

First, you should acknowledge that the constraint is not meaningless as you said. The usefulness of that constraint is independent of this issue.

Next, the reader should understand constraints. They don't have to know that a template instance has void type, yet their code will magically work for people who do know that.

> Wouldn't something more explicit be much better?

Such as what?

> What is the real use case? 

You are asking for a change so the burden is on you to justify it. If you like, make a draft pull request and see if it breaks anything.

dlangBugzillaToGithub avatar May 13 '23 13:05 dlangBugzillaToGithub

dlang-bugzilla (@CyberShadow) commented on 2023-05-13T13:54:24Z

(In reply to Nick Treleaven from comment #7)
> Can you give examples of confusing code?

I find your examples above confusing.

It is meaningless for a template to have a type.

It is also meaningless for a template instance to have a type, unless of course it is an eponymous template which resolves to something that has a type.

"void" is an arbitrary choice. Why the unit type? Why not the bottom type? Why not typeof(null), etc.?

> First, you should acknowledge that the constraint is not meaningless as you
> said. The usefulness of that constraint is independent of this issue.
> 
> Next, the reader should understand constraints. They don't have to know that
> a template instance has void type, yet their code will magically work for
> people who do know that.

I have no idea what you're trying to say by this.

> > Wouldn't something more explicit be much better?
> 
> Such as what?

To check that something is a type: if (is(X))
To check that something is a template: if (__traits(isTemplate, X))
To check that something is a template instance: if (is(X : Template!Args, alias Template, Args...)) (or use alias parameter destructuring)
To check that something is an instance of a particular template: if (__traits(isSame, TemplateOf!X, SomeTemplate))

Yeah, these are also awkward and inconsistent. std.traits does have a bunch of helpers, such as `isType`, and there've been proposals to put something more readable on top of IsExpression in Phobos.

> You are asking for a change so the burden is on you to justify it. If you
> like, make a draft pull request and see if it breaks anything.

What is this, 90's newsgroups? I'm not playing this "burden of proof" BS.

There is nothing personal about this issue. We do not file requests that we are personally "asking for"; we file proposals on how to progress on our shared goal of making the language better. And bugs.

Every single bit of the language spec needs to be be defensible. If something is not backed by a strong rationale, it is candidate for revision, and we do need to continuously attack and revise the weak parts of the language. Otherwise, we are never going to get a better language.

And yeah, fixing this will probably require a deprecation cycle.

dlangBugzillaToGithub avatar May 13 '23 13:05 dlangBugzillaToGithub

dlang-bugzilla (@CyberShadow) commented on 2023-05-13T14:03:06Z

(In reply to Nick Treleaven from comment #7)
> > Wouldn't something more explicit be much better?
> 
> Such as what?

For the specific example above, probably this:

void f(alias a)() if (is(typeof(a.i) : int))

or:

void f(alias a)() if (is(typeof(writeln(a.i))))

depending on your intention.

dlangBugzillaToGithub avatar May 13 '23 14:05 dlangBugzillaToGithub

I think every expression has to have a type. Assuming that's correct, if a template and/or a template instantiation no longer had a type, the grammar would have to be changed, e.g.:

  • To allow a function call with IFTI. Currently a function call needs an expression on the left
  • To allow optional parens on a template function (called with IFTI) which is the left-hand side of a larger expression.
  • As previous item, but with partial template arguments supplied.
  • The Expression grammar doesn't support TemplateInstance (except as a PrimaryExpression itself) on the left of various expressions, and that might be awkward to add.

To avoid the first 2 issues, another idea might be to change the type of a template to a new type __Template. That would at least avoid confusion in error messages about a void expression. However it could break code (e.g. 2nd overload constraint and body of std.range.tee).

ntrel avatar May 02 '25 12:05 ntrel