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

Support by-name implicits with recursive dictionaries #6050

Merged
merged 9 commits into from Jul 10, 2018

Commits on Jul 10, 2018

  1. Implementation of byname implicits with recursive dictionaries

    This is an implementation of byname implicit parameters with recursive
    dictionaries, intended as a language-level replacement for shapeless's
    Lazy type. As of this commit, implicit arguments can be marked as byname
    and at call sites recursive uses of implicit values are permitted if
    they occur in an implicit byname argument.
    
    Consider the following example,
    
      trait Foo { def next: Foo }
      object Foo {
        implicit def foo(implicit rec: Foo): Foo =
          new Foo { def next = rec }
      }
      val foo = implicitly[Foo]
      assert(foo eq foo.next)
    
    In current Scala this would diverge due to the recursive implicit
    argument rec of method foo. Under the scheme implemented in this commit
    we can mark the recursive implicit parameter as byname,
    
      trait Foo { def next: Foo }
      object Foo {
        implicit def foo(implicit rec: => Foo): Foo =
          new Foo { def next = rec }
      }
      val foo = implicitly[Foo]
      assert(foo eq foo.next)
    
    and recursive occurrences of this sort are extracted out as val members
    of a local synthetic object as follows,
    
      val foo: Foo = scala.Predef.implicitly[Foo](
        {
          object LazyDefns$1 {
            val rec$1: Foo = Foo.foo(rec$1)
                             //      ^^^^^
                             // recursive knot tied here
          }
          LazyDefns$1.rec$1
        }
      )
      assert(foo eq foo.next)
    
    and the example compiles with the assertion successful. Note that the
    recursive use of rec$1 occurs within the byname argument of foo and is
    consequently deferred. The desugaring matches what a programmer would do
    to construct such a recursive value explicitly.
    
    This general pattern is essential to the derivation of type class
    instances for recursive data types, one of shapeless's most common
    applications.
    
    Byname implicits have a number of benefits over the macro implementation
    of Lazy in shapeless,
    
    + the implementation of Lazy in shapeless is extremely delicate, relying
      on non-portable compiler internals. By contrast there is already an
      initial implementation of byname implicits in Dotty:
      scala/scala3#1998.
    + the shapeless implementation is unable to modify divergence checking,
      so to solve recursive instances it effectively disables divergence
      checking altogether ... this means that incautious use of Lazy can cause
      the typechecker to loop indefinitely. The byname implicits
      implementation is able to both solve recursive occurrences and check for
      divergence.
    + the implementation of Lazy interferes with the heuristics for solving
      inductive implicits in scala#6481 because the latter depends on being able to
      verify that induction steps strictly reduce the size of the types being
      solved for; the additional Lazy type constructors make the type appear
      be non-decreasing in size. Whilst this could be special-cased, doing so
      would require some knowledge of shapeless to be incorporated into the
      compiler. Being a language-level feature, byname implicits can be
      accommodated directly in the induction heuristics.
    + in common cases more implicit arguments would have to be marked as
      Lazy than would have to be marked as byname under this PR due to
      restrictions on what the Lazy macro is able to do. Given that there is a
      runtime cost associated with capturing the thunks required for both Lazy
      and byname arguments, any reduction in the number is beneficial.
    milessabin committed Jul 10, 2018
    Copy the full SHA
    682fec0 View commit details
    Browse the repository at this point in the history
  2. Use finer grained fresh name creators in by-name implicit desugaring

    This is the new way to freshen names while keeping compiler output
    stable when sources are re-ordered or incrementally recompiled.
    retronym authored and milessabin committed Jul 10, 2018
    Copy the full SHA
    5bf4f02 View commit details
    Browse the repository at this point in the history
  3. Copy the full SHA
    34ffae3 View commit details
    Browse the repository at this point in the history
  4. Copy the full SHA
    5b480f0 View commit details
    Browse the repository at this point in the history
  5. Adjust owner of byname implicit RHS

    An implicit macro within the by-name implicit dictionary might
    return a typechecked tree, so we ought to use changeOwner when
    moving that code into the RHS of a synthetic member of `LazyDefns`
    retronym authored and milessabin committed Jul 10, 2018
    Copy the full SHA
    5dd9909 View commit details
    Browse the repository at this point in the history
  6. Copy the full SHA
    ac35aeb View commit details
    Browse the repository at this point in the history
  7. Copy the full SHA
    eb05421 View commit details
    Browse the repository at this point in the history
  8. Copy the full SHA
    c88c64e View commit details
    Browse the repository at this point in the history
  9. Minor code cleanups in Contexts

    retronym authored and milessabin committed Jul 10, 2018
    Copy the full SHA
    882881b View commit details
    Browse the repository at this point in the history