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

Class Lifter #266

Draft
wants to merge 75 commits into
base: hkmc2
Choose a base branch
from
Draft

Class Lifter #266

wants to merge 75 commits into from

Conversation

CAG2Mark
Copy link
Contributor

@CAG2Mark CAG2Mark commented Jan 27, 2025

Lifts classes and nested functions to the top-level.

Method

General idea: when functions have to capture local variables, we need to rewrite their parameter lists. For example:

fun f(arg) =
  let unused = 0
  fun g(arg2) = set arg1 = 1
  let foo = g
  foo(unused)
  arg

is transformed into

fun g(f_capture)(arg2) =
  set f_capture.arg1 = 1 
class f$capture(arg1)
fun f(arg) =
  let capture = f$capture(arg)
  let foo = g(capture)
  foo(unused)
  arg

Variables which are not mutated will be passed as a parameter directly.

TODO

  • Lift functions.
  • Lift classes:
    • Instantiate does not work at all
    • Functions within functions within classes are lifted out of the class. This is technically fine, but it's better to have them as class methods. However, we need private methods for this, which is not yet implemented.
    • Private variables can't be accessed by nested classes/functions after being lifted out.
    • Modules are not lifted properly
  • Only pass mutated variables through the capture class. Move other variables by passing them as an argument directly. Note that there are some subtle cases where this causes problems, such as
    fun f() =
      let x = g
      let y = 2
      fun g() = y
      x()
    so for now, we only do this when the function is not passed around as a first-class object. More thorough analysis could be done in another PR.
    • More advanced analysis is work-in-progress.
  • Replace calls with a single call where we can. For example
    fun f(arg) =
      let unused = 0
      fun g(arg2) = set arg1 = 1
      g(unused)
      arg
    could be rewritten as
    fun g(f_capture, arg2) =
      set f_capture.arg1 = 1
    fun g$(f_capture)(arg2) = g(f_capture, arg2)
    class f$capture(arg1)
    fun f(arg) =
      let capture = f$capture(arg)
      g(capture, unused)
      arg
    where the call g(unused) is symbolically replaced.
    ("lifting" curried functions is much easier than the lifting in general, so it can be done later)

@CAG2Mark CAG2Mark self-assigned this Jan 27, 2025
@LPTK
Copy link
Contributor

LPTK commented Jan 27, 2025

What's the intended scheme regarding mutable local variables?

FWIW, we should distinguish mutable from immutable variables. I intend to let mut be used to annotate pattern variables, like in Rust, and to reject re-assignments to non-mut variables. Note that this would still leave let x; if b do x = 1 well-formed.

When there are only immutable variables being captured, the scheme can be simpler.

@CAG2Mark CAG2Mark marked this pull request as draft January 27, 2025 10:12
@CAG2Mark
Copy link
Contributor Author

CAG2Mark commented Jan 27, 2025

What's the intended scheme regarding mutable local variables?

FWIW, we should distinguish mutable from immutable variables. I intend to let mut be used to annotate pattern variables, like in Rust, and to reject re-assignments to non-mut variables. Note that this would still leave let x; if b do x = 1 well-formed.

When there are only immutable variables being captured, the scheme can be simpler.

I don't distinguish between local variables that are mutated / not mutated for now. I intend to move every local variable that's referenced inside a nested class / function into a closure.

Would it be better to add the immutable local variables to each class/function directly?

P.S. seems I accidentally marked this PR as ready earlier, but it's definitely not. It's now marked as a draft.

@CAG2Mark
Copy link
Contributor Author

Added much more into to the the first comment.

@LPTK
Copy link
Contributor

LPTK commented Feb 6, 2025

As discussed in the meeting, it would be nice to emit a warning if lifted classes are used in instance tests or as first-class values, which is not supported yet.

And please add this test to the PR:

fun foo1(x, n) =
  class C()
  print(x is C)
  if n > 0 do foo1(C(), n - 1)

:lift
fun foo2(x, n) =
  class C()
  print(x is C)
  if n > 0 do foo2(C(), n - 1)

foo1(0, 1)
//│ > false
//│ > false

foo2(0, 1)
//│ > false
//│ > true

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.

3 participants