Skip to content

Change explaining printer #6476

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
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
51 changes: 37 additions & 14 deletions compiler/src/dotty/tools/dotc/printing/Formatting.scala
Original file line number Diff line number Diff line change
Expand Up @@ -106,39 +106,59 @@ object Formatting {

private type Recorded = AnyRef /*Symbol | ParamRef | SkolemType */

private class Seen extends mutable.HashMap[String, List[Recorded]] {
private case class SeenKey(str: String, isType: Boolean)
private class Seen extends mutable.HashMap[SeenKey, List[Recorded]] {

override def default(key: String) = Nil
override def default(key: SeenKey) = Nil

def record(str: String, entry: Recorded)(implicit ctx: Context): String = {
def record(str: String, isType: Boolean, entry: Recorded)(implicit ctx: Context): String = {

/** If `e1` is an alias of another class of the same name, return the other
* class symbol instead. This normalization avoids recording e.g. scala.List
* and scala.collection.immutable.List as two different types
*/
def followAlias(e1: Recorded): Recorded = e1 match {
case e1: Symbol if e1.isAliasType =>
val underlying = e1.typeRef.underlyingClassRef(refinementOK = false).typeSymbol
if (underlying.name == e1.name) underlying else e1
case _ => e1
}
val key = SeenKey(str, isType)
val existing = apply(key)
lazy val dealiased = followAlias(entry)
var alts = apply(str).dropWhile(alt => dealiased ne followAlias(alt))

// alts: The alternatives in `existing` that are equal, or follow (an alias of) `entry`
var alts = existing.dropWhile(alt => dealiased ne followAlias(alt))
if (alts.isEmpty) {
alts = entry :: apply(str)
update(str, alts)
alts = entry :: existing
update(key, alts)
}
str + "'" * (alts.length - 1)
}
}

private class ExplainingPrinter(seen: Seen)(_ctx: Context) extends RefinedPrinter(_ctx) {

/** True if printer should a source module instead of its module class */
private def useSourceModule(sym: Symbol): Boolean =
sym.is(ModuleClass, butNot = Package) && sym.sourceModule.exists && !_ctx.settings.YdebugNames.value

override def simpleNameString(sym: Symbol): String =
if ((sym is ModuleClass) && sym.sourceModule.exists) simpleNameString(sym.sourceModule)
else seen.record(super.simpleNameString(sym), sym)
if (useSourceModule(sym)) simpleNameString(sym.sourceModule)
else seen.record(super.simpleNameString(sym), sym.isType, sym)

override def ParamRefNameString(param: ParamRef): String =
seen.record(super.ParamRefNameString(param), param)
seen.record(super.ParamRefNameString(param), param.isInstanceOf[TypeParamRef], param)

override def toTextRef(tp: SingletonType): Text = tp match {
case tp: SkolemType => seen.record(tp.repr.toString, tp)
case tp: SkolemType => seen.record(tp.repr.toString, isType = true, tp)
case _ => super.toTextRef(tp)
}

override def toText(tp: Type): Text = tp match {
case tp: TypeRef if useSourceModule(tp.symbol) => Str("object ") ~ super.toText(tp)
case _ => super.toText(tp)
}
}

/** Create explanation for single `Recorded` type or symbol */
Expand Down Expand Up @@ -197,10 +217,13 @@ object Formatting {
}

val toExplain: List[(String, Recorded)] = seen.toList.flatMap {
case (str, entry :: Nil) =>
if (needsExplanation(entry)) (str, entry) :: Nil else Nil
case (str, entries) =>
entries.map(alt => (seen.record(str, alt), alt))
case (key, entry :: Nil) =>
if (needsExplanation(entry)) (key.str, entry) :: Nil else Nil
case (key, entries) =>
for (alt <- entries) yield {
val tickedString = seen.record(key.str, key.isType, alt)
(tickedString, alt)
}
}.sortBy(_._1)

def columnar(parts: List[(String, String)]): List[String] = {
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ class PlainPrinter(_ctx: Context) extends Printer {
}
}

/** The string representation of this type used as a prefix */
/** The string representation of this type used as a prefix, including separator */
protected def toTextPrefix(tp: Type): Text = controlled {
homogenize(tp) match {
case NoPrefix => ""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
if (ctx.settings.YdebugNames.value) name.debugString else NameTransformer.decodeIllegalChars(name.toString)

override protected def simpleNameString(sym: Symbol): String =
nameString(if (ctx.property(XprintMode).isEmpty) sym.originalName else sym.name)
nameString(if (ctx.property(XprintMode).isEmpty) sym.initial.name else sym.name)

override def fullNameString(sym: Symbol): String =
if (isEmptyPrefix(sym.maybeOwner)) nameString(sym)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ class CompletionTest {
withSources(
code"""object O { object MyObject }""",
code"""import O.My${m1}"""
).completion(m1, Set(("MyObject", Module, "O.MyObject")))
).completion(m1, Set(("MyObject", Module, "O.MyObject$")))
}

@Test def importCompleteWithClassAndCompanion: Unit = {
Expand Down Expand Up @@ -114,7 +114,7 @@ class CompletionTest {
).completion(m1, Set(("myVal", Field, "Int"),
("myDef", Method, "=> Int"),
("myVar", Variable, "Int"),
("myObject", Module, "MyObject.myObject"),
("myObject", Module, "MyObject.myObject$"),
("myClass", Class, "MyObject.myClass"),
("myTrait", Class, "MyObject.myTrait")))
}
Expand All @@ -138,7 +138,7 @@ class CompletionTest {
code"""object O {
val out = java.io.FileDesc${m1}
}""".withSource
.completion(m1, Set(("FileDescriptor", Module, "java.io.FileDescriptor")))
.completion(m1, Set(("FileDescriptor", Module, "java.io.FileDescriptor$")))
}

@Test def importRename: Unit = {
Expand Down Expand Up @@ -197,7 +197,7 @@ class CompletionTest {
| object bat
| val bizz: ba${m1}
|}""".withSource
.completion(m1, Set(("bar", Field, "Bar"), ("bat", Module, "Foo.bat")))
.completion(m1, Set(("bar", Field, "Bar"), ("bat", Module, "Foo.bat$")))
}

@Test def completionOnRenamedImport: Unit = {
Expand Down