diff options
author | Adriaan Moors <adriaan.moors@typesafe.com> | 2014-11-06 11:50:13 +0100 |
---|---|---|
committer | Adriaan Moors <adriaan.moors@typesafe.com> | 2014-11-06 14:17:45 +0100 |
commit | 9c36a76171d497fe277030d2f682f927ae2657f4 (patch) | |
tree | a548b30aea610dcec56057fd99c25f7cfb6ab715 /src/compiler | |
parent | ae70f020cdd22d155882191ca60d40542615b606 (diff) | |
download | scala-9c36a76171d497fe277030d2f682f927ae2657f4.tar.gz scala-9c36a76171d497fe277030d2f682f927ae2657f4.tar.bz2 scala-9c36a76171d497fe277030d2f682f927ae2657f4.zip |
[sammy] eta-expansion, overloading (SI-8310)
Playing with Java 8 Streams from the repl showed
we weren't eta-expanding, nor resolving overloading for SAMs.
Also, the way Java uses wildcards to represent use-site variance
stresses type inference past its bendiness point (due to excessive existentials).
I introduce `wildcardExtrapolation` to simplify the resulting types
(without losing precision): `wildcardExtrapolation(tp) =:= tp`.
For example, the `MethodType` given by `def bla(x: (_ >: String)): (_ <: Int)`
is both a subtype and a supertype of `def bla(x: String): Int`.
Translating http://winterbe.com/posts/2014/07/31/java8-stream-tutorial-examples/
into Scala shows most of this works, though we have some more work to do (see near the end).
```
scala> import java.util.Arrays
scala> import java.util.stream.Stream
scala> import java.util.stream.IntStream
scala> val myList = Arrays.asList("a1", "a2", "b1", "c2", "c1")
myList: java.util.List[String] = [a1, a2, b1, c2, c1]
scala> myList.stream.filter(_.startsWith("c")).map(_.toUpperCase).sorted.forEach(println)
C1
C2
scala> myList.stream.filter(_.startsWith("c")).map(_.toUpperCase).sorted
res8: java.util.stream.Stream[?0] = java.util.stream.SortedOps$OfRef@133e7789
scala> Arrays.asList("a1", "a2", "a3").stream.findFirst.ifPresent(println)
a1
scala> Stream.of("a1", "a2", "a3").findFirst.ifPresent(println)
a1
scala> IntStream.range(1, 4).forEach(println)
<console>:37: error: object creation impossible, since method accept in trait IntConsumer of type (x$1: Int)Unit is not defined
(Note that Int does not match Any: class Int in package scala is a subclass of class Any in package scala, but method parameter types must match exactly.)
IntStream.range(1, 4).forEach(println)
^
scala> IntStream.range(1, 4).forEach(println(_: Int)) // TODO: can we avoid this annotation?
1
2
3
scala> Arrays.stream(Array(1, 2, 3)).map(n => 2 * n + 1).average.ifPresent(println(_: Double))
5.0
scala> Stream.of("a1", "a2", "a3").map(_.substring(1)).mapToInt(_.parseInt).max.ifPresent(println(_: Int)) // whoops!
ReplGlobal.abort: Unknown type: <error>, <error> [class scala.reflect.internal.Types$ErrorType$, class scala.reflect.internal.Types$ErrorType$] TypeRef? false
error: Unknown type: <error>, <error> [class scala.reflect.internal.Types$ErrorType$, class scala.reflect.internal.Types$ErrorType$] TypeRef? false
scala.reflect.internal.FatalError: Unknown type: <error>, <error> [class scala.reflect.internal.Types$ErrorType$, class scala.reflect.internal.Types$ErrorType$] TypeRef? false
at scala.reflect.internal.Reporting$class.abort(Reporting.scala:59)
scala> IntStream.range(1, 4).mapToObj(i => "a" + i).forEach(println)
a1
a2
a3
```
Diffstat (limited to 'src/compiler')
-rw-r--r-- | src/compiler/scala/tools/nsc/typechecker/Infer.scala | 6 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/typechecker/Typers.scala | 36 |
2 files changed, 32 insertions, 10 deletions
diff --git a/src/compiler/scala/tools/nsc/typechecker/Infer.scala b/src/compiler/scala/tools/nsc/typechecker/Infer.scala index ee2775ee26..8979b26719 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Infer.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Infer.scala @@ -295,11 +295,17 @@ trait Infer extends Checkable { && !isByNameParamType(tp) && isCompatible(tp, dropByName(pt)) ) + def isCompatibleSam(tp: Type, pt: Type): Boolean = { + val samFun = typer.samToFunctionType(pt) + (samFun ne NoType) && isCompatible(tp, samFun) + } + val tp1 = normalize(tp) ( (tp1 weak_<:< pt) || isCoercible(tp1, pt) || isCompatibleByName(tp, pt) + || isCompatibleSam(tp, pt) ) } def isCompatibleArgs(tps: List[Type], pts: List[Type]) = (tps corresponds pts)(isCompatible) diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 70acb03584..c41b81aaf5 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -741,6 +741,26 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper case _ => } + /** + * Convert a SAM type to the corresponding FunctionType, + * extrapolating BoundedWildcardTypes in the process + * (no type precision is lost by the extrapolation, + * but this facilitates dealing with the types arising from Java's use-site variance). + */ + def samToFunctionType(tp: Type, sam: Symbol = NoSymbol): Type = { + val samSym = sam orElse samOf(tp) + + def correspondingFunctionSymbol = { + val numVparams = samSym.info.params.length + if (numVparams > definitions.MaxFunctionArity) NoSymbol + else FunctionClass(numVparams) + } + + if (samSym.exists && samSym.owner != correspondingFunctionSymbol) // don't treat Functions as SAMs + wildcardExtrapolation(normalize(tp memberInfo samSym)) + else NoType + } + /** Perform the following adaptations of expression, pattern or type `tree` wrt to * given mode `mode` and given prototype `pt`: * (-1) For expressions with annotated types, let AnnotationCheckers decide what to do @@ -824,7 +844,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper case Block(_, tree1) => tree1.symbol case _ => tree.symbol } - if (!meth.isConstructor && isFunctionType(pt)) { // (4.2) + if (!meth.isConstructor && (isFunctionType(pt) || samOf(pt).exists)) { // (4.2) debuglog(s"eta-expanding $tree: ${tree.tpe} to $pt") checkParamsConvertible(tree, tree.tpe) val tree0 = etaExpand(context.unit, tree, this) @@ -2838,7 +2858,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper * as `(a => a): Int => Int` should not (yet) get the sam treatment. */ val sam = - if (!settings.Xexperimental || pt.typeSymbol == FunctionSymbol) NoSymbol + if (pt.typeSymbol == FunctionSymbol) NoSymbol else samOf(pt) /* The SAM case comes first so that this works: @@ -2848,15 +2868,11 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper * Note that the arity of the sam must correspond to the arity of the function. */ val samViable = sam.exists && sameLength(sam.info.params, fun.vparams) + val ptNorm = if (samViable) samToFunctionType(pt, sam) else pt val (argpts, respt) = - if (samViable) { - val samInfo = pt memberInfo sam - (samInfo.paramTypes, samInfo.resultType) - } else { - pt baseType FunctionSymbol match { - case TypeRef(_, FunctionSymbol, args :+ res) => (args, res) - case _ => (fun.vparams map (_ => if (pt == ErrorType) ErrorType else NoType), WildcardType) - } + ptNorm baseType FunctionSymbol match { + case TypeRef(_, FunctionSymbol, args :+ res) => (args, res) + case _ => (fun.vparams map (_ => if (pt == ErrorType) ErrorType else NoType), WildcardType) } if (!FunctionSymbol.exists) |