Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Issue 809 - Should be possible to convert lazy argument to delegate #10264

Merged
merged 1 commit into from Aug 26, 2019

Conversation

RazvanN7
Copy link
Contributor

@RazvanN7 RazvanN7 commented Aug 2, 2019

&dg will extract the delegate, if dg is an lazy argument. I'm not sure if this needs a spec PR or not; if it does, where would it be appropriate to add it? Maybe the lazy parameters section [1]?

[1] https://dlang.org/spec/function.html#lazy-params

@dlang-bot
Copy link
Contributor

Thanks for your pull request and interest in making D better, @RazvanN7! We are looking forward to reviewing it, and you should be hearing from a maintainer soon.
Please verify that your PR follows this checklist:

  • My PR is fully covered with tests (you can see the coverage diff by visiting the details link of the codecov check)
  • My PR is as minimal as possible (smaller, focused PRs are easier to review than big ones)
  • I have provided a detailed rationale explaining my changes
  • New or modified functions have Ddoc comments (with Params: and Returns:)

Please see CONTRIBUTING.md for more information.


If you have addressed all reviews or aren't sure how to proceed, don't hesitate to ping us with a simple comment.

Bugzilla references

Auto-close Bugzilla Severity Description
809 enhancement Should be possible to convert lazy argument to delegate

Testing this PR locally

If you don't have a local development environment setup, you can use Digger to test this PR:

dub fetch digger
dub run digger -- build "master + dmd#10264"

Geod24
Geod24 previously requested changes Aug 2, 2019
Copy link
Member

@Geod24 Geod24 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love this change, but it needs a runnable test, not compilable.

@JinShil
Copy link
Contributor

JinShil commented Aug 2, 2019

Maybe the lazy parameters section [1]?

Yeah, makes sense to me.

}
}
}

exp.e1 = exp.e1.toLvalue(sc, null);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it ok that exp.e1 here and following all of this could be mutated by lines 6247 and 6248 if it is a TOK.variable?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be fine because it is only performing semantic analysis, thus enriching the AST. In order to be sure that you have a delegate there you need to do some transformations.

@RazvanN7
Copy link
Contributor Author

RazvanN7 commented Aug 2, 2019

@Geod24 Done.

RazvanN7 added a commit to RazvanN7/dlang.org that referenced this pull request Aug 2, 2019
@RazvanN7
Copy link
Contributor Author

RazvanN7 commented Aug 2, 2019

Spec change: dlang/dlang.org#2686

@JinShil
Copy link
Contributor

JinShil commented Aug 2, 2019

I'm having second thoughts about this. Suppose we were to some day introduce rvalue references. Would we then want the & operator to return a reference to the return value?

@aliak00
Copy link

aliak00 commented Aug 3, 2019

In addition to what JinShil said, I also think there's some inconsistency here. Currently taking the address of a delegate gives you a pointer to that delegate. If lazy is to be viewed as a delegate (as making sense according to the bug report) then there's an asymmetry between typeof(&lazyParam) and typeof(&delegateType). Is that ok?

@RazvanN7
Copy link
Contributor Author

RazvanN7 commented Aug 5, 2019

I'm having second thoughts about this. Suppose we were to some day introduce rvalue references. Would we then want the & operator to return a reference to the return value?

I think that if we were to introduce rvalue references (which I hope we will not), one solution might be to consider &dg as extracting the delegate and &dg() as having a reference to the return value.

If lazy is to be viewed as a delegate (as making sense according to the bug report) then there's an asymmetry between typeof(&lazyParam) and typeof(&delegateType). Is that ok?

Yes. Today, whenever you use lazyParam, the compiler interprets it as lazyParam() i.e. a call to a delegate; by using the & operator you specify to the compiler that you want to extract the delegate; you still have the call operator () to disambiguate whenever you want.

@aliak00
Copy link

aliak00 commented Aug 5, 2019

Yes. Today, whenever you use lazyParam, the compiler interprets it as lazyParam() i.e. a call to a delegate; by using the & operator you specify to the compiler that you want to extract the delegate; you still have the call operator () to disambiguate whenever you want.

Mhm. Ok. Are you sure? Below when I reference d it does not seem to be interpreted as d(). And if it was, then this patch would change the meaning of what &d is right?

import std;

alias D = ref int delegate();

ref f(lazy D d) {
    return d();
}

void main() {
    int x = 3;
    ref int g() {
        return x;
    }
    f(&g);
    writeln(x);
}

Is lazy D d supposed to behave like a D? It doesn't seem

@RazvanN7
Copy link
Contributor Author

RazvanN7 commented Aug 5, 2019

@aliak00 I don't see any references to d in your example. Do you mean to g? If yes, then this patch does not touch that case, because it extracts the delegate from lazy params only.

Is lazy D d supposed to behave like a D? It doesn't seem

Not really. lazy D d is not an lvalue, whereas D d is.

@aliak00
Copy link

aliak00 commented Aug 5, 2019

Hmm. Seems I didn't even finish my typing 😛 I did not mean g. d is referenced inside f. And if a reference to lazyParam is interpreted as lazyParam() then the reference to d should be interpreted as d() yes? Or did I misunderstand? In which case, &d should be &d()? and d() returns a delegate, which is addressable?

(the code i posted above uses d() and not d because the latter does not compile currently).

@RazvanN7
Copy link
Contributor Author

RazvanN7 commented Aug 5, 2019

Ok. I understand. Currently, how you've written the example it will not compile because d is a function call (however you write it d or d()), therefore an rvalue, and you cannot take the address of an rvalue.

With my patch, this does not change; you will have the same behavior, however, if you write return &d;, then you can extract the delegate of the lazy param and return a reference to it. How does that sound?

@aliak00
Copy link

aliak00 commented Aug 5, 2019

Ah true. Sorry, the example does not compile yes. Yeah I understand the proposal. It's just that my understanding of what a lazy T should be is maybe incorrect. So:

import std;

void main() {
    f0( { return 3; } );
    f1( { return 3; } );
}

void f0(int delegate() x) {
    auto a = x;
    auto b = &x;
    pragma(msg, typeof(b));
}
void f1(lazy int delegate() x) {
    auto a = x;
    auto b = &x;
    pragma(msg, typeof(b));
}

To me, if we say that lazyParam is evaluated as lazyParam() then the above two functions should compile and produce the same result no? It doesn't right now (should it?) so my understanding seems incorrect.

@RazvanN7
Copy link
Contributor Author

RazvanN7 commented Aug 5, 2019

int delegate() x is different than lazy int delegate() x in the respect that in the first case x is an lvalue while in the latter it is an rvalue. That is the only difference. You can check this out in the spec [1], the 3rd rule: "A lazy parameter cannot be an lvalue.".

That is why your first example compiles (you can take the address of a delegate() variable), but the second one does not: the lazy parameter is an rvalue.

I hope this explanation makes things a bit clearer.

[1] https://dlang.org/spec/function.html#lazy-params

@aliak00
Copy link

aliak00 commented Aug 5, 2019

Yep, finally got it! Thanks a lot @RazvanN7

@RazvanN7
Copy link
Contributor Author

RazvanN7 commented Aug 5, 2019

Don't mention it!

@thewilsonator thewilsonator dismissed Geod24’s stale review August 5, 2019 14:08

Test is now runnable

Copy link
Member

@Geod24 Geod24 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another thing which is not handled, much more fundamental:
At the moment lazy are scope delegate. Meaning they don't perform memory allocation.
If you can extract them, it means the extract delegate must be scope, and you need to add a test with -dip1000 otherwise it breaks safety.

@RazvanN7
Copy link
Contributor Author

RazvanN7 commented Aug 6, 2019

@Geod24 Nice catch! I added a fail compilation test.

@RazvanN7
Copy link
Contributor Author

RazvanN7 commented Aug 7, 2019

Autotester failure is unrelated.

*/
if (e1.op == TOK.variable)
if (e1.op == TOK.variable && !((cast(VarExp)e1).delegateWasExtracted))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use isVarExp()

* to the delegate anymore, but rather, the
* actual symbol.
*/
if (exp.e1.op == TOK.variable)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use isVarExp()

{
exp.e1 = exp.e1.expressionSemantic(sc);
exp.e1 = resolveProperties(sc, exp.e1);
if (exp.e1.op == TOK.call)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use isCallExp()

auto callExp = cast(CallExp)exp.e1;
if (callExp.e1.type.ty == Tdelegate)
{
VarExp ve2 = cast(VarExp)callExp.e1;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use isVarExp(). Also, how do you know callExp.e1 is a VarExp?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because resolvePropertiesX replaces the VarExp with a CallExp for which e1 is the initial VarExp. (look at line 1302 in expressionsem.d)

if (callExp.e1.type.ty == Tdelegate)
{
VarExp ve2 = cast(VarExp)callExp.e1;
ve2.delegateWasExtracted = true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not understand why "extracting a delegate" permanently alters the semantics?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expression semantic on an AddrExp will rewrite the AST so that we have the name of a delegate instead of variable; after that when resolve properties is called on the newly rewritten AST node, when it finds the var name it will try to replace it again as a CallExp, so we need to store the fact that the delegate was already extracted.

if (exp.e1.op == TOK.call)
{
auto callExp = cast(CallExp)exp.e1;
if (callExp.e1.type.ty == Tdelegate)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use type.toBasetype().ty, not type.ty

Copy link
Member

@WalterBright WalterBright left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my comments.

@WalterBright
Copy link
Member

I'm unsure as well that this won't simply produce other inconsistencies.

Is this a problem that actually needs solving? Note that the bugzilla issue gives a simple workaround.

@RazvanN7
Copy link
Contributor Author

RazvanN7 commented Aug 7, 2019

@WalterBright I made the changes. Thanks for your feedback.

Is this a problem that actually needs solving? Note that the bugzilla issue gives a simple workaround

It seems that folks want this as it is more expressive than the workaround.

@andralex
Copy link
Member

andralex commented Aug 7, 2019

Spec change: dlang/dlang.org#2686

I think this is worth moving forward with. cc @atilaneves

@andralex
Copy link
Member

andralex commented Aug 7, 2019

Let's see if @WalterBright is okay with the changes following the tech review.

On the semantics side, lazy has always been a black sheep - a delegate in disguise. Allowing extracting the underlying delegate brings it closer to normalcy.

@atilaneves
Copy link
Contributor

I like it but I'll defer to @WalterBright.

@RazvanN7
Copy link
Contributor Author

Ping.

@RazvanN7
Copy link
Contributor Author

Another respectful ping on this.

@thewilsonator
Copy link
Contributor

Adding 72h -> merge

@thewilsonator thewilsonator added the 72h no objection -> merge The PR will be merged if there are no objections raised. label Aug 21, 2019
@dlang-bot dlang-bot merged commit 0de4525 into dlang:master Aug 26, 2019
ntrel added a commit to ntrel/dmd that referenced this pull request Oct 25, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
72h no objection -> merge The PR will be merged if there are no objections raised. auto-merge Enhancement
Projects
None yet
9 participants