October 19, 2022

On Wednesday, 19 October 2022 at 08:29:20 UTC, mw wrote:

>

OK, I saw what they are talking about.

Basically, in Eiffel, all query @mustuse.

Here in D @mustuse is an remedy add-on, which can be specified by the programmers in the middle of the inheritance tree, the question is what if the pointers got casted either upwards or downwards?

(It's too late for me here today.)

My gut feeling is: make it simple, if a method is marked as @mustuse anywhere in the inheritance tree, that method in all the class levels of the inheritance tree became @mustuse!

October 19, 2022

On Wednesday, 19 October 2022 at 08:35:21 UTC, mw wrote:

>

On Wednesday, 19 October 2022 at 08:29:20 UTC, mw wrote:

>

OK, I saw what they are talking about.

Basically, in Eiffel, all query @mustuse.

Here in D @mustuse is an remedy add-on, which can be specified by the programmers in the middle of the inheritance tree, the question is what if the pointers got casted either upwards or downwards?

(It's too late for me here today.)

My gut feeling is: make it simple, if a method is marked as @mustuse anywhere in the inheritance tree, that method in all the class levels of the inheritance tree became @mustuse!

The correct rule is exactly what Mike quoted from my email discussion with Walter: implicit conversions may add @mustuse, but can never remove it.

From this, it follows that

  • a @mustuse method cannot override a non-@mustuse method.
  • a @mustuse class/interface cannot inherit from a non-@mustuse class/interface.

In other words, you cannot introduce @mustuse in a derived class; it must be present in the base class.

This is somewhat problematic for D, because we have a universal base class, Object, and neither it nor any of its methods are @mustuse.

October 19, 2022

On Wednesday, 19 October 2022 at 13:17:00 UTC, Paul Backus wrote:

>

On Wednesday, 19 October 2022 at 08:35:21 UTC, mw wrote:

>

On Wednesday, 19 October 2022 at 08:29:20 UTC, mw wrote:

>

OK, I saw what they are talking about.

Basically, in Eiffel, all query @mustuse.

Here in D @mustuse is an remedy add-on, which can be specified by the programmers in the middle of the inheritance tree, the question is what if the pointers got casted either upwards or downwards?

(It's too late for me here today.)

My gut feeling is: make it simple, if a method is marked as @mustuse anywhere in the inheritance tree, that method in all the class levels of the inheritance tree became @mustuse!

The correct rule is exactly what Mike quoted from my email discussion with Walter: implicit conversions may add @mustuse, but can never remove it.

From this, it follows that

  • a @mustuse method cannot override a non-@mustuse method.
  • a @mustuse class/interface cannot inherit from a non-@mustuse class/interface.

In other words, you cannot introduce @mustuse in a derived class; it must be present in the base class.

Let's concentrate on only @mustuse as a function attribute for now, since I have not checked the exact semantics of @mustuse as a type annotation.

There are two directions to propagate the attribute in the inheritance tree, upwards and downwards. We all agree on the direction of downwards propagation to the derived class methods, I.e in your expression: "may add @mustuse, but can never remove it".

Now I only need to convince you the other direction to upwards is also needed:

It's actually quite simple, let us consider the original example which started this discussion, and do this exercise:

class AbstractPaths {
AbstractPaths remove (int I) {...}
}

class Paths : AbstractPaths {
@mustuse
AbstractPaths remove (int I) {...}
}

AbstractPaths absPaths = new Paths();

absPaths.remove(i); // shall @mustuse be enforced here on absPaths?

Let's step back, and ask why we want to introduce @mustuse in the beginning?
The purpose of the annotation is to help programmers do not discard important return values accidentally as what the OP's author has experienced disaster.

Then the answer is very clear: we do want @mustuse be enforced on the absPaths.remove(i) call in the above example!

Otherwise, it will be a loophole in the @mustuse as a function attribute logic, which defeat its purpose.

Convinced?

And as a consequence, all the AbstractPaths' derived classes' remove(i) method now must have this annotation ( injected by the D compiler, the programmer does not have to manually add it in all the places). That is why I say:

if a method is marked as @mustuse anywhere in the inheritance tree, that method in all the class levels in the whole inheritance tree became @mustuse!

October 20, 2022
On 20/10/2022 5:31 AM, mw wrote:
> if a method is marked as @mustuse anywhere in the inheritance tree, that method in all the class levels in the whole inheritance tree became @mustuse!

Unfortunately people don't compile their program all at once.

So therefore this won't work.

I considered something along these lines, but its ultimately error prone to try to force an attribute on for a class hierarchy (including not allowing casts to parents).
October 19, 2022
On Wednesday, 19 October 2022 at 16:37:51 UTC, rikki cattermole wrote:
> On 20/10/2022 5:31 AM, mw wrote:
>> if a method is marked as @mustuse anywhere in the inheritance tree, that method in all the class levels in the whole inheritance tree became @mustuse!
>
> Unfortunately people don't compile their program all at once.

No, that's a limitation of current D compiler, or the build system dub.

Yes, for this logic to work, every compilation need to do global system analysis (that is what Eiffel compiler does).

Now the compiler writer have to work harder to implement this logic. Do not compromise!



> So therefore this won't work.
>
> I considered something along these lines, but its ultimately error prone to try to force an attribute on for a class hierarchy (including not allowing casts to parents).


October 20, 2022
On 20/10/2022 5:42 AM, mw wrote:
> On Wednesday, 19 October 2022 at 16:37:51 UTC, rikki cattermole wrote:
>> On 20/10/2022 5:31 AM, mw wrote:
>>> if a method is marked as @mustuse anywhere in the inheritance tree, that method in all the class levels in the whole inheritance tree became @mustuse!
>>
>> Unfortunately people don't compile their program all at once.
> 
> No, that's a limitation of current D compiler, or the build system dub.

Or Meson
Or CMake
Or make...
Or cli script

> Yes, for this logic to work, every compilation need to do global system analysis (that is what Eiffel compiler does).

But not for native compilers. They work in partial builds, this is core to how they work.

> Now the compiler writer have to work harder to implement this logic. Do not compromise!

No amount of work is going to change the fact that the compiler may not be aware that code exists.

rt package one example.

Shared libraries another.

October 19, 2022
On Wed, Oct 19, 2022 at 04:31:59PM +0000, mw via Digitalmars-d wrote: [...]
> class AbstractPaths {
>   AbstractPaths remove (int I) {...}
> }
> 
> 
> class Paths : AbstractPaths {
>   @mustuse
>   AbstractPaths remove (int I) {...}
> }
> 
> 
> AbstractPaths absPaths = new Paths();
> 
> absPaths.remove(i);  // shall @mustuse be enforced here on absPaths?

No, because it is not enforceable.  Consider:

	class MyPaths : AbstractPaths {
		// N.B.: no @mustuse
		override AbstractPaths remove (int I) {...}
	}

	AbstractPaths absPaths = new MyPaths;
	absPaths.remove(i); // shall @mustuse be enforced?

That is, given an instance of AbstractPaths, *you cannot tell* whether @mustuse should be enforced or not. If it's an instance of Paths, then it must be enforced, but if it's an instance of MyPaths, then it must not be.  Conclusion: we cannot enforce @mustuse in the base class AbstractPaths.


> Let's step back, and ask why we want to introduce @mustuse in the beginning?  The purpose of the annotation is to help programmers do not discard important return values accidentally as what the OP's author has experienced disaster.
> 
> Then the answer is very clear: we do want @mustuse be enforced on the absPaths.remove(i) call in the above example!

No, the correct answer is that the base class method must also be attributed with @mustuse.  It should be illegal to have a non-@mustuse base class method overridden by a @mustuse derived class method.

Why?  Consider the following scenarios:

1) Base class has @mustuse, derived class does not.  Then the base class's @mustuse can be circumvented by a derived class that omits the attribute, defeating the purpose of @mustuse in the base class.

2) Base class does not have @mustuse, but derived class does.  Then instances of the derived class, cast to the base class references, allow bypassing @mustuse in the derived class.

If indeed @mustuse's purpose is to prevent accidentally discarding
important return values, i.e., something marked @mustuse must *never* be
silently dropped, then neither (1) nor (2) is acceptable.

Conclusion: if the base class method has @mustuse, then so must the derived class method. If the base class method does not have @mustuse, then the derived class method cannot have it either.


[...]
> And as a consequence, all the AbstractPaths' derived classes' remove(i) method now must have this annotation ( injected by the D compiler, the programmer does not have to manually add it in all the places).

This is not enforceable, since the base class and derived class could be in two far-flung files, and due to incremental compilation the compiler cannot enforce @mustuse in an unmarked method in a class that might have some distant relative in the inheritance tree that has @mustuse.

The only way this can be done is to make it a compile error for a base class method and a derived class method to have a mismatch in the @mustuse attribute.


> That is why I say:
> 
> if a method is marked as @mustuse anywhere in the inheritance tree, that method in all the class levels in the whole inheritance tree became @mustuse!

This is correct.  But it cannot be implicit; if you attribute a method @mustuse somewhere in the inheritance tree, then you must also write @mustuse in every other class in the hierarchy. Otherwise it's not enforceable.


T

-- 
Winners never quit, quitters never win. But those who never quit AND never win are idiots.
October 19, 2022
On Wednesday, 19 October 2022 at 16:47:45 UTC, rikki cattermole wrote:
>
>> Yes, for this logic to work, every compilation need to do global system analysis (that is what Eiffel compiler does).
>
> But not for native compilers. They work in partial builds, this is core to how they work.

Eiffel is a native compiler, and they do support incremental build. They have a compilation technology called melting ice:

https://www.eiffel.org/doc/eiffelstudio/Melting_Ice_Technology

(In the past, I'm more a language theorist than a language implementator, so I didn't study the details of it.)

>> Now the compiler writer have to work harder to implement this logic. Do not compromise!
>
> No amount of work is going to change the fact that the compiler may not be aware that code exists.
>
> rt package one example.
>
> Shared libraries another.

Yes pre-built binaries is a concern, but since @mustuse now is a half-way introduced remedy (rather than D doing DbC right from the very beginning), I think we only need to enforce @mustuse on the source code that the compiler actually visit during each compilation, that will eliminate the problems the programmer has control with. (for pre-built binaries that the programmer have no control, there is no way to apply the enforcement either).


October 19, 2022
Right now in this discussion thread there are two aspects:
1) the language logic, and
2) the implementation logic (and current status, including pre-built binaries).

I'm not interested in 2). We need to first make the language logic correct.

> If indeed @mustuse's purpose is to prevent accidentally discarding
> important return values, i.e., something marked @mustuse must *never* be
> silently dropped, then neither (1) nor (2) is acceptable.

That's because you still consider the @mustuse attribute on each class level individually, if you think about the global system analysis as a whole, it will work, and consistently. Yes, what I proposed is transitive closure:

    /--------Base------\
    |        |         |
Derived1   Derive2  Derive3

If the programmer only manually marked Derive3.remove() as @mustuse, everything else (Base, Derived1   Derive2  Derive3)'s .remove() method will become @mustuse (as seen by the compiler internally).


> This is not enforceable, since the base class and derived class could be in two far-flung files, and due to incremental compilation the compiler cannot enforce @mustuse in an unmarked method in a class that might have some distant relative in the inheritance tree that has @mustuse.

(all these comments are compiler implementation issues (2), which I don't want to comment)


>> That is why I say:
>> 
>> if a method is marked as @mustuse anywhere in the inheritance tree, that method in all the class levels in the whole inheritance tree became @mustuse!
>
> This is correct.  But it cannot be implicit; if you attribute a method @mustuse somewhere in the inheritance tree, then you must also write @mustuse in every other class in the hierarchy. Otherwise it's not enforceable.

I'm glad you agree with my global analysis as a whole view here.

(I want the compiler to propagate and inject such attribute is only to save the programmers manual work, by no means it means it's implicit.

As to the compiler implementation, I'll let Walter figure it out).

October 19, 2022

On Wednesday, 19 October 2022 at 16:31:59 UTC, mw wrote:

>

On Wednesday, 19 October 2022 at 13:17:00 UTC, Paul Backus wrote:

>

From this, it follows that

  • a @mustuse method cannot override a non-@mustuse method.
  • a @mustuse class/interface cannot inherit from a non-@mustuse class/interface.

In other words, you cannot introduce @mustuse in a derived class; it must be present in the base class.

Let's concentrate on only @mustuse as a function attribute for now, since I have not checked the exact semantics of @mustuse as a type annotation.

There are two directions to propagate the attribute in the inheritance tree, upwards and downwards. We all agree on the direction of downwards propagation to the derived class methods, I.e in your expression: "may add @mustuse, but can never remove it".

You have this completely backwards. A derived class may remove @mustuse, but can never add it.

In your example, attempting to add @mustuse to Paths.remove would result in a compile-time error:

class AbstractPaths {
  AbstractPaths remove (int I) {...}
}


class Paths : AbstractPaths {
  @mustuse
  AbstractPaths remove (int I) {...}
  // Error: @mustuse method cannot override method without @mustuse
}