Skip to content

Transform/extension methods #140

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

Merged
merged 47 commits into from
Jul 17, 2014

Conversation

odersky
Copy link
Contributor

@odersky odersky commented Jul 5, 2014

New phase ported: extension methods

val forwarder = ref(extensionMeth.termRef)
.appliedToTypes(origTParams.map(_.typeRef))
.appliedToArg(This(origClass))
.appliedToArgss(vparamss.nestedMap(vparam => ref(vparam.symbol)))
Copy link
Member

Choose a reason for hiding this comment

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

Does this handle repeated parameters? This is handled in Scala 2 with:

// TreeGen
  def paramToArg(vparam: Symbol): Tree =
    paramToArg(Ident(vparam), isRepeatedParamType(vparam.tpe))

  def paramToArg(vparam: ValDef): Tree =
    paramToArg(Ident(vparam.name), treeInfo.isRepeatedParamType(vparam.tpt))

  def paramToArg(arg: Ident, isRepeatedParam: Boolean): Tree  =
    if (isRepeatedParam) wildcardStar(arg) else arg

  /** Make forwarder to method `target`, passing all parameters in `params` */
  def mkForwarder(target: Tree, vparamss: List[List[Symbol]]) =
    (target /: vparamss)((fn, vparams) => Apply(fn, vparams map paramToArg))

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Repeated parameters are already eliminated in Typer. See makeVarArg in Applications.scala.

Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure how code in the typer would kick in for the body of this generated forwarder.

Here's the test case I was trying to run:

class Testable(val c: String) extends AnyVal {
  def matching(cases: Boolean*) = cases contains true
}

object Test extends App {
  assert(new Testable("").matching(true, false))
}

(Here's the related bug fix in the old impl: scala/scala@1e0a30a)

I patched a few things, but didn't quite get through the intervening problems:

diff --git a/bin/dotc b/bin/dotc
index eaaf9c7..11df533 100755
--- a/bin/dotc
+++ b/bin/dotc
@@ -6,3 +6,3 @@
 # Configuration
-SCALA_VERSION=2.11.0-M7
+SCALA_VERSION=2.11.0-RC3
 DOTTY_VERSION=0.1
diff --git a/src/dotty/tools/dotc/core/Symbols.scala b/src/dotty/tools/dotc/core/Symbols.scala
index 6421018..bd82c58 100644
--- a/src/dotty/tools/dotc/core/Symbols.scala
+++ b/src/dotty/tools/dotc/core/Symbols.scala
@@ -393,3 +393,2 @@ object Symbols {
     final def isDerivedValueClass(implicit ctx: Context): Boolean =
-      false && // value classes are not supported yet
       isClass && denot.derivesFrom(defn.AnyValClass) && !isPrimitiveValueClass
@@ -465,3 +464,3 @@ object Symbols {
       if (lastDenot == null) s"Naked$prefixString#$id"
-      else lastDenot.toString// +"#"+id // !!! DEBUG
+      else lastDenot.toString +"#"+id // !!! DEBUG

diff --git a/src/dotty/tools/dotc/core/transform/Erasure.scala b/src/dotty/tools/dotc/core/transform/Erasure.scala
index e35cdd1..e7c544a 100644
--- a/src/dotty/tools/dotc/core/transform/Erasure.scala
+++ b/src/dotty/tools/dotc/core/transform/Erasure.scala
@@ -163,3 +163,4 @@ class Erasure(isJava: Boolean, isSemi: Boolean, isConstructor: Boolean, wildcard
   private def eraseDerivedValueClassRef(tref: TypeRef)(implicit ctx: Context): Type =
-    unsupported("eraseDerivedValueClass")
+    defn.AnyRefType
+//    unsupported("eraseDerivedValueClass")

diff --git a/src/dotty/tools/dotc/transform/ExtensionMethods.scala b/src/dotty/tools/dotc/transform/ExtensionMethods.scala
index 55b53ef..3abf90c 100644
--- a/src/dotty/tools/dotc/transform/ExtensionMethods.scala
+++ b/src/dotty/tools/dotc/transform/ExtensionMethods.scala
@@ -57,3 +57,3 @@ class ExtensionMethods extends MacroTransform with IdentityDenotTransformer { th
         val alts = decl.alternatives
-        val index = alts indexOf imeth
+        val index = alts indexOf imeth.denot
         assert(index >= 0, alts+" does not contain "+imeth)
@@ -163,2 +163,3 @@ class ExtensionMethods extends MacroTransform with IdentityDenotTransformer { th
             .withPos(rhs.pos)
+          println(forwarder)
           cpy.DefDef(tree, mods, name, tparams, vparamss, tpt, forwarder)
Exception in thread "main" java.util.NoSuchElementException: key not found: class Testable#5035
    at scala.collection.MapLike$class.default(MapLike.scala:228)
    at scala.collection.AbstractMap.default(Map.scala:59)

@odersky
Copy link
Contributor Author

odersky commented Jul 7, 2014

Reviewers: Hold off with the extension methods pull request for a bit. I'll have to do some proper testing first. This should not have been a pull request when it was first sent. I just wanted to check whether Travis build would work (because in a previous commit, it didn't), but pushing to staging should have been enough for that.

@odersky
Copy link
Contributor Author

odersky commented Jul 10, 2014

ExtensionMethods now works and passes the tests. So, ready for review, I think. @retronym @DarkDimius , want to give it a go? Thanks!

@odersky
Copy link
Contributor Author

odersky commented Jul 10, 2014

Rebased on master and force pushed

@odersky
Copy link
Contributor Author

odersky commented Jul 10, 2014

There are 5 tests which fail. These are all neg tailrec tests, and the failures are because tailrec is not run. @DarkDimius: Once @tailrec passes -Ycheck can you re-enable it and push on this branch?

import NameOps._
import TypeUtils._

/** A transformer that provides a convenient way to create companion objects
Copy link
Contributor

Choose a reason for hiding this comment

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

Comment is wrong

@odersky
Copy link
Contributor Author

odersky commented Jul 13, 2014

@DarkDimius Have a look at the latest commit. I believe it contains what you need for making type-correct tailrec methods.

retronym referenced this pull request in dotty-staging/dotty Jul 14, 2014
The previous version of ExtensionMethods added extension methods manually,
in the companion objetct scope, using enteredAfter. Extension methods therefore
had to run before pickling, so that other modules would see the generated extension methods.
This is suboptimal, for two reasons; (1) Mixin is very much like extension methods, yet it
has to run after pickling. (2) The pickling info gets larger than it has to be.

With this commit, extension methods are added as an InfoTransformer. So extension methods
now should run after pickling.
@DarkDimius
Copy link
Contributor

Will die with NullPointerException transforming such value class:

trait That1[A]
class T[A, This <: That1[A]] extends AnyVal {
  self: This =>
  var next: This = _
  final def loop(x: This, cnt: Int): Int = loop(x, cnt + 1)
}

odersky added 12 commits July 17, 2014 11:01
This is done to streamline changing class denotations in new phases
by adding to (or otherwise modifying) their decls scope.
Should do the same with other name-creator/name-test pairs.
Added the following utility methods:

 - polyDefDef: Create a DefDef given a function that takes type and value parameters and yields a body.
 - appliedToTypeTrees: Apply function to type arguments ion a TypeApply if arguments are nonempty.
 - mkAsInstanceOf
 - ensureConforms: generate a cast if expression has non-conforming type.
Added this case, so that .symbol on a ThisType returns the underlying class.
(1) Make sure ModifierFlags is TermFlags and TypeFlags
(2) Shorten private <local> to private[this]; same with protected
(3) Print [this] for local symbols in RefinedPrinter
Rewrote SuperAccessors (more to be done; see comments), and
added stuff here and there to make it work smoother.
... for the common supertype of MethodType, PolyType, and ExprType.
Signed was confusing.
The problem is that when an installAfter completely replaces a previous denotation, a symbol or NamedType
might still hang on to that denotation in the cache. We need to enforce that we switch to the new denotation.
This is done by setting the replaced denotation's validFor field to Nowhere.
d.T is an access to a structural type member, so rejecting this is OK. Not sure why we compiled
this before without warning.
1) We now always generate companion objects for classes. This is done in
   mini-phase "companions", which also assures that companion-modules appear
   after companion-classes.

2) PostTyperTransformers is gone; the part which normalizes trees has been
   rolled into TreeTransform and the part which reordered companion classes
   and modules is now in Companions.

Note: Some tests were deisabled; should be re-enabled by Dmitry where needed.
odersky added 23 commits July 17, 2014 11:02
The problem is that exploration methods are run and phase dependent,
whereas Definitions has an implicit context that freezes the period
when Definitions ewas created.

We should complement this by splitting Definitions into a global and
per/run part, but that is independent of the change in this commit.
If a symbol is defined in phases M..N, and that
symbol is then accessed in a phase before M, but in a new run,
we should not issue a stale symbol error (after all, the
symbol is not defined yet). Instead we now return a
NoDenotation.
If -uniqid is on, RefinedPrinter now prints unique ids in definitions.
Some fixes to toStatic. Also added a method that transforms a RepeatedType to the corresponding Seq type.
Makes sure there are no references to RepeatedParamClass left in the types.
Also added placeholder for ElimLocals, which remains to be written.
TreeTypeMap needs to track all binding occurrences and propagate maps
to all usage occurrences.

Also, fixed `ref` for prefixless types (these mapped to a SelectFromType tree before,
now are mapped to Idents).
Fixed extension methods so that it now runs and passes the build.
Also enables ElimRepeated, which is a prerequistite for ExtensionMethods.

Exception: Tailrec is currently disabled, because it needs to run before
ExtensionMethods but it fails the -Ycheck test. Therefore the current tests
skip this phase.
Removed diagnsostics message.
When figuring out which of a number of static methods corresponds to an overloaded dynamic
method, we only need to compare signatures. No need to resconstruct the full dynamic type.
Pattern match needs to compare against list of names, not single name.
Universal equality bites again...

Also fix comment for ElimRepeated.
The previous version of ExtensionMethods added extension methods manually,
in the companion objetct scope, using enteredAfter. Extension methods therefore
had to run before pickling, so that other modules would see the generated extension methods.
This is suboptimal, for two reasons; (1) Mixin is very much like extension methods, yet it
has to run after pickling. (2) The pickling info gets larger than it has to be.

With this commit, extension methods are added as an InfoTransformer. So extension methods
now should run after pickling.
When refined printing a DefTree, if one is after typer,
print the symbol's definition plus the rhs of the tree
rather than the contents of the tree.

Why: After typer, it's always the symbol's contents that should matter.

Question: Can we enforce through the types that parts of DefTree reflected
by the symbol disappear/are ignored?
Singleton types are normally widened before inferring types, yet
they did show up as elements of SeqLiterals.
1) Avoid double transformation of a tree
2) Apply the type map to the type of the tree-mapped tree,
   instead of to the type of the original tree.
1) Disabled insertApplyOrImplicit - it leads to untyped trees which cause
   assertion errors down the road. In any case, neither apply methods nor
   implicits can be inserted when retyping.

2) Avoid copying tree annotations into symbols when retyping.
This acommit allows TailRec to run after ExtensionMethods. This is
due to the following changes:

1) Extension methods now "rewire" calls to other extension methods of the
   same class, so that they go directly to other extension methods
   instead of to the original method in the class.

2) Tailrec annotations get removed from original method and get
   added to the extension method instead.

With this commit all tests can be re-enabled again.
Methods dealing with fully parameterized versions of classes were
pulled from `TypeUtils` and `ExtensionMethods` into `FullyParameterization`.
`TypeUtils` is left for possible future use, but is empty right now.
t6574 has a new test where we produce identical code in an if-then-else. This broke
the rewiring logic before, and is fixed now.

Also, more comments and test cases.
Problem was reported by @DarkDimius. Test case will come in next commit.
In a situation like

    class C { this: D =>
    }

the effective self type of the class is taken to be D & C.

Why? First, Scala 2.x does it that way. Second, it's needed to make the
FullParameterization transform go though.
Adapt the transformation so that self types are handled correctly.
Can now decide on rewiring on a node-by-nide basis.
@odersky
Copy link
Contributor Author

odersky commented Jul 17, 2014

Force-pushed after rebasing on @DarkDimius TreeTransformer fixes.

@odersky
Copy link
Contributor Author

odersky commented Jul 17, 2014

Force-pushed after @DarkDimius tree transformer fixes. ElimRepeated is now a tree transform, not a macro transform.

@DarkDimius
Copy link
Contributor

LGTM

DarkDimius added a commit that referenced this pull request Jul 17, 2014
@DarkDimius DarkDimius merged commit 9e1759f into scala:master Jul 17, 2014
@allanrenucci allanrenucci deleted the transform/extensionMethods branch December 14, 2017 19:25
WojciechMazur pushed a commit to WojciechMazur/dotty that referenced this pull request Mar 19, 2025
Backport "Add test cases project for presentation compiler" to 3.3 LTS
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