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

Automatically recover unique receivers if unsendable return types aren't used #612

Open
yorickpeterse opened this issue Oct 12, 2023 · 2 comments
Labels
compiler Changes related to the compiler feature New things to add to Inko, such as a new standard library module
Milestone

Comments

@yorickpeterse
Copy link
Collaborator

yorickpeterse commented Oct 12, 2023

Description

A pattern I've run into is the following:

You have a receiver foo typed as uni Foo and call a mutable method bar. Foo.bar is typed as fn mut bar(...) -> Option[uni Something]. The arguments passed are all sendable. At the time of writing, such a call is disallowed because the method requires a mutable receiver, and thus it isn't safe to return Option, because the receiver (foo) may retain references to it.

To work around this, one can sometimes do the following:

let foo = recover {
  let foo = recover foo # turn the uni Foo into a Foo

  foo.bar(...)
  foo # turn the Foo back into a uni Foo
}

This is valid because whatever bar returns won't outlive the recover block, and thus no aliases violate the uniqueness constraints of uni T types.

Applying this pattern manually however is painful/repetitive, so we should provide a way for the compiler to do this automatically. Specifically, I think we can generalize this to the following:

If the receiver is uni T, the arguments are sendable, then we allow non-sendable return types (even for mutable methods) if and only if the return value is not used in any way. Thus, this is valid:

foo.bar(...)
bla

But this is not:

let a = foo.bar(...)
bla

Neither is this (because the value is moved outside of the match arm):

match thing {
  case Whatever -> foo.bar(...)
  ...
}

Within these constraints, we can essentially treat foo.bar(...) similar to the explicit recover block, but without the manually written boilerplate.

Related work

Pony basically does this: https://tutorial.ponylang.io/reference-capabilities/recovering-capabilities.html#automatic-receiver-recovery, specifically the following (emphasis mine):

and the return type of the method is either sendable or isn't used at the call-site, then we can "automatically recover" the receiver.

Related issues

@yorickpeterse yorickpeterse added feature New things to add to Inko, such as a new standard library module compiler Changes related to the compiler labels Oct 12, 2023
@yorickpeterse yorickpeterse changed the title Allow returning non-sendable types on uni T receivers if the value is unused/ignored Automatically recover unique receivers if unsendable return types aren't used Oct 12, 2023
@yorickpeterse yorickpeterse modified the milestone: 0.14.0 Oct 14, 2023
@yorickpeterse yorickpeterse added this to the 0.18.0 milestone Oct 22, 2024
@yorickpeterse
Copy link
Collaborator Author

In the current type checking setup this is a bit tricky: we perform the uniqueness checks as part of the regular type checker, which does a single pass over each method body from top to bottom. This means that when we encounter some expression foo, we don't necessary know if it's used or not (i.e. if it's assigned, the last expression in a body, etc).

In theory we could special-case this: CheckMethodBody::expression is given a used argument, which is set to true when the expression resides in any other expression that takes ownership of the value (Return, DefineVariable, etc) or is the last expression in the list.

In light of #713, it might however be better to move the uniqueness checks into a separate pass entirely. For that particular issue we need a separate inference pass after type checking anyway, and keeping all uniqueness checking code in one place might make things easier to work with.

@yorickpeterse
Copy link
Collaborator Author

It might even be worth performing the checks against MIR instead of HIR. This way we have a much cleaner/simpler IR to work with, and one that's faster to iterate over. We don't need the check to be performed before lowering to MIR either, since MIR's correctness (structurally at least) is not influenced by the uniqueness rules being enforced.

In this setup, the HIR type checker is mainly meant to catch type errors that would result in nonsensical MIR being generated, such as calls to non-existing methods. Further/more complicated analysis would be performed on MIR, which is generally easier/more efficient to work with.

@yorickpeterse yorickpeterse modified the milestones: 0.18.0, 0.19.0 Dec 2, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
compiler Changes related to the compiler feature New things to add to Inko, such as a new standard library module
Projects
None yet
Development

No branches or pull requests

1 participant