Skip to content

Commit 186f955

Browse files
authored
Merge pull request #1514 from OlivierBlanvillain/fix-1513
Fix #1513: misaligned by name type parameter type bounds
2 parents 78a4ff4 + 61f7e08 commit 186f955

File tree

6 files changed

+87
-19
lines changed

6 files changed

+87
-19
lines changed

src/dotty/tools/dotc/core/Types.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2532,8 +2532,8 @@ object Types {
25322532
/** A type for polymorphic methods */
25332533
class PolyType(val paramNames: List[TypeName])(paramBoundsExp: GenericType => List[TypeBounds], resultTypeExp: GenericType => Type)
25342534
extends CachedGroundType with GenericType with MethodOrPoly {
2535-
val paramBounds = paramBoundsExp(this)
2536-
val resType = resultTypeExp(this)
2535+
val paramBounds: List[TypeBounds] = paramBoundsExp(this)
2536+
val resType: Type = resultTypeExp(this)
25372537
def variances = Nil
25382538

25392539
protected def computeSignature(implicit ctx: Context) = resultSignature

src/dotty/tools/dotc/transform/PostTyper.scala

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisTran
9393
*
9494
* should behave differently.
9595
*
96-
* O1.x should have the same effect as { println("43"; 42 }
96+
* O1.x should have the same effect as { println("43"); 42 }
9797
*
9898
* whereas
9999
*
@@ -103,10 +103,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisTran
103103
* purity of the prefix unless the selection goes to an inline val.
104104
*/
105105
private def normalizeTree(tree: Tree)(implicit ctx: Context): Tree = tree match {
106-
case tree: TypeTree => tree
107-
case TypeApply(fn, args) =>
108-
Checking.checkBounds(args, fn.tpe.widen.asInstanceOf[PolyType])
109-
tree
106+
case _: TypeTree | _: TypeApply => tree
110107
case _ =>
111108
if (tree.isType) {
112109
Checking.typeChecker.traverse(tree)

src/dotty/tools/dotc/typer/Checking.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ object Checking {
3737
* well as for AppliedTypeTree nodes. Also checks that type arguments to
3838
* *-type parameters are fully applied.
3939
*/
40-
def checkBounds(args: List[tpd.Tree], boundss: List[TypeBounds], instantiate: (Type, List[Type]) => Type)(implicit ctx: Context) = {
40+
def checkBounds(args: List[tpd.Tree], boundss: List[TypeBounds], instantiate: (Type, List[Type]) => Type)(implicit ctx: Context): Unit = {
4141
(args, boundss).zipped.foreach { (arg, bound) =>
4242
if (!bound.isHK && arg.tpe.isHK)
4343
ctx.error(ex"missing type parameter(s) for $arg", arg.pos)

src/dotty/tools/dotc/typer/TypeAssigner.scala

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -323,21 +323,30 @@ trait TypeAssigner {
323323
case pt: PolyType =>
324324
val paramNames = pt.paramNames
325325
if (hasNamedArg(args)) {
326-
val argMap = new mutable.HashMap[Name, Type]
326+
// Type arguments which are specified by name (immutable after this first loop)
327+
val namedArgMap = new mutable.HashMap[Name, Type]
327328
for (NamedArg(name, arg) <- args)
328-
if (argMap.contains(name))
329+
if (namedArgMap.contains(name))
329330
ctx.error("duplicate name", arg.pos)
330331
else if (!paramNames.contains(name))
331332
ctx.error(s"undefined parameter name, required: ${paramNames.mkString(" or ")}", arg.pos)
332333
else
333-
argMap(name) = arg.tpe
334+
namedArgMap(name) = arg.tpe
335+
336+
// Holds indexes of non-named typed arguments in paramNames
334337
val gapBuf = new mutable.ListBuffer[Int]
335-
def nextPoly = {
336-
val idx = gapBuf.length
338+
def nextPoly(idx: Int) = {
339+
val newIndex = gapBuf.length
337340
gapBuf += idx
338-
PolyParam(pt, idx)
341+
// Re-index unassigned type arguments that remain after transformation
342+
PolyParam(pt, newIndex)
343+
}
344+
345+
// Type parameters after naming assignment, conserving paramNames order
346+
val normArgs: List[Type] = paramNames.zipWithIndex.map { case (pname, idx) =>
347+
namedArgMap.getOrElse(pname, nextPoly(idx))
339348
}
340-
val normArgs = paramNames.map(pname => argMap.getOrElse(pname, nextPoly))
349+
341350
val transform = new TypeMap {
342351
def apply(t: Type) = t match {
343352
case PolyParam(`pt`, idx) => normArgs(idx)
@@ -349,19 +358,20 @@ trait TypeAssigner {
349358
else {
350359
val gaps = gapBuf.toList
351360
pt.derivedPolyType(
352-
gaps.map(paramNames.filterNot(argMap.contains)),
361+
gaps.map(paramNames),
353362
gaps.map(idx => transform(pt.paramBounds(idx)).bounds),
354363
resultType1)
355364
}
356365
}
357366
else {
358367
val argTypes = args.tpes
359-
if (sameLength(argTypes, paramNames)|| ctx.phase.prev.relaxedTyping) pt.instantiate(argTypes)
368+
if (sameLength(argTypes, paramNames) || ctx.phase.prev.relaxedTyping) pt.instantiate(argTypes)
360369
else wrongNumberOfArgs(fn.tpe, "type ", pt.paramNames.length, tree.pos)
361370
}
362371
case _ =>
363372
errorType(i"${err.exprStr(fn)} does not take type parameters", tree.pos)
364373
}
374+
365375
tree.withType(ownType)
366376
}
367377

@@ -385,8 +395,8 @@ trait TypeAssigner {
385395

386396
def assignType(tree: untpd.Closure, meth: Tree, target: Tree)(implicit ctx: Context) =
387397
tree.withType(
388-
if (target.isEmpty) meth.tpe.widen.toFunctionType(tree.env.length)
389-
else target.tpe)
398+
if (target.isEmpty) meth.tpe.widen.toFunctionType(tree.env.length)
399+
else target.tpe)
390400

391401
def assignType(tree: untpd.CaseDef, body: Tree)(implicit ctx: Context) =
392402
tree.withType(body.tpe)

tests/pos/t1513a.scala

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
object Test {
2+
// Heterogeneous lists and natural numbers as defined in shapeless.
3+
4+
sealed trait HList
5+
sealed trait ::[H, T <: HList] extends HList
6+
sealed trait HNil extends HList
7+
8+
sealed trait Nat
9+
sealed trait Succ[P <: Nat] extends Nat
10+
sealed trait Zero extends Nat
11+
12+
// Accessor type class to compute the N'th element of an HList L.
13+
14+
trait Accessor[L <: HList, N <: Nat] { type Out }
15+
object Accessor {
16+
type Aux[L <: HList, N <: Nat, O] = Accessor[L, N] { type Out = O }
17+
18+
// (H :: T).At[Zero] = H
19+
implicit def caseZero[H, T <: HList]: Aux[H :: T, Zero, H] = ???
20+
21+
// T.At[N] = O => (H :: T).At[Succ[N]] = O
22+
implicit def caseN[H, T <: HList, N <: Nat, O]
23+
(implicit a: Aux[T, N, O]): Aux[H :: T, Succ[N], O] = ???
24+
}
25+
26+
case class Proxy[T]()
27+
28+
def at1[NN <: Nat, OO] (implicit e: Accessor.Aux[String :: HNil, NN, OO]): OO = ???
29+
def at2[NN <: Nat, OO](p: Proxy[NN])(implicit e: Accessor.Aux[String :: HNil, NN, OO]): OO = ???
30+
31+
// N is fixed by a value
32+
at2(Proxy[Zero]): String
33+
34+
// N is fixed as a type parameter (by name)
35+
at1[NN = Zero]: String
36+
}

tests/pos/t1513b.scala

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
object Test {
2+
def f[
3+
T1 <: String,
4+
T2 <: Int,
5+
T3 <: Boolean
6+
](a1: T1, a2: T2, a3: T3) = ()
7+
8+
f ("", 1, true)
9+
f[T1 = String] ("", 1, true)
10+
f[T2 = Int] ("", 1, true)
11+
f[T3 = Boolean] ("", 1, true)
12+
f[T1 = String, T2 = Int] ("", 1, true)
13+
f[T1 = String, T3 = Boolean] ("", 1, true)
14+
f[T2 = Int, T1 = String] ("", 1, true)
15+
f[T2 = Int, T3 = Boolean] ("", 1, true)
16+
f[T3 = Boolean, T2 = Int] ("", 1, true)
17+
f[T3 = Boolean, T1 = String] ("", 1, true)
18+
f[T1 = String, T2 = Int, T3 = Boolean]("", 1, true)
19+
f[T1 = String, T3 = Boolean, T2 = Int] ("", 1, true)
20+
f[T2 = Int, T1 = String, T3 = Boolean]("", 1, true)
21+
f[T2 = Int, T3 = Boolean, T1 = String] ("", 1, true)
22+
f[T3 = Boolean, T1 = String, T2 = Int] ("", 1, true)
23+
f[T3 = Boolean, T2 = Int, T1 = String] ("", 1, true)
24+
f[String, Int, Boolean] ("", 1, true)
25+
}

0 commit comments

Comments
 (0)