December 06, 2019
On Friday, 6 December 2019 at 12:47:14 UTC, berni44 wrote:
> On Friday, 6 December 2019 at 06:49:41 UTC, berni44 wrote:
>> int a = void;
>>
>> static if (!__traits(compiles, a?a:a))
>>
>> But I'm not sure if ?: can be applied to all thinkable types.
>
> Even that doesn't work (don't know, why I got this morning the impression, that it does).
>
>
> Is the following a bug in __traits?

Probably not, because foo(…) could have been separately compiled in a different file unit?

However, I think this and many other cases show that Walter is on the right track by using flow-analysis, but that D as a language would be better served by embracing Flow-typing whole-heartedly and take flow-analysis to the next level rather than doing it here-and-there.

E.g. tracking of constness/immutability/nullability/initialization/aliasing etc can be done in new and intersting way if D adds flowtyping in a wholesome manner.

It would probably require a new IR and perhaps break too much, so you would end up with D3m but it would be great from a metaprogramming perspective.

December 08, 2019
On 12/6/19 1:49 AM, berni44 wrote:
> On Thursday, 5 December 2019 at 19:45:27 UTC, Steven Schveighoffer wrote:
>> What is the point of formatting items that aren't initialized? You would be printing garbage.
>>
>> What if you just initialize the items you wish to print,
> 
> You are on the wrong track... I'm programming inside of phobos. Such questions do not arise there. If the user provides a struct with (partially) uninitialized items std.format has to cope with this somehow. We cannot really answer the question, why the user is doing this, nor can we make him initialize the items before printing.

The normal compiled code works correctly during normal execution. It prints garbage. What you are looking to do is do this at compile-time, which is not allowed.

Again, just don't do that.

> 
> Ali's example shows, that this is a serious issue. IMHO my example should print `Foo(void)` or `Foo([void, void, void])` with `int[3]`. With that, Ali's example will work.

Ali's example is different, because it's given an *actually initialized* structure, not one that is uninitialized, and the compiler complains. I'd consider that an actual bug (in Phobos I would guess, not the compiler).

What you want is for CTFE to have some mechanism to deal with uninitialized data aside from an error (which is perfectly acceptable).

> 
> For me, the question remains, how to detect (at compile time) if a variable is void. The best I could come up with yet is:
> 
> int a = void;
> 
> static if (!__traits(compiles, a?a:a))
> 
> But I'm not sure if ?: can be applied to all thinkable types.

You are thinking incorrectly about CTFE ;) It's executing an *already compiled* function, at compile time. There is no static determination of anything at compile-time for CTFE function parameters. This is why the __ctfe variable is a runtime variable, and not a compile-time one.

So because you can access the variable at runtime (runtime D doesn't reject accesses to uninitialized data, as it doesn't know that has happened), it compiles and will try to run in CTFE.

In fact, you can pass it in uninitialized data, and as long as CTFE doesn't interpret any code that will read it, it will work.

For example, this works:

```
struct Foo
{
   int i = 5;
   int a = void;
   string toString() { import std.conv; return i.to!string; }
}

import std.format;
static x = format("%s", Foo());
```

It's hard to have discussions about abstract types that obviously are not useful. Maybe if you have a more likely example, you can get help finding the right course of action.

-Steve
December 08, 2019
On Sunday, 8 December 2019 at 18:16:29 UTC, Steven Schveighoffer wrote:
>> Ali's example shows, that this is a serious issue. IMHO my example should print `Foo(void)` or `Foo([void, void, void])` with `int[3]`. With that, Ali's example will work.
>
> Ali's example is different, because it's given an *actually initialized* structure, not one that is uninitialized, and the compiler complains. I'd consider that an actual bug (in Phobos I would guess, not the compiler).

Well actually format uses only the *type* S of the second parameter and uses S.init to check if that can be printed (if it would use the real parameter, that could have unwanted side effects I guess). It cannot print it, because S.init contains an uninitialized member. This produces the error "cannot read uninitialized variable i in CTFE". So, at that time, the code is somehow able to recognize, that the variable "i" is uninitialized, else it could not produce this error. What I'm looking for is a way, to catch that. That would solve the issue.

> You are thinking incorrectly about CTFE ;) It's executing an *already compiled* function, at compile time.

That's indeed something, I did not know. :-)

> It's hard to have discussions about abstract types that obviously are not useful. Maybe if you have a more likely example, you can get help finding the right course of action.

As I wrote above, I was looking into issue 19769. This is the test given there:

```
import std.uni;
import std.format;
static immutable x = format("%s", cast(const)"test".asCapitalized);
```

This doesn't compile, because asCapitalized returns a struct (ToCapitalizerImpl), which contains uninitialized data (dchar[3] buf = void;) and format tries to print that. It first tries to print it as a range, but isInputRange misses somehow, that this struct is a range (probably because of the cast(const), but I don't know exactly) and therefore formatValueImpl tries to print the members of that struct directly, but as buf is not initialized, that fails. And the result is a strange error message (about uninitialized variables, where everything looks initialized).

This bug could be fixed by making isInputRange recognize the struct as a range. But while I looked, what happens, I found out, that printing structs with uninitialized data fails (but with an errormessage, that says, that this data is uninitialized). I thought, it would be nice, if format would just print <void> instead of throwing an error and hence my question if doing so is possible.
December 08, 2019
On 12/8/19 2:41 PM, berni44 wrote:
> As I wrote above, I was looking into issue 19769. This is the test given there:
> 
> ```
> import std.uni;
> import std.format;
> static immutable x = format("%s", cast(const)"test".asCapitalized);
> ```
> 
> This doesn't compile, because asCapitalized returns a struct (ToCapitalizerImpl), which contains uninitialized data (dchar[3] buf = void;) and format tries to print that. It first tries to print it as a range, but isInputRange misses somehow, that this struct is a range (probably because of the cast(const), but I don't know exactly) and therefore formatValueImpl tries to print the members of that struct directly, but as buf is not initialized, that fails. And the result is a strange error message (about uninitialized variables, where everything looks initialized).


It definitely is the cast(const). Removing it works. And it makes sense -- a const type cannot be a range, as popFront can never be const.

So one thing you CAN do, in cases like this is just initialize the damn void stuff in CTFE. i.e. here: https://github.com/dlang/phobos/blob/a24888e533adfe8d141eb598be22a50df5e26a66/std/uni.d#L9390

change to:

auto result = ToCapitalizerImpl(str);
if(__ctfe) result.buf[] = dchar.init;
return result;

> 
> This bug could be fixed by making isInputRange recognize the struct as a range. But while I looked, what happens, I found out, that printing structs with uninitialized data fails (but with an errormessage, that says, that this data is uninitialized). I thought, it would be nice, if format would just print <void> instead of throwing an error and hence my question if doing so is possible.

OK, I see what you mean. Confusing error messages can be indeed a huge problem.

But let's consider the fix I outline above. In this case, instead of "Test", like he expects, he's going to get x equal to (yes, I did it at runtime to see what it would be):

const(ToCapitalizerImpl)(const(Result)(const(ByCodeUnitImpl)("test"), 4294967295), const(ToCaserImpl)(const(Result)(const(ByCodeUnitImpl)(""), 4294967295), 0, "\0\0\0"), false, "\0\0\0", 0)

Which is going to result in a *different* confusion. Even if it said "<void>" like you want, it's still a pile of gibberish that's far from what you expect.

So which is better? A compiler error saying essentially in a long and confusing way, "dude, you really don't want to do that", or a resulting binary that has, um... that other thing instead of "Test"?

-Steve
December 09, 2019
On Sunday, 8 December 2019 at 20:18:23 UTC, Steven Schveighoffer wrote:
> It definitely is the cast(const). Removing it works. And it makes sense -- a const type cannot be a range, as popFront can never be const.

Nit-picking: A infinite range producing always the same item could be....

> So one thing you CAN do, in cases like this is just initialize the damn void stuff in CTFE.

Hmm. Feels a little bit like healing symptoms. We would need to identify all such places in Phobos. I'm not sure, if this is, what I'd like to see. (And it doesn't fix Ali's examples.) But I havn't got a better idea yet.

> const(ToCapitalizerImpl)(const(Result)(const(ByCodeUnitImpl)("test"), 4294967295), const(ToCaserImpl)(const(Result)(const(ByCodeUnitImpl)(""), 4294967295), 0, "\0\0\0"), false, "\0\0\0", 0)

Indeed, not really, what one wants to have...

> Which is going to result in a *different* confusion. Even if it said "<void>" like you want, it's still a pile of gibberish that's far from what you expect.

So, what would we expect? Ideally, it would note, that it's not a good idea to make a range const. But I think, that neither the compiler (does not know about ranges) nor Phobos (may not even be called) can do that.

Yet I've got no clue, what's best here...
December 09, 2019
On 12/9/19 1:10 AM, berni44 wrote:

> So, what would we expect? Ideally, it would note, that it's not a good idea to make a range const. But I think, that neither the compiler (does not know about ranges) nor Phobos (may not even be called) can do that.
> 
> Yet I've got no clue, what's best here...

This is a frequent problem when building generic "do the best you can" libraries.

Those libraries usually go something like this:

static if(someFeatureWorks!T)
{
   useFeature(t);
}
else
{
   fallback(t);
}

Generally the "someFeatureWorks" (e.g. isRange) works on the assumption that you can compile something. The problem is, that there are various cases why you can't compile it, not all of which mean it wasn't *intended* to compile. For example, having an error in one of your functions makes it all of a sudden not a range!

One thing we could do (well, actually, we can't do this because it would be too disruptive) is change the check that you can compile a call to popFront into hasMember!(T, "popFront"). What this does is capture the intention -- if the user put in a popFront method, I can't imagine they wanted to declare anything but a range type.

Then the code tries to use it as a range and it fails because you didn't do something correctly (i.e., you made it const, or you have a bug in your popFront function, etc.).

I've been meaning to write a blog post on this, but haven't had any time for it.

-Steve
December 09, 2019
On Monday, 9 December 2019 at 16:26:10 UTC, Steven Schveighoffer wrote:
> Then the code tries to use it as a range and it fails because you didn't do something correctly (i.e., you made it const, or you have a bug in your popFront function, etc.).

Yeah, the disadvantage of ducktyping...

1 2
Next ›   Last »