Skip to content

Commit cde5e67

Browse files
committed
Fix pretty-printing of renamed lambda params
Previously, the expression val a: (x: Int) => Bar[x.type] = ??? : ((x: Int) => Foo[x.type]) lead to the following error: Found: (x: Int) => Foo[x.type] Required: (x: Int) => Bar[x².type] where: x is a reference to a value parameter x² is a reference to a value parameter There's two problems here: 1. In the second lambda, x is supposed to be renamed to x², but the binder wasn't renamed. 2. Since the binders introducing x are part of the printed types, there isn't any ambiguity and no renaming is actually needed. (1.) is a straight-up bug in the printing logic that we fix. (2.) is maybe a matter of opinion, but for clarity we choose to address it by using the same superscript for every parameter reference with the same name whose binder has been printed. This does meant that `(x: Int) => (x: Int) => x.type` won't be disambiguated, but something like that would be better addressed by printing a warning about name shadowing anyway.
1 parent b67e269 commit cde5e67

File tree

5 files changed

+84
-7
lines changed

5 files changed

+84
-7
lines changed

compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -297,9 +297,9 @@ class PlainPrinter(_ctx: Context) extends Printer {
297297

298298
protected def paramsText(lam: LambdaType): Text = {
299299
val erasedParams = lam.erasedParams
300-
def paramText(name: Name, tp: Type, erased: Boolean) =
301-
keywordText("erased ").provided(erased) ~ toText(name) ~ lambdaHash(lam) ~ toTextRHS(tp, isParameter = true)
302-
Text(lam.paramNames.lazyZip(lam.paramInfos).lazyZip(erasedParams).map(paramText), ", ")
300+
def paramText(ref: ParamRef, erased: Boolean) =
301+
keywordText("erased ").provided(erased) ~ ParamRefNameString(ref) ~ lambdaHash(lam) ~ toTextRHS(ref.underlying, isParameter = true)
302+
Text(lam.paramRefs.lazyZip(erasedParams).map(paramText), ", ")
303303
}
304304

305305
protected def ParamRefNameString(name: Name): String = nameString(name)
@@ -363,7 +363,7 @@ class PlainPrinter(_ctx: Context) extends Printer {
363363
case tp @ ConstantType(value) =>
364364
toText(value)
365365
case pref: TermParamRef =>
366-
nameString(pref.binder.paramNames(pref.paramNum)) ~ lambdaHash(pref.binder)
366+
ParamRefNameString(pref) ~ lambdaHash(pref.binder)
367367
case tp: RecThis =>
368368
val idx = openRecs.reverse.indexOf(tp.binder)
369369
if (idx >= 0) selfRecName(idx + 1)

compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
168168
~ " " ~ argText(args.last)
169169
}
170170

171-
private def toTextMethodAsFunction(info: Type, isPure: Boolean, refs: Text = Str("")): Text = info match
171+
protected def toTextMethodAsFunction(info: Type, isPure: Boolean, refs: Text = Str("")): Text = info match
172172
case info: MethodType =>
173173
val capturesRoot = refs == rootSetText
174174
changePrec(GlobalPrec) {

compiler/src/dotty/tools/dotc/reporting/Message.scala

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,13 @@ object Message:
5151
*/
5252
private class Seen(disambiguate: Boolean):
5353

54+
/** The set of lambdas that were opened at some point during printing. */
55+
private val openedLambdas = new collection.mutable.HashSet[LambdaType]
56+
57+
/** Register that `tp` was opened during printing. */
58+
def openLambda(tp: LambdaType): Unit =
59+
openedLambdas += tp
60+
5461
val seen = new collection.mutable.HashMap[SeenKey, List[Recorded]]:
5562
override def default(key: SeenKey) = Nil
5663

@@ -89,8 +96,22 @@ object Message:
8996
val existing = seen(key)
9097
lazy val dealiased = followAlias(entry)
9198

92-
// alts: The alternatives in `existing` that are equal, or follow (an alias of) `entry`
93-
var alts = existing.dropWhile(alt => dealiased ne followAlias(alt))
99+
/** All lambda parameters with the same name are given the same superscript as
100+
* long as their corresponding binder has been printed.
101+
* See tests/neg/lambda-rename.scala for test cases.
102+
*/
103+
def sameSuperscript(cur: Recorded, existing: Recorded) =
104+
(cur eq existing) ||
105+
(cur, existing).match
106+
case (cur: ParamRef, existing: ParamRef) =>
107+
(cur.paramName eq existing.paramName) &&
108+
openedLambdas.contains(cur.binder) &&
109+
openedLambdas.contains(existing.binder)
110+
case _ =>
111+
false
112+
113+
// The length of alts corresponds to the number of superscripts we need to print.
114+
var alts = existing.dropWhile(alt => !sameSuperscript(dealiased, followAlias(alt)))
94115
if alts.isEmpty then
95116
alts = entry :: existing
96117
seen(key) = alts
@@ -208,10 +229,20 @@ object Message:
208229
case tp: SkolemType => seen.record(tp.repr.toString, isType = true, tp)
209230
case _ => super.toTextRef(tp)
210231

232+
override def toTextMethodAsFunction(info: Type, isPure: Boolean, refs: Text): Text =
233+
info match
234+
case info: LambdaType =>
235+
seen.openLambda(info)
236+
case _ =>
237+
super.toTextMethodAsFunction(info, isPure, refs)
238+
211239
override def toText(tp: Type): Text =
212240
if !tp.exists || tp.isErroneous then seen.nonSensical = true
213241
tp match
214242
case tp: TypeRef if useSourceModule(tp.symbol) => Str("object ") ~ super.toText(tp)
243+
case tp: LambdaType =>
244+
seen.openLambda(tp)
245+
super.toText(tp)
215246
case _ => super.toText(tp)
216247

217248
override def toText(sym: Symbol): Text =

tests/neg/lambda-rename.check

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
-- [E007] Type Mismatch Error: tests/neg/lambda-rename.scala:4:33 ------------------------------------------------------
2+
4 |val a: (x: Int) => Bar[x.type] = ??? : ((x: Int) => Foo[x.type]) // error
3+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
4+
| Found: (x: Int) => Foo[x.type]
5+
| Required: (x: Int) => Bar[x.type]
6+
|
7+
| longer explanation available when compiling with `-explain`
8+
-- [E007] Type Mismatch Error: tests/neg/lambda-rename.scala:7:33 ------------------------------------------------------
9+
7 |val b: HK[[X] =>> Foo[(X, X)]] = ??? : HK[[X] =>> Bar[(X, X)]] // error
10+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
11+
| Found: HK[[X] =>> Bar[(X, X)]]
12+
| Required: HK[[X] =>> Foo[(X, X)]]
13+
|
14+
| longer explanation available when compiling with `-explain`
15+
-- [E007] Type Mismatch Error: tests/neg/lambda-rename.scala:10:33 -----------------------------------------------------
16+
10 |val c: HK[[X] =>> Foo[(X, X)]] = ??? : HK[[Y] =>> Foo[(X, X)]] // error
17+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
18+
| Found: HK[[Y] =>> Foo[(X, X)]]
19+
| Required: HK[[X²] =>> Foo[(X², X²)]]
20+
|
21+
| where: X is a class
22+
| X² is a type variable
23+
|
24+
| longer explanation available when compiling with `-explain`
25+
-- [E007] Type Mismatch Error: tests/neg/lambda-rename.scala:12:33 -----------------------------------------------------
26+
12 |val d: HK[[Y] =>> Foo[(X, X)]] = ??? : HK[[X] =>> Foo[(X, X)]] // error
27+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
28+
| Found: HK[[X] =>> Foo[(X, X)]]
29+
| Required: HK[[Y] =>> Foo[(X², X²)]]
30+
|
31+
| where: X is a type variable
32+
| X² is a class
33+
|
34+
| longer explanation available when compiling with `-explain`

tests/neg/lambda-rename.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
class Foo[T]
2+
class Bar[T]
3+
4+
val a: (x: Int) => Bar[x.type] = ??? : ((x: Int) => Foo[x.type]) // error
5+
6+
trait HK[F <: AnyKind]
7+
val b: HK[[X] =>> Foo[(X, X)]] = ??? : HK[[X] =>> Bar[(X, X)]] // error
8+
9+
class X
10+
val c: HK[[X] =>> Foo[(X, X)]] = ??? : HK[[Y] =>> Foo[(X, X)]] // error
11+
12+
val d: HK[[Y] =>> Foo[(X, X)]] = ??? : HK[[X] =>> Foo[(X, X)]] // error

0 commit comments

Comments
 (0)