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 this being captured by immediately invoked functions #123

Merged
merged 7 commits into from
Jul 27, 2022

Conversation

chengluyu
Copy link
Member

This is mentioned in #65 .

:js
class A: { x: int }
  method MA = let _ = 1 in this.x
//│ Defined class A
//│ Defined A.MA: A -> int
//│ // Prelude
//│ class A {
//│   constructor(fields) {
//│     this.x = fields.x;
//│   }
//│   get MA() {
//│     return ((function (_) {
//│       return this.x;
//│     })(1));
//│   }
//│ }
//│ // End of generated code

(A { x = 1 }).MA
//│ res: int
//│ Runtime error:
//│   TypeError: Cannot read properties of undefined (reading 'x')

@LPTK
Copy link
Contributor

LPTK commented Jul 19, 2022

Cool!

But does this really solve the problem in general? What if there is a captured this in a generated local function?

@chengluyu
Copy link
Member Author

Sadly this PR only works in this particular case.

The root of the problem is we implement let and pattern matches with IIFEs. IIFEs introduce extra function scopes in generated code, which shadow this. Although we can keep track of runtime scopes and add alias variable declarations but that would be cumbersome.

A better solution is to avoid IIFEs and generate a series of JavaScript statements.

@LPTK
Copy link
Contributor

LPTK commented Jul 20, 2022

Why not just add a const self = this at the beginning of methods and then refer to this self instead? It should be a trivial change that would avoid mistranslating the semantics of MLscript.

@LPTK
Copy link
Contributor

LPTK commented Jul 20, 2022

Of course we need to make sure that self itself is not shadowed. I think your Scope infrastructure should be able to deal with that.

Also, if the self symbol is never accessed in the method body, we can omit the binding in the generated code.

@chengluyu
Copy link
Member Author

It's done.

@LPTK
Copy link
Contributor

LPTK commented Jul 20, 2022

Please ad some tests making sure that this self cannot be shadowed.

@chengluyu
Copy link
Member Author

chengluyu commented Jul 20, 2022

I added some test cases but found that shadowing this caused problems. The reason is that we didn't allocate runtime names for parameters (as well as parameter patterns) in translatePattern. The latest commit fixes this. I also added test cases for illegal parameters.

@LPTK
Copy link
Contributor

LPTK commented Jul 21, 2022

Ok, now that #117 is merged you can adapt your changes here and we can merge it.

@chengluyu chengluyu force-pushed the fix/capturing-this branch from dc35036 to 55aa401 Compare July 22, 2022 06:35
@chengluyu
Copy link
Member Author

chengluyu commented Jul 22, 2022

I rebased the branch. This PR is ready.

Copy link
Contributor

@LPTK LPTK left a comment

Choose a reason for hiding this comment

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

Ok, LGTM.

But it actually occurred to me that we could also define self in the class scope, instead of in each method, which would produce leaner code. As in:

class C { self = this; ... }

Would that be easy to change now?

shared/src/main/scala/mlscript/codegen/Scope.scala Outdated Show resolved Hide resolved
shared/src/test/diff/mlscript/TypeClasses.mls Outdated Show resolved Hide resolved
@chengluyu
Copy link
Member Author

chengluyu commented Jul 27, 2022

Ok, LGTM.

But it actually occurred to me that we could also define self in the class scope, instead of in each method, which would produce leaner code. As in:

class C { self = this; ... }

Would that be easy to change now?

There had been some trouble implementing this. Now I think self = this is a temporary and ugly fix. It will be unnecessary after we remove IIFEs. So let’s not do it right now.

@LPTK
Copy link
Contributor

LPTK commented Jul 27, 2022

Now I think self = this is a temporary and ugly fix. It will be unnecessary after we remove IIFEs.

I disagree. JS semantics is just stupid and we should NOT follow it, so the self-fix will be needed in general.

The following MLscript (using the future syntax):

class A {
  fun m() =
    fun f() = this.x
    f
}

should generate this JS:

class A {
  m() {
    let self = this
    function f() { return self.x }
    return f
  }
}

or alternatively:

class A {
  #self = this
  m() {
    function f() { return #self.x }
    return f
  }
}

But it's okay if we don't do the latter for now if it's too annoying to implement.

@chengluyu
Copy link
Member Author

Now I think self = this is a temporary and ugly fix. It will be unnecessary after we remove IIFEs.

I disagree. JS semantics is just stupid and we should NOT follow it, so the self-fix will be needed in general.

The following MLscript (using the future syntax):

class A {
  fun m() =
    fun f() = this.x
    f
}

should generate this JS:

class A {
  m() {
    let self = this
    function f() { return self.x }
    return f
  }
}

or alternatively:

class A {
  #self = this
  m() {
    function f() { return #self.x }
    return f
  }
}

But it's okay if we don't do the latter for now if it's too annoying to implement.

This is another case. It’s not IIFE. In your case, we should create alias because of the nested function f. It should only be created when the body of f refers to this.

But now we add the alias anyway. That's why I say it’s ugly because the generated code is not easy to understand. In the future, we will deprecate IIFEs for pattern matching and let expressions. Then we can remove the fix in this PR. By the time when the nested function syntax arrives, we should add anther mechanism which prevents inner functions shadowing this.

Co-authored-by: Lionel Parreaux <lionel.parreaux@gmail.com>
@LPTK
Copy link
Contributor

LPTK commented Jul 27, 2022

By the time when the nested function syntax arrives, we should add anther mechanism which prevents inner functions shadowing this.

But why add another mechanism? It's the same problem. The fix could be improved later, but there is no reason to just remove it.

@LPTK LPTK merged commit 30400f9 into mlscript Jul 27, 2022
@LPTK LPTK deleted the fix/capturing-this branch July 27, 2022 07:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants