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

Make polymorphic functions more efficient and expressive #17548

Merged
merged 8 commits into from
Jun 21, 2023

Conversation

smarter
Copy link
Member

@smarter smarter commented May 20, 2023

This PR enhances polymorphic function types in two ways:

  • Dependent result types can now be inferred from the expected type
  • polymorphic lambdas are now implemented using JVM lambdas when possible instead of anonymous classes.

Additionally, we fix the logic for renaming bound variables when pretty-printing lambdas and fix the handling of this in refinements.

@smarter smarter force-pushed the poly-closure2 branch 5 times, most recently from 7535ede to 0022e5b Compare May 21, 2023 17:20
@smarter smarter requested a review from odersky May 21, 2023 20:35
Copy link
Contributor

@odersky odersky left a comment

Choose a reason for hiding this comment

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

Otherwise LGTM

@@ -151,7 +151,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
case class CapturesAndResult(refs: List[Tree], parent: Tree)(implicit @constructorOnly src: SourceFile) extends TypTree

/** Short-lived usage in typer, does not need copy/transform/fold infrastructure */
case class DependentTypeTree(tp: List[Symbol] => Type)(implicit @constructorOnly src: SourceFile) extends Tree
case class DependentTypeTree(tp: (List[TypeSymbol], List[TermSymbol]) => Type)(implicit @constructorOnly src: SourceFile) extends Tree
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe we simplify this so that a DependentTypeTree just takes a (tp: List[Symbol] => Type)?

Copy link
Member Author

Choose a reason for hiding this comment

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

Using a List[Symbol] means that we need to spend extra time concatenating and partitioning the parameters, and we need to document this behavior, so I'm not sure it's really more simple, but I'm happy to do the change if you prefer it that way.

Copy link
Contributor

Choose a reason for hiding this comment

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

I agree, in the end it does not seem to be a simplification. So OK to keep as is.

Copy link
Contributor

Choose a reason for hiding this comment

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

When I started my comment stream I thought it would come out simpler than it did 😄

val tpe = tpFun(termParamss.head)
// A lambda has at most one type parameter list followed by exactly one term parameter list.
val tpe = (paramss: @unchecked) match
case TypeSymbols(tparams) :: TermSymbols(vparams) :: Nil => tpFun(tparams, vparams)
Copy link
Contributor

Choose a reason for hiding this comment

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

If we simplify DependentTypeTrees, these would become:

  case TypeSymbols(tparams) :: TermSymbols(vparams) :: Nil => tpFun(tparams ++ vparams)
  case TermSymbols(vparams) :: Nil => tpFun(vparams)

val resultTpt = pt.dealias match
case RefinedType(parent, nme.apply, poly @ PolyType(_, mt: MethodType)) if parent.classSymbol eq defn.PolyFunctionClass =>
untpd.DependentTypeTree((tsyms, vsyms) =>
mt.resultType.substParams(mt, vsyms.map(_.termRef)).substParams(poly, tsyms.map(_.typeRef)))
Copy link
Contributor

Choose a reason for hiding this comment

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

If we simplify DependentTypeTrees, this would become:

   untpd.DependentTypeTree(syms =>
      val (tsyms, vsyms) = syms.partition(_.isType)
      mt.resultType.substParams(mt, vsyms.map(_.termRef)).substParams(poly, tsyms.map(_.typeRef)))

@odersky odersky assigned odersky and unassigned odersky Jun 6, 2023
@odersky
Copy link
Contributor

odersky commented Jun 7, 2023

So I am OK to merge as is.

@odersky odersky assigned odersky and unassigned odersky Jun 7, 2023
Previously, the expression

    val a: (x: Int) => Bar[x.type] = ??? : ((x: Int) => Foo[x.type])

lead to the following error:

    Found:    (x: Int) => Foo[x.type]
    Required: (x: Int) => Bar[x².type]

    where:    x  is a reference to a value parameter
              x² is a reference to a value parameter

There's two problems here:
1. In the second lambda, x is supposed to be renamed to x², but the binder
   wasn't renamed.
2. Since the binders introducing x are part of the printed types, there isn't
   any ambiguity and no renaming is actually needed.

(1.) is a straight-up bug in the printing logic that we fix. (2.) is maybe a
matter of opinion, but for clarity we choose to address it by using the same
superscript for every parameter reference with the same name whose binder has
been printed. This does meant that `(x: Int) => (x: Int) => x.type` won't be
disambiguated, but something like that would be better addressed by printing a
warning about name shadowing anyway.
This was dropped from the syntax, so this isn't needed anymore.
Reuse the existing `DependentTypeTree` mechanism already in place for
monomorphic lambdas to compute the result type of polymorphic lambdas based on
their expected type.
Previously, we desugared them manually into anonymous class instances,
but by using a Closure node instead, we ensure that they get translated
into indy lambdas on the JVM.

Also cleaned up and added a TODO in the desugaring of polymorphic function types
into refinement types since I realized that purity wasn't taken into account.
…uilds

This avoids finding compilation errors late in the development cycle. Having
explicit nulls on is especially important because typed and untyped trees are
now only distinguished using `Null`, so on the non-bootstrapped build we don't
get compiler errors when passing an untyped Tree where a typed Tree is expected.
Just like for monomorphic closures this isn't currently supported.
The check used to disallow any reference of the form `X.this.type` even though
`X` is never the refinement class.

This change mirrors the check done with `ThisType` a couple of line below.
@smarter smarter merged commit 7a683ab into scala:main Jun 21, 2023
@smarter smarter deleted the poly-closure2 branch June 21, 2023 11:27
@Kordyjan Kordyjan added this to the 3.4.0 milestone Aug 1, 2023
@Kordyjan Kordyjan added the release-notes Should be mentioned in the release notes label Nov 20, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
release-notes Should be mentioned in the release notes
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants