Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions bincompat-forward.whitelist.conf
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,10 @@ filter {
{
matchName="scala.concurrent.impl.Promise$DefaultPromise"
problemName=MissingTypesProblem
},
{
matchName="scala.reflect.runtime.Settings.YpartialUnification"
problemName=MissingMethodProblem
}
]
}
2 changes: 1 addition & 1 deletion project/ScalaOptionParser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ object ScalaOptionParser {
"-Yeta-expand-keeps-star", "-Yide-debug", "-Yinfer-argument-types", "-Yinfer-by-name",
"-Yissue-debug", "-Ylog-classpath", "-Ymacro-debug-lite", "-Ymacro-debug-verbose", "-Ymacro-no-expand",
"-Yno-completion", "-Yno-generic-signatures", "-Yno-imports", "-Yno-predef",
"-Yoverride-objects", "-Yoverride-vars", "-Ypatmat-debug", "-Yno-adapted-args", "-Ypos-debug", "-Ypresentation-debug",
"-Yoverride-objects", "-Yoverride-vars", "-Ypatmat-debug", "-Yno-adapted-args", "-Ypartial-unification", "-Ypos-debug", "-Ypresentation-debug",
"-Ypresentation-strict", "-Ypresentation-verbose", "-Yquasiquote-debug", "-Yrangepos", "-Yreify-copypaste", "-Yreify-debug", "-Yrepl-class-based",
"-Yrepl-sync", "-Yshow-member-pos", "-Yshow-symkinds", "-Yshow-symowners", "-Yshow-syms", "-Yshow-trees", "-Yshow-trees-compact", "-Yshow-trees-stringified", "-Ytyper-debug",
"-Ywarn-adapted-args", "-Ywarn-dead-code", "-Ywarn-inaccessible", "-Ywarn-infer-any", "-Ywarn-nullary-override", "-Ywarn-nullary-unit", "-Ywarn-numeric-widen", "-Ywarn-unused", "-Ywarn-unused-import", "-Ywarn-value-discard",
Expand Down
3 changes: 2 additions & 1 deletion src/compiler/scala/tools/nsc/settings/ScalaSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ trait ScalaSettings extends AbsScalaSettings
protected def defaultClasspath = sys.env.getOrElse("CLASSPATH", ".")

/** Enabled under -Xexperimental. */
protected def experimentalSettings = List[BooleanSetting](YmethodInfer, overrideObjects, overrideVars)
protected def experimentalSettings = List[BooleanSetting](YpartialUnification)

/** Enabled under -Xfuture. */
protected def futureSettings = List[BooleanSetting]()
Expand Down Expand Up @@ -201,6 +201,7 @@ trait ScalaSettings extends AbsScalaSettings
val etaExpandKeepsStar = BooleanSetting ("-Yeta-expand-keeps-star", "Eta-expand varargs methods to T* rather than Seq[T]. This is a temporary option to ease transition.").withDeprecationMessage(removalIn212)
val inferByName = BooleanSetting ("-Yinfer-by-name", "Allow inference of by-name types. This is a temporary option to ease transition. See SI-7899.").withDeprecationMessage(removalIn212)
val YdisableFlatCpCaching = BooleanSetting ("-YdisableFlatCpCaching", "Do not cache flat classpath representation of classpath elements from jars across compiler instances.")
val YpartialUnification = BooleanSetting ("-Ypartial-unification", "Enable partial unification in type constructor inference")

val exposeEmptyPackage = BooleanSetting ("-Yexpose-empty-package", "Internal only: expose the empty package.").internalOnly()
val Ydelambdafy = ChoiceSetting ("-Ydelambdafy", "strategy", "Strategy used for translating lambdas into JVM code.", List("inline", "method"), "method")
Expand Down
40 changes: 35 additions & 5 deletions src/reflect/scala/reflect/internal/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3131,13 +3131,43 @@ trait Types
*/
def unifyFull(tpe: Type): Boolean = {
def unifySpecific(tp: Type) = {
Copy link
Contributor

@adriaanm adriaanm May 23, 2016

Choose a reason for hiding this comment

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

To summarize my feedback from the individual comments, here's unifySpecific with my suggestions applied:

def unifySpecific(tp: Type) = {
  val tpTypeArgs = tp.typeArgs
  val arityDelta = compareLengths(typeArgs, tpTypeArgs)
  if (arityDelta == 0) {
    val lhs = if (isLowerBound) tpTypeArgs else typeArgs
    val rhs = if (isLowerBound) typeArgs else tpTypeArgs
    // This is a higher-kinded type var with same arity as tp.
    // If so (see SI-7517), side effect: adds the type constructor itself as a bound.
    isSubArgs(lhs, rhs, params, AnyDepth) && {addBound(tp.typeConstructor); true}
  } else if (arityDelta < 0 && typeArgs.nonEmpty) {
    // Simple algorithm as suggested by Paul Chiusano in the comments on SI-2712
    //
    //   https://issues.scala-lang.org/browse/SI-2712?focusedCommentId=61270
    //
    // Treat the type constructor as curried and partially applied, we treat a prefix
    // as constants and solve for the suffix. For the example in the ticket, unifying
    // M[A] with Int => Int this unifies as,
    //
    //   M[t] = [t][Int => t]  --> abstract on the right to match the expected arity
    //   A = Int               --> capture the remainder on the left
    //
    // A more "natural" unifier might be M[t] = [t][t => t]. There's lots of scope for
    // experimenting with alternatives here.
    val numCaptured = tpTypeArgs.length - typeArgs.length
    val (captured, abstractedArgs) = tpTypeArgs.splitAt(numCaptured)

    val (lhs, rhs) =
      if (isLowerBound) (abstractedArgs, typeArgs)
      else (typeArgs, abstractedArgs)

    isSubArgs(lhs, rhs, params, AnyDepth) && {
      val tpSym = tp.typeSymbolDirect
      val abstractedTypeParams = tpSym.typeParams.drop(numCaptured).map(_.cloneSymbol(tpSym))

      addBound(PolyType(abstractedTypeParams, appliedType(tp.typeConstructor, captured ++ abstractedTypeParams.map(_.tpeHK))))
      true
    }
  } else false
}

sameLength(typeArgs, tp.typeArgs) && {
val lhs = if (isLowerBound) tp.typeArgs else typeArgs
val rhs = if (isLowerBound) typeArgs else tp.typeArgs
val tpTypeArgs = tp.typeArgs
val arityDelta = compareLengths(typeArgs, tpTypeArgs)
if (arityDelta == 0) {
val lhs = if (isLowerBound) tpTypeArgs else typeArgs
val rhs = if (isLowerBound) typeArgs else tpTypeArgs
// This is a higher-kinded type var with same arity as tp.
// If so (see SI-7517), side effect: adds the type constructor itself as a bound.
isSubArgs(lhs, rhs, params, AnyDepth) && { addBound(tp.typeConstructor); true }
}
isSubArgs(lhs, rhs, params, AnyDepth) && {addBound(tp.typeConstructor); true}
} else if (settings.YpartialUnification && arityDelta < 0 && typeArgs.nonEmpty) {
// Simple algorithm as suggested by Paul Chiusano in the comments on SI-2712
//
// https://issues.scala-lang.org/browse/SI-2712?focusedCommentId=61270
//
// Treat the type constructor as curried and partially applied, we treat a prefix
// as constants and solve for the suffix. For the example in the ticket, unifying
// M[A] with Int => Int this unifies as,
//
// M[t] = [t][Int => t] --> abstract on the right to match the expected arity
// A = Int --> capture the remainder on the left
//
// A more "natural" unifier might be M[t] = [t][t => t]. There's lots of scope for
// experimenting with alternatives here.
Copy link

@djspiewak djspiewak Apr 15, 2016

Choose a reason for hiding this comment

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

If this does make it in for 2.12 (or even if it doesn't), the above should be expanded into a blog post somewhere. We need to be clear that this is very literally adding left-to-right curried type constructor semantics to Scala. I tend to think this is a good thing, and it lines up very very nicely with things the community is already doing by default (e.g. right-biased Xor), but it needs to be made clear.

Copy link
Contributor

Choose a reason for hiding this comment

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

it lines up very very nicely with things the community is already doing by default (e.g. right-biased Xor), but it needs to be made clear.

I'm guessing that's an heritage of Haskell's (here, from Either), where type inference works this way since ever. Hence, I conjecture no-Haskell Scalaers might especially need this explained.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm guessing that's an heritage of Haskell's (here, from Either), where type inference works this way since ever.

Yes, but on top of that, in Haskell, left-to-right is the only thing you can do.

val numCaptured = tpTypeArgs.length - typeArgs.length
val (captured, abstractedArgs) = tpTypeArgs.splitAt(numCaptured)

val (lhs, rhs) =
if (isLowerBound) (abstractedArgs, typeArgs)
else (typeArgs, abstractedArgs)

isSubArgs(lhs, rhs, params, AnyDepth) && {
val tpSym = tp.typeSymbolDirect
Copy link
Member

@retronym retronym Apr 17, 2016

Choose a reason for hiding this comment

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

This is lossy: the prefix of the tp will not contribute to the bounds of the synthetic type params. Any time you see code that looks that typeSymbol.info, there is cause for suspicion.

scala> class C[A] { trait Two[_1 <: A, _2 <: A]; type One[_1 <: A] = Two[_1, _1] }
defined class C

scala> def test(cint: C[Int])(two: cint.Two[Int, Int]) = f(two)
<console>:13: error: inferred kinds of the type arguments ([_2 <: A]cint.Two[Int,_2]) do not conform to the expected kinds of the type parameters (type M).
[_2 <: A]cint.Two[Int,_2]'s type parameters do not match type M's expected parameters:
type _2's bounds <: A are stricter than type _'s declared bounds <: Int
       def test(cint: C[Int])(two: cint.Two[Int, Int]) = f(two)
                                                         ^

That said, it appears we have the same problem with regular inference, so we can open a ticket to followup and fix both cases:

scala> def test(cint: C[Int])(two: cint.Two[Int, Int]) = { f[cint.One](two) }
<console>:13: error: kinds of the type arguments (cint.One) do not conform to the expected kinds of the type parameters (type M).
cint.One's type parameters do not match type M's expected parameters:
type _1's bounds <: A are stricter than type _'s declared bounds <: Int
       def test(cint: C[Int])(two: cint.Two[Int, Int]) = { f[cint.One](two) }
                                                            ^

scala> def test(cint: C[Int])(two: cint.Two[Int, Int]) = { type One1[_1 <: Int] = cint.One[_1]; f[One1](two) }
test: (cint: C[Int])(two: cint.Two[Int,Int])One1[Int]

A fix would be something along the lines of:

scala> val two = typeOf[cint.Two[Int, Int]]
two: $r.intp.global.Type = cint.Two[Int,Int]

scala> val instantiatedTypeParams = two.prefix.memberInfo(two.typeSymbolDirect).typeParams
instantiatedTypeParams: List[$r.intp.global.Symbol] = List(type _1, type _2)

scala> instantiatedTypeParams.map(_.defString)
res23: List[String] = List(_1 <: Int, _2 <: Int)

val abstractedTypeParams = tpSym.typeParams.drop(numCaptured).map(_.cloneSymbol(tpSym))

addBound(PolyType(abstractedTypeParams, appliedType(tp.typeConstructor, captured ++ abstractedTypeParams.map(_.tpeHK))))
true
}
} else false
}
// The type with which we can successfully unify can be hidden
// behind singleton types and type aliases.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ abstract class MutableSettings extends AbsSettings {
def printtypes: BooleanSetting
def uniqid: BooleanSetting
def verbose: BooleanSetting
def YpartialUnification: BooleanSetting

def Yrecursion: IntSetting
def maxClassfileName: IntSetting
Expand Down
1 change: 1 addition & 0 deletions src/reflect/scala/reflect/runtime/Settings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ private[reflect] class Settings extends MutableSettings {
val printtypes = new BooleanSetting(false)
val uniqid = new BooleanSetting(false)
val verbose = new BooleanSetting(false)
val YpartialUnification = new BooleanSetting(false)

val Yrecursion = new IntSetting(0)
val maxClassfileName = new IntSetting(255)
Expand Down
13 changes: 13 additions & 0 deletions test/files/neg/t2712-1.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
t2712-1.scala:7: error: no type parameters for method foo: (m: M[A])Unit exist so that it can be applied to arguments (test.Two[Int,String])
--- because ---
argument expression's type is not compatible with formal parameter type;
found : test.Two[Int,String]
required: ?M[?A]
def test(ma: Two[Int, String]) = foo(ma) // should fail with -Ypartial-unification *disabled*
^
t2712-1.scala:7: error: type mismatch;
found : test.Two[Int,String]
required: M[A]
def test(ma: Two[Int, String]) = foo(ma) // should fail with -Ypartial-unification *disabled*
^
two errors found
8 changes: 8 additions & 0 deletions test/files/neg/t2712-1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package test

trait Two[A, B]

object Test {
def foo[M[_], A](m: M[A]) = ()
def test(ma: Two[Int, String]) = foo(ma) // should fail with -Ypartial-unification *disabled*
}
13 changes: 13 additions & 0 deletions test/files/neg/t2712-2.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
t2712-2.scala:16: error: type mismatch;
found : test.Foo
required: test.Two[test.X1,Object]
Note: test.X2 <: Object (and test.Foo <: test.Two[test.X1,test.X2]), but trait Two is invariant in type B.
You may wish to define B as +B instead. (SLS 4.5)
test1(foo): One[X3] // fails with -Ypartial-unification enabled
^
t2712-2.scala:16: error: type mismatch;
found : test.Two[test.X1,Object]
required: test.One[test.X3]
test1(foo): One[X3] // fails with -Ypartial-unification enabled
^
two errors found
1 change: 1 addition & 0 deletions test/files/neg/t2712-2.flags
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
-Ypartial-unification
18 changes: 18 additions & 0 deletions test/files/neg/t2712-2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package test

class X1
class X2
class X3

trait One[A]
trait Two[A, B]

class Foo extends Two[X1, X2] with One[X3]
object Test {
def test1[M[_], A](x: M[A]): M[A] = x

val foo = new Foo

test1(foo): One[X3] // fails with -Ypartial-unification enabled
test1(foo): Two[X1, X2] // fails without -Ypartial-unification
}
6 changes: 6 additions & 0 deletions test/files/neg/t2712-3.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
t2712-3.scala:17: error: type mismatch;
found : test.One[test.X3]
required: test.Two[test.X1,test.X2]
test1(foo): Two[X1, X2] // fails without -Ypartial-unification
^
one error found
18 changes: 18 additions & 0 deletions test/files/neg/t2712-3.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package test

class X1
class X2
class X3

trait One[A]
trait Two[A, B]

class Foo extends Two[X1, X2] with One[X3]
object Test {
def test1[M[_], A](x: M[A]): M[A] = x

val foo = new Foo

test1(foo): One[X3] // fails with -Ypartial-unification enabled
test1(foo): Two[X1, X2] // fails without -Ypartial-unification
}
1 change: 1 addition & 0 deletions test/files/neg/t2712.flags
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
-Ypartial-unification
1 change: 1 addition & 0 deletions test/files/pos/t2712-1.flags
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
-Ypartial-unification
9 changes: 9 additions & 0 deletions test/files/pos/t2712-1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package test

// Original test case from,
//
// https://issues.scala-lang.org/browse/SI-2712
object Test {
def meh[M[_], A](x: M[A]): M[A] = x
meh{(x: Int) => x} // solves ?M = [X] Int => X and ?A = Int ...
}
2 changes: 2 additions & 0 deletions test/files/pos/t2712-2.flags
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-Ypartial-unification

25 changes: 25 additions & 0 deletions test/files/pos/t2712-2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package test

// See: https://github.com/milessabin/si2712fix-demo/issues/3
object Test {
trait A[T1, T2] { }
trait B[T1, T2] { }
class C[T] extends A[T, Long] with B[T, Double]
class CB extends A[Boolean, Long] with B[Boolean, Double]

trait A2[T]
trait B2[T]
class C2[T] extends A2[T] with B2[T]
class CB2 extends A2[Boolean] with B2[Boolean]

def meh[M[_], A](x: M[A]): M[A] = x

val m0 = meh(new C[Boolean])
m0: C[Boolean]
val m1 = meh(new CB)
m1: A[Boolean, Long]
val m2 = meh(new C2[Boolean])
m2: C2[Boolean]
val m3 = meh(new CB2)
m3: A2[Boolean]
}
2 changes: 2 additions & 0 deletions test/files/pos/t2712-3.flags
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-Ypartial-unification

24 changes: 24 additions & 0 deletions test/files/pos/t2712-3.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package test

object Test1 {
class Foo[T, F[_]]
def meh[M[_[_]], F[_]](x: M[F]): M[F] = x
meh(new Foo[Int, List]) // solves ?M = [X[_]]Foo[Int, X[_]] ?A = List ...
}

object Test2 {
trait TC[T]
class Foo[F[_], G[_]]
def meh[G[_[_]]](g: G[TC]) = ???
meh(new Foo[TC, TC]) // solves ?G = [X[_]]Foo[TC, X]
}

object Test3 {
trait TC[F[_]]
trait TC2[F[_]]
class Foo[F[_[_]], G[_[_]]]
new Foo[TC, TC2]

def meh[G[_[_[_]]]](g: G[TC2]) = ???
meh(new Foo[TC, TC2]) // solves ?G = [X[_[_]]]Foo[TC, X]
}
2 changes: 2 additions & 0 deletions test/files/pos/t2712-4.flags
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-Ypartial-unification

17 changes: 17 additions & 0 deletions test/files/pos/t2712-4.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package test

object Test1 {
trait X
trait Y extends X
class Foo[T, U <: X]
def meh[M[_ <: A], A](x: M[A]): M[A] = x
meh(new Foo[Int, Y])
}

object Test2 {
trait X
trait Y extends X
class Foo[T, U >: Y]
def meh[M[_ >: A], A](x: M[A]): M[A] = x
meh(new Foo[Int, X])
}
1 change: 1 addition & 0 deletions test/files/pos/t2712-5.flags
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
-Ypartial-unification
29 changes: 29 additions & 0 deletions test/files/pos/t2712-5.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package test

import scala.language.higherKinds

trait Functor[F[_]] {
def map[A, B](f: A => B, fa: F[A]): F[B]
}

object Functor {
implicit def function[A]: Functor[({ type l[B] = A => B })#l] =
new Functor[({ type l[B] = A => B })#l] {
def map[C, B](cb: C => B, ac: A => C): A => B = cb compose ac
}
}

object FunctorSyntax {
implicit class FunctorOps[F[_], A](fa: F[A])(implicit F: Functor[F]) {
def map[B](f: A => B): F[B] = F.map(f, fa)
}
}

object Test {

val f: Int => String = _.toString

import FunctorSyntax._

f.map((s: String) => s.reverse)
}
1 change: 1 addition & 0 deletions test/files/pos/t2712-6.flags
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
-Ypartial-unification
12 changes: 12 additions & 0 deletions test/files/pos/t2712-6.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package test

object Tags {
type Tagged[A, T] = {type Tag = T; type Self = A}

type @@[T, Tag] = Tagged[T, Tag]

trait Disjunction

def meh[M[_], A](ma: M[A]): M[A] = ma
meh(null.asInstanceOf[Int @@ Disjunction])
}
1 change: 1 addition & 0 deletions test/files/pos/t2712-7.flags
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
-Ypartial-unification
15 changes: 15 additions & 0 deletions test/files/pos/t2712-7.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package test

// Cats Xor, Scalaz \/, scala.util.Either
sealed abstract class Xor[+A, +B] extends Product with Serializable
object Xor {
final case class Left[+A](a: A) extends (A Xor Nothing)
final case class Right[+B](b: B) extends (Nothing Xor B)
}

object TestXor {
import Xor._
def meh[F[_], A, B](fa: F[A])(f: A => B): F[B] = ???
meh(new Right(23): Xor[Boolean, Int])(_ < 13)
meh(new Left(true): Xor[Boolean, Int])(_ < 13)
}
1 change: 1 addition & 0 deletions test/files/pos/t5683.flags
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
-Ypartial-unification
23 changes: 23 additions & 0 deletions test/files/pos/t5683.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
object Test {
trait NT[X]
trait W[W, A] extends NT[Int]
type StringW[T] = W[String, T]
trait K[M[_], A, B]

def k[M[_], B](f: Int => M[B]): K[M, Int, B] = null

val okay1: K[StringW,Int,Int] = k{ (y: Int) => null: StringW[Int] }
val okay2 = k[StringW,Int]{ (y: Int) => null: W[String, Int] }

val crash: K[StringW,Int,Int] = k{ (y: Int) => null: W[String, Int] }

// remove `extends NT[Int]`, and the last line gives an inference error
// rather than a crash.
// test/files/pos/t5683.scala:12: error: no type parameters for method k: (f: Int => M[B])Test.K[M,Int,B] exist so that it can be applied to arguments (Int => Test.W[String,Int])
// --- because ---
// argument expression's type is not compatible with formal parameter type;
// found : Int => Test.W[String,Int]
// required: Int => ?M[?B]
// val crash: K[StringW,Int,Int] = k{ (y: Int) => null: W[String, Int] }
// ^
}
2 changes: 2 additions & 0 deletions test/files/pos/t6895b.flags
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-Ypartial-unification

Loading