May 08, 2023

On Sunday, 7 May 2023 at 00:14:35 UTC, Walter Bright wrote:

>

On 5/6/2023 8:55 AM, Quirin Schroll wrote:

>

I have a very stupid question now: Why does a minor grammar change require a DIP? How do people determine what is an enhancement and what requires a DIP?

It's not a stupid question at all.

The reason is, it is definitely non-trivial, it will impact a lot of code with deprecations, people will want a strong explanation of why this is worthwhile, and enable a wider audience to look for risks and flaws.

Thank you for responding.

The proposal can be summed up as:

  1. Allow TypeCtor in the TypeCtor(Type) of BasicType syntax to be “mutable,” which is syntactically just nothing, i.e. allow BasicType to be (Type).
  2. Allow ref as the initial token for a type; such a type must be a function or delegate type to make sense.

They’re even orthogonal: The first clearly does not depend on the second and even the second does not depend on the first, but they really shine together.

My post was mostly about motivation, why this is useful/necessary and that, albeit it sounds big, is actually quite a small change.

>

At a first reading, this looks like a worthwhile endeavor.

This is probably about changing a few lines of code in the parser, without any (big) additions.

>

But the thing is, what you've written is already pretty close to being a DIP. You've already done the hard part.

One question: can this peacefully coexist with the existing syntax, before we push it with a deprecation?

Yes. This was essentially asked by Basile B. and the answer is that the deprecations are entirely optional. They’re like deprecating () => { } (for creating nested lambdas) or, even closer, not allowing bit-operators and comparison operators without clarifying parentheses.

I suggested them because I think that confusing syntax should only be allowed if truly the lesser of two evils. Demanding clarifying parentheses is not horrible breakage.

We can 100% implement the feature today and discuss the deprecations in the next years or so.

May 09, 2023

Given:
TypeBasicType
BasicType(Type)

I have found one issue, but it falls under the category of Nobody Writes Such Code:
If a type T has a static indexing operator (opIndex or similar), then the token sequence T[] can denote a Type or an Expression. If that token sequence is itself in parentheses and not prefixed by a TypeCtor, then it currently cannot parse as a Type, but with the proposed changes, it could, and in some circumstances, that would make valid working code change meaning.

Example:

enum isType(alias a) = is(a);

struct X
{
    static int opIndex() @safe => 0;
}

// Parsed as a type,  `X[]` is slice of `X`.
// Parsed as a value, `X[]` is `X.opIndex()`.

void main() @safe
{
    static assert(!isType!( 0 ));
    static assert( isType!(int));

    static assert( isType!( X[] ));
    static assert(!isType!((X[]))); // This would fail!
}

Again, nobody writes such code and relies on a pair of parentheses to make something a value that’s read by any human as a type. This is something that is even bad today. Nothing should change what it is just because you put it in a pair of parentheses.

May 09, 2023

On Tuesday, 9 May 2023 at 17:15:41 UTC, Quirin Schroll wrote:

>

I have found one issue, but it falls under the category of Nobody Writes Such Code:
If a type T has a static indexing operator (opIndex or similar), then the token sequence T[] can denote a Type or an Expression. If that token sequence is itself in parentheses and not prefixed by a TypeCtor, then it currently cannot parse as a Type, but with the proposed changes, it could, and in some circumstances, that would make valid working code change meaning.

There are some other examples with similar problems:

The code X*[1] could be both an expression and a type. As an expression X would need to implement opBinary for multiplication. As a type it would be an array to a pointer to X.

The code X*[0][0] could also be both. As an expression it would multiply X with [0][0], which is 0. As a type it would be an array of an array of a pointer to X. This example does not need operator overloading.

A more complex example also uses * as a pointer dereference: X**[Y][0]
This could be a type, if X is a type and Y is also a type or evaluates to an integer.
If X is an integer and Y is a pointer to an integer, then it would be a valid expression.

May 09, 2023
On Sunday, 7 May 2023 at 07:35:22 UTC, Timon Gehr wrote:
> On 5/7/23 02:16, Walter Bright wrote:
>> [...]
>
> There is no conflict between the two features in the first place.
>
> [...]

In my opinion tuples are more like struct... I dont't see how they are related to arrays.
May 10, 2023

On Tuesday, 9 May 2023 at 20:37:53 UTC, Basile B. wrote:

>

On Sunday, 7 May 2023 at 07:35:22 UTC, Timon Gehr wrote:

>

On 5/7/23 02:16, Walter Bright wrote:

>

[...]

There is no conflict between the two features in the first place.

[...]

In my opinion tuples are more like struct... I dont't see how they are related to arrays.

What is a T[2]? Something that contains two Ts, accessed by index, otherwise unstructured.

What is a Tuple!(T, T)? Something that contains two Ts, accessed by index, otherwise unstructured.

Can you see it now?

(If you thought I meant T[], that is indeed unrelated to tuples. But T[] isn’t an array, it’s a slice.)

May 10, 2023

On Saturday, 6 May 2023 at 16:20:56 UTC, Quirin Schroll wrote:

> >

It could be trailing return type syntax:

function(ref int i) -> ref int refIdPtr;

Then we don't need the alias A = ref T rule, and we can keep parentheses for expressions (including tuples in future).

I agree that trailing return types (TRT) are really well readable – in a language designed around them, which D isn’t. I find it irritating that the -> Type is followed by the variable name. In languages that have them, variables are declared by a keyword, then follows the name, then a separator and then comes the type. In that order, TRT makes sense.

It's the same pattern as int var; - Type identifier;. Do you think it would cause parsing ambiguities? If not, maybe you read it as naming the return type? In languages that have named return types, I think a named return would be written with parentheses: -> (Type identifier).

>

I put parts of a declaration in their own line when they’re getting big: Big return type → own line. 1 big (template)

OK but that wastes vertical space, particularly in an IDE popup window with a long list of overloads.

>

TRT are are a much greater language change, though, compared to making a token at a very specific place optional. Their grammar must really be implemented and maintained separately.

Conceptually I think it would be simple:

(function | delegate) ParameterList Attributes? -> StorageClass Type
StorageClass Identifier ParameterList Attributes? -> StorageClass Type
const method() -> int;
>

Also, we get the same as C++: Two ways to declare a function.

Modern C++ may be moving toward using TRT, at least Herb Sutter recommends it. The idea is to move to TRT over time. Linters could enforce it.

> > >
 const Object  function()  f0; // Make this an error …
const (Object  function()) f1; // … and require this!
(const Object) function()  f2; // To differentiate from this.
 const(Object) function()  f3; // (Same type as f2)

I don't get what the issue is with f0 and f3, aren't they clear enough?

With f0: Does const apply to the return value or the variable f0? I’m quite adept with D and I’m only 95% sure it applies to f0. 95 is not 100, and frankly, when it comes to syntax, it should be 100.

You're right, f1 is better. I thought I was used to const applying to the variable/parameter, but I just confused myself trying to get a parameter to work:

void f(const ref int function() p);

(I started thinking if the function could have leading const, ref would parse). Although if leading const on functions was deprecated there might be less confusion for the parameter case.

>

With f3: Less of an issue, but basically the same. To someone new to D, it might be odd that const applies to something different if a parenthesis follows.

OK. I'm not sure if parenthesized types would complicate parsing.

May 10, 2023

On Wednesday, 10 May 2023 at 13:30:10 UTC, Nick Treleaven wrote:

>

If not, maybe you read it as naming the return type? In languages that have named return types,

I meant named return value(s).

May 10, 2023

On Tuesday, 9 May 2023 at 18:13:09 UTC, Tim wrote:

>

On Tuesday, 9 May 2023 at 17:15:41 UTC, Quirin Schroll wrote:

>

I have found one issue, but it falls under the category of Nobody Writes Such Code:
If a type T has a static indexing operator (opIndex or similar), then the token sequence T[] can denote a Type or an Expression. If that token sequence is itself in parentheses and not prefixed by a TypeCtor, then it currently cannot parse as a Type, but with the proposed changes, it could, and in some circumstances, that would make valid working code change meaning.

There are some other examples with similar problems:

The code X*[1] could be both an expression and a type. As an expression X would need to implement opBinary for multiplication. As a type it would be an array to a pointer to X.

To parse as a type, X must be a type. Therefore, to a type or expression depending on what to parse, X must be a type.

To parse as an expression X it must be an aggregate type has a static member function template opBinary that can be instantiated as opBinary!"*" and called with [1]. It must be static because X must parse as a type if X is unambiguously a type or an expression. (Phrased in reverse, if X parses as an expression, but never as a type, then X*[1] cannot parse as a type.) If X is ambiguous itself, you just shift the problem down one layer.

>

The code X*[0][0] could also be both. As an expression it would multiply X with [0][0], which is 0. As a type it would be an array of an array of a pointer to X. This example does not need operator overloading.

A more complex example also uses * as a pointer dereference: X**[Y][0]
This could be a type, if X is a type and Y is also a type or evaluates to an integer.
If X is an integer and Y is a pointer to an integer, then it would be a valid expression.

To change meaning silently, X would have to be an aggregate type that implements opBinary as a static function. This is really important because Nobody Writes Such Code.

I haven’t been able to come up with an example that necessitates a static operator function.

May 10, 2023

On Wednesday, 10 May 2023 at 14:12:33 UTC, Quirin Schroll wrote:

>

To change meaning silently, X would have to be an aggregate type that implements opBinary as a static function. This is really important because Nobody Writes Such Code.

I haven’t been able to come up with an example that necessitates a static operator function.

The parser does not know if X is a type or something else, because that is only determined later in semantic analysis. So the parser would need to prefer a type or expression. Semantic analysis could maybe change the chosen tree later if it is wrong.

But you are right, that it would not change the meaning silently without static opBinary. If the grammar is changed, then the user would get an error message.

May 10, 2023
On 5/7/2023 10:56 AM, David Gileadi wrote:
> On 5/6/23 5:50 PM, Walter Bright wrote:
> If you happen to be using Thunderbird, I made an add-on that renders Markdown messages pretty well.
> 
> https://addons.thunderbird.net/en-US/thunderbird/addon/render-markdown-messages/?src=search

I actually found and installed that last night! Thanks, it works great!