Skip to content

Commit 3533a75

Browse files
committed
Type parameter clause inference for lambdas
When a lambda is written without a type parameter clause but the expected type is a polymorphic function type, try to adapt the lambda into a polymorphic lambda by inferring an appropriate type parameter clause. This change broke one example in spire which relied on implicit conversions. The fix has been accepted upstream: typelevel/spire#1247
1 parent 2753a17 commit 3533a75

File tree

5 files changed

+40
-2
lines changed

5 files changed

+40
-2
lines changed

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1460,6 +1460,33 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
14601460
def typedFunctionValue(tree: untpd.Function, pt: Type)(using Context): Tree = {
14611461
val untpd.Function(params: List[untpd.ValDef] @unchecked, _) = tree: @unchecked
14621462

1463+
// If the expected type is a polymorphic function type:
1464+
//
1465+
// [S_1, ..., S_m] => (T_1, ..., T_n) => R
1466+
// (where each S_i might have type bounds)
1467+
//
1468+
// and we are typing a lambda of the form:
1469+
//
1470+
// (x_1, ..., x_n) => e
1471+
// (where each x_i might have a type ascription)
1472+
//
1473+
// then continue with an inferred type parameter clause:
1474+
//
1475+
// [S'_1, ..., S'_m] => (x_1, ..., x_n) => e
1476+
// (where the bounds of S'_i are the bounds of S_i after substituting S_j by S'_j for all j)
1477+
pt match
1478+
case RefinedType(parent, nme.apply, poly @ PolyType(_, mt: MethodType))
1479+
if (parent.typeSymbol eq defn.PolyFunctionClass) &&
1480+
params.lengthCompare(mt.paramNames) == 0
1481+
=>
1482+
val tparams = poly.paramNames.lazyZip(poly.paramInfos).map: (name, info) =>
1483+
untpd.TypeDef(UniqueName.fresh(name), untpd.InLambdaTypeTree(isResult = false, (tsyms, vsyms) =>
1484+
info.substParams(poly, tsyms.map(_.typeRef))
1485+
)).withFlags(SyntheticParam)
1486+
.withSpan(tree.span.startPos)
1487+
return typed(untpd.PolyFunction(tparams, tree), pt)
1488+
case _ =>
1489+
14631490
val (isContextual, isDefinedErased) = tree match {
14641491
case tree: untpd.FunctionWithMods => (tree.mods.is(Given), tree.erasedParams)
14651492
case _ => (false, tree.args.map(_ => false))

tests/neg/i13769.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
val tup = (1, "s")
2-
val te = tup.map((x: _ <: Int) => List(x)) // error
2+
val te = tup.map((x: _ <: Int) => List(x)) // error // error

tests/neg/polymorphic-functions2.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@ val wrongLength2: [T] => T => T = [T] => (x, x) => x // error
44
val notSubType: [T] => T => T = [T <: Int] => x => x // error
55

66
val notInScope: [T] => T => T = [S] => x => (x: T) // error
7+
8+
val notInScopeInferred: [T] => T => T = x => (x: T) // error

tests/run/polymorphic-functions.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,4 +118,13 @@ object Test extends App {
118118
[S, T <: List[S]] => (x, y) => x :: y
119119
val i4: [T, S <: List[T]] => (T, S) => List[T] =
120120
[T, S <: List[T]] => (x, y: S) => x :: y
121+
122+
// Inferred type clause
123+
val it1: [T] => T => T = x => x
124+
val it2: [T] => (T, Int) => T = (x, y: Int) => x
125+
val it3: [T, S <: List[T]] => (T, S) => List[T] = (x, y) => x :: y
126+
val tuple1: (String, String) = (1, 2.0).map[[_] =>> String](_.toString)
127+
val tuple2: (List[Int], List[Double]) = (1, 2.0).map(List(_))
128+
// Not supported yet, require eta-expansion with a polymorphic expected type
129+
// val tuple3: (List[Int], List[Double]) = (1, 2.0).map(List.apply)
121130
}

0 commit comments

Comments
 (0)