Skip to content

Commit 4af1386

Browse files
authored
Merge pull request #11643 from dotty-staging/fix-11637
Merge -explain-types behavior into -explain behavior
2 parents 2961f69 + 9b45f99 commit 4af1386

40 files changed

+277
-101
lines changed

compiler/src/dotty/tools/dotc/config/ScalaSettings.scala

+3-1
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,9 @@ class ScalaSettings extends Settings.SettingGroup with CommonScalaSettings {
103103
val semanticdbTarget: Setting[String] = PathSetting("-semanticdb-target", "Specify an alternative output directory for SemanticDB files.", "")
104104

105105
val deprecation: Setting[Boolean] = BooleanSetting("-deprecation", "Emit warning and location for usages of deprecated APIs.", aliases = List("--deprecation"))
106-
val explainTypes: Setting[Boolean] = BooleanSetting("-explain-types", "Explain type errors in more detail.", aliases = List("--explain-types"))
106+
val explainTypes: Setting[Boolean] = BooleanSetting("-explain-types", "Explain type errors in more detail (deprecated, use -explain instead).", aliases = List("--explain-types"))
107+
// this setting is necessary for cross compilation, since it is mentioned in sbt-tpolecat, for instance
108+
// it is otherwise subsumed by -explain, and should be dropped as soon as we can.
107109
val explain: Setting[Boolean] = BooleanSetting("-explain", "Explain errors in more detail.", aliases = List("--explain"))
108110
val feature: Setting[Boolean] = BooleanSetting("-feature", "Emit warning and location for usages of features that should be imported explicitly.", aliases = List("--feature"))
109111
val release: Setting[String] = ChoiceSetting("-release", "release", "Compile code with classes specific to the given version of the Java platform available on the classpath and emit bytecode for this version.", supportedReleaseVersions, "", aliases = List("--release"))

compiler/src/dotty/tools/dotc/core/Constraint.scala

+3
Original file line numberDiff line numberDiff line change
@@ -168,4 +168,7 @@ abstract class Constraint extends Showable {
168168

169169
/** Check that constraint only refers to TypeParamRefs bound by itself */
170170
def checkClosed()(using Context): Unit
171+
172+
/** A string describing the constraint's contents without a header or trailer */
173+
def contentsToString(using Context): String
171174
}

compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala

+13-9
Original file line numberDiff line numberDiff line change
@@ -648,7 +648,7 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
648648

649649
// ---------- toText -----------------------------------------------------
650650

651-
override def toText(printer: Printer): Text = {
651+
private def contentsToText(printer: Printer): Text =
652652
//Printer.debugPrintUnique = true
653653
def entryText(tp: Type) = tp match {
654654
case tp: TypeBounds =>
@@ -657,20 +657,19 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
657657
" := " ~ tp.toText(printer)
658658
}
659659
val indent = 3
660-
val header: Text = "Constraint("
661-
val uninstVarsText = " uninstVars = " ~
662-
Text(uninstVars map (_.toText(printer)), ", ") ~ ";"
660+
val uninstVarsText = " uninstantiated variables: " ~
661+
Text(uninstVars.map(_.toText(printer)), ", ")
663662
val constrainedText =
664-
" constrained types = " ~ Text(domainLambdas map (_.toText(printer)), ", ")
663+
" constrained types: " ~ Text(domainLambdas map (_.toText(printer)), ", ")
665664
val boundsText =
666-
" bounds = " ~ {
665+
" bounds: " ~ {
667666
val assocs =
668667
for (param <- domainParams)
669668
yield (" " * indent) ~ param.toText(printer) ~ entryText(entry(param))
670669
Text(assocs, "\n")
671670
}
672671
val orderingText =
673-
" ordering = " ~ {
672+
" ordering: " ~ {
674673
val deps =
675674
for {
676675
param <- domainParams
@@ -683,8 +682,13 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
683682
Text(deps, "\n")
684683
}
685684
//Printer.debugPrintUnique = false
686-
Text.lines(List(header, uninstVarsText, constrainedText, boundsText, orderingText, ")"))
687-
}
685+
Text.lines(List(uninstVarsText, constrainedText, boundsText, orderingText))
686+
687+
override def toText(printer: Printer): Text =
688+
Text.lines(List("Constraint(", contentsToText(printer), ")"))
689+
690+
def contentsToString(using Context): String =
691+
contentsToText(ctx.printer).show
688692

689693
override def toString: String = {
690694
def entryText(tp: Type): String = tp match {

compiler/src/dotty/tools/dotc/core/TypeComparer.scala

+20-21
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
206206
* code would have two extra parameters for each of the many calls that go from
207207
* one sub-part of isSubType to another.
208208
*/
209-
protected def recur(tp1: Type, tp2: Type): Boolean = trace(s"isSubType ${traceInfo(tp1, tp2)} ${approx.show}", subtyping) {
209+
protected def recur(tp1: Type, tp2: Type): Boolean = trace(s"isSubType ${traceInfo(tp1, tp2)}${approx.show}", subtyping) {
210210

211211
def monitoredIsSubType = {
212212
if (pendingSubTypes == null) {
@@ -2276,13 +2276,6 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
22762276
NoType
22772277
}
22782278

2279-
/** Show type, handling type types better than the default */
2280-
private def showType(tp: Type)(using Context) = tp match {
2281-
case ClassInfo(_, cls, _, _, _) => cls.showLocated
2282-
case bounds: TypeBounds => "type bounds" + bounds.show
2283-
case _ => tp.show
2284-
}
2285-
22862279
/** A comparison function to pick a winner in case of a merge conflict */
22872280
private def isAsGood(tp1: Type, tp2: Type): Boolean = tp1 match {
22882281
case tp1: ClassInfo =>
@@ -2542,10 +2535,10 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
25422535
finally myInstance = saved
25432536

25442537
/** The trace of comparison operations when performing `op` */
2545-
def explained[T](op: ExplainingTypeComparer => T)(using Context): String =
2538+
def explained[T](op: ExplainingTypeComparer => T, header: String = "Subtype trace:")(using Context): String =
25462539
val cmp = explainingTypeComparer
25472540
inSubComparer(cmp)(op)
2548-
cmp.lastTrace()
2541+
cmp.lastTrace(header)
25492542

25502543
def tracked[T](op: TrackingTypeComparer => T)(using Context): T =
25512544
inSubComparer(trackingTypeComparer)(op)
@@ -2561,10 +2554,13 @@ object TypeComparer {
25612554
var tpe: Type = NoType
25622555
}
25632556

2564-
private[core] def show(res: Any)(using Context): String = res match {
2565-
case res: printing.Showable if !ctx.settings.YexplainLowlevel.value => res.show
2566-
case _ => String.valueOf(res)
2567-
}
2557+
private[core] def show(res: Any)(using Context): String =
2558+
if ctx.settings.YexplainLowlevel.value then String.valueOf(res)
2559+
else res match
2560+
case ClassInfo(_, cls, _, _, _) => cls.showLocated
2561+
case bounds: TypeBounds => i"type bounds [$bounds]"
2562+
case res: printing.Showable => res.show
2563+
case _ => String.valueOf(res)
25682564

25692565
/** The approximation state indicates how the pair of types currently compared
25702566
* relates to the types compared originally.
@@ -2591,8 +2587,8 @@ object TypeComparer {
25912587
def addLow: Repr = approx | LoApprox
25922588
def addHigh: Repr = approx | HiApprox
25932589
def show: String =
2594-
val lo = if low then "LoApprox" else ""
2595-
val hi = if high then "HiApprox" else ""
2590+
val lo = if low then " (left is approximated)" else ""
2591+
val hi = if high then " (right is approximated)" else ""
25962592
lo ++ hi
25972593
end ApproxState
25982594
type ApproxState = ApproxState.Repr
@@ -2694,8 +2690,8 @@ object TypeComparer {
26942690
def constrainPatternType(pat: Type, scrut: Type)(using Context): Boolean =
26952691
comparing(_.constrainPatternType(pat, scrut))
26962692

2697-
def explained[T](op: ExplainingTypeComparer => T)(using Context): String =
2698-
comparing(_.explained(op))
2693+
def explained[T](op: ExplainingTypeComparer => T, header: String = "Subtype trace:")(using Context): String =
2694+
comparing(_.explained(op, header))
26992695

27002696
def tracked[T](op: TrackingTypeComparer => T)(using Context): T =
27012697
comparing(_.tracked(op))
@@ -2844,17 +2840,20 @@ class ExplainingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
28442840
res
28452841
}
28462842

2843+
private def frozenNotice: String =
2844+
if frozenConstraint then " in frozen constraint" else ""
2845+
28472846
override def isSubType(tp1: Type, tp2: Type, approx: ApproxState): Boolean =
28482847
def moreInfo =
28492848
if Config.verboseExplainSubtype || ctx.settings.verbose.value
28502849
then s" ${tp1.getClass} ${tp2.getClass}"
28512850
else ""
2852-
traceIndented(s"${show(tp1)} <:< ${show(tp2)}$moreInfo ${approx.show} ${if (frozenConstraint) " frozen" else ""}") {
2851+
traceIndented(s"${show(tp1)} <: ${show(tp2)}$moreInfo${approx.show}$frozenNotice") {
28532852
super.isSubType(tp1, tp2, approx)
28542853
}
28552854

28562855
override def recur(tp1: Type, tp2: Type): Boolean =
2857-
traceIndented(s"${show(tp1)} <:< ${show(tp2)} recur ${if (frozenConstraint) " frozen" else ""}") {
2856+
traceIndented(s"${show(tp1)} <: ${show(tp2)} (recurring)$frozenNotice") {
28582857
super.recur(tp1, tp2)
28592858
}
28602859

@@ -2878,5 +2877,5 @@ class ExplainingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
28782877
super.addConstraint(param, bound, fromBelow)
28792878
}
28802879

2881-
def lastTrace(): String = "Subtype trace:" + { try b.toString finally b.clear() }
2880+
def lastTrace(header: String): String = header + { try b.toString finally b.clear() }
28822881
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ class ConsoleReporter(
3535

3636
if (didPrint && shouldExplain(dia))
3737
printMessage(explanation(dia.msg))
38-
else if (didPrint && dia.msg.explanation.nonEmpty)
38+
else if (didPrint && dia.msg.canExplain)
3939
printMessage("\nlonger explanation available when compiling with `-explain`")
4040
}
4141

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ import java.util.Optional
1212
object Diagnostic:
1313

1414
def shouldExplain(dia: Diagnostic)(using Context): Boolean =
15-
dia.msg.explanation.nonEmpty && ctx.settings.explain.value
15+
ctx.settings.explain.value && dia.msg.canExplain
16+
|| ctx.settings.explainTypes.value && dia.msg.isInstanceOf[TypeMismatchMsg]
17+
// keep old explain-types behavior for backwards compatibility and cross-compilation
1618

1719
// `Diagnostics to be consumed by `Reporter` ---------------------- //
1820
class Error(

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,9 @@ enum ErrorMessageID extends java.lang.Enum[ErrorMessageID] {
170170
TraitMayNotDefineNativeMethodID,
171171
JavaEnumParentArgsID,
172172
AlreadyDefinedID,
173-
CaseClassInInlinedCodeID
173+
CaseClassInInlinedCodeID,
174+
OverrideTypeMismatchErrorID,
175+
OverrideErrorID
174176

175177
def errorNumber = ordinal - 2
176178
}

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

+19-9
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,13 @@ abstract class Message(val errorId: ErrorMessageID) { self =>
5858
*/
5959
protected def explain: String
6060

61+
/** Does this message have an explanation?
62+
* This is normally the same as `explain.nonEmpty` but can be overridden
63+
* if we need a way to return `true` without actually calling the
64+
* `explain` method.
65+
*/
66+
def canExplain: Boolean = explain.nonEmpty
67+
6168
private var myMsg: String | Null = null
6269
private var myIsNonSensical: Boolean = false
6370

@@ -95,22 +102,25 @@ abstract class Message(val errorId: ErrorMessageID) { self =>
95102
* that was captured in the original message.
96103
*/
97104
def persist: Message = new Message(errorId) {
98-
val kind = self.kind
99-
val msg = self.msg
100-
val explain = self.explain
105+
val kind = self.kind
106+
val msg = self.msg
107+
val explain = self.explain
108+
override val canExplain = self.canExplain
101109
}
102110

103111
def append(suffix: => String): Message = mapMsg(_ ++ suffix)
104112

105113
def mapMsg(f: String => String): Message = new Message(errorId):
106-
val kind = self.kind
107-
def msg = f(self.msg)
108-
def explain = self.explain
114+
val kind = self.kind
115+
def msg = f(self.msg)
116+
def explain = self.explain
117+
override def canExplain = self.canExplain
109118

110119
def appendExplanation(suffix: => String): Message = new Message(errorId):
111-
val kind = self.kind
112-
def msg = self.msg
113-
def explain = self.explain ++ suffix
120+
val kind = self.kind
121+
def msg = self.msg
122+
def explain = self.explain ++ suffix
123+
override def canExplain = true
114124

115125
override def toString = msg
116126
}

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

+20-26
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,10 @@ import transform.SymUtils._
4545
abstract class TypeMsg(errorId: ErrorMessageID) extends Message(errorId):
4646
def kind = "Type"
4747

48-
abstract class TypeMismatchMsg(errorId: ErrorMessageID) extends Message(errorId):
48+
abstract class TypeMismatchMsg(found: Type, expected: Type)(errorId: ErrorMessageID)(using Context) extends Message(errorId):
4949
def kind = "Type Mismatch"
50+
def explain = err.whyNoMatchStr(found, expected)
51+
override def canExplain = true
5052

5153
abstract class NamingMsg(errorId: ErrorMessageID) extends Message(errorId):
5254
def kind = "Naming"
@@ -237,7 +239,7 @@ import transform.SymUtils._
237239
}
238240

239241
class TypeMismatch(found: Type, expected: Type, addenda: => String*)(using Context)
240-
extends TypeMismatchMsg(TypeMismatchID):
242+
extends TypeMismatchMsg(found, expected)(TypeMismatchID):
241243

242244
// replace constrained TypeParamRefs and their typevars by their bounds where possible
243245
// the idea is that if the bounds are also not-subtypes of each other to report
@@ -275,9 +277,7 @@ import transform.SymUtils._
275277
val (foundStr, expectedStr) = Formatting.typeDiff(found2, expected2)(using printCtx)
276278
s"""|Found: $foundStr
277279
|Required: $expectedStr""".stripMargin
278-
+ whereSuffix + err.whyNoMatchStr(found, expected) + postScript
279-
280-
def explain = ""
280+
+ whereSuffix + postScript
281281
end TypeMismatch
282282

283283
class NotAMember(site: Type, val name: Name, selected: String, addendum: => String = "")(using Context)
@@ -1075,6 +1075,14 @@ import transform.SymUtils._
10751075
|"""
10761076
}
10771077

1078+
class OverrideError(override val msg: String) extends DeclarationMsg(OverrideErrorID):
1079+
def explain = ""
1080+
1081+
class OverrideTypeMismatchError(override val msg: String, memberTp: Type, otherTp: Type)(using Context)
1082+
extends DeclarationMsg(OverrideTypeMismatchErrorID):
1083+
def explain = err.whyNoMatchStr(memberTp, otherTp)
1084+
override def canExplain = true
1085+
10781086
class ForwardReferenceExtendsOverDefinition(value: Symbol, definition: Symbol)(using Context)
10791087
extends ReferenceMsg(ForwardReferenceExtendsOverDefinitionID) {
10801088
def msg = em"${definition.name} is a forward reference extending over the definition of ${value.name}"
@@ -1325,7 +1333,7 @@ import transform.SymUtils._
13251333
if (isNullary) "\nNullary methods may not be called with parenthesis"
13261334
else ""
13271335

1328-
"You have specified more parameter lists as defined in the method definition(s)." + addendum
1336+
"You have specified more parameter lists than defined in the method definition(s)." + addendum
13291337
}
13301338

13311339
}
@@ -1415,36 +1423,22 @@ import transform.SymUtils._
14151423
}
14161424

14171425
class DoesNotConformToBound(tpe: Type, which: String, bound: Type)(using Context)
1418-
extends TypeMismatchMsg(DoesNotConformToBoundID) {
1419-
def msg = em"Type argument ${tpe} does not conform to $which bound $bound${err.whyNoMatchStr(tpe, bound)}"
1420-
def explain = ""
1426+
extends TypeMismatchMsg(tpe, bound)(DoesNotConformToBoundID) {
1427+
def msg = em"Type argument ${tpe} does not conform to $which bound $bound"
14211428
}
14221429

14231430
class DoesNotConformToSelfType(category: String, selfType: Type, cls: Symbol,
1424-
otherSelf: Type, relation: String, other: Symbol)(
1431+
otherSelf: Type, relation: String, other: Symbol)(
14251432
implicit ctx: Context)
1426-
extends TypeMismatchMsg(DoesNotConformToSelfTypeID) {
1433+
extends TypeMismatchMsg(selfType, otherSelf)(DoesNotConformToSelfTypeID) {
14271434
def msg = em"""$category: self type $selfType of $cls does not conform to self type $otherSelf
14281435
|of $relation $other"""
1429-
def explain =
1430-
em"""You mixed in $other which requires self type $otherSelf, but $cls has self type
1431-
|$selfType and does not inherit from $otherSelf.
1432-
|
1433-
|Note: Self types are indicated with the notation
1434-
| ${s"class "}$other ${hl("{ this: ")}$otherSelf${hl(" => ")}
1435-
"""
14361436
}
14371437

14381438
class DoesNotConformToSelfTypeCantBeInstantiated(tp: Type, selfType: Type)(
14391439
implicit ctx: Context)
1440-
extends TypeMismatchMsg(DoesNotConformToSelfTypeCantBeInstantiatedID) {
1440+
extends TypeMismatchMsg(tp, selfType)(DoesNotConformToSelfTypeCantBeInstantiatedID) {
14411441
def msg = em"""$tp does not conform to its self type $selfType; cannot be instantiated"""
1442-
def explain =
1443-
em"""To create an instance of $tp it needs to inherit $selfType in some way.
1444-
|
1445-
|Note: Self types are indicated with the notation
1446-
| ${s"class "}$tp ${hl("{ this: ")}$selfType${hl(" => ")}
1447-
|"""
14481442
}
14491443

14501444
class AbstractMemberMayNotHaveModifier(sym: Symbol, flag: FlagSet)(
@@ -2208,7 +2202,7 @@ import transform.SymUtils._
22082202
}
22092203

22102204
class NoMatchingOverload(val alternatives: List[SingleDenotation], pt: Type)(using Context)
2211-
extends TypeMismatchMsg(NoMatchingOverloadID) {
2205+
extends TypeMsg(NoMatchingOverloadID) {
22122206
def msg =
22132207
em"""None of the ${err.overloadedAltsStr(alternatives)}
22142208
|match ${err.expectedTypeStr(pt)}"""

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

+19-8
Original file line numberDiff line numberDiff line change
@@ -117,14 +117,25 @@ object ErrorReporting {
117117
}
118118

119119
/** A subtype log explaining why `found` does not conform to `expected` */
120-
def whyNoMatchStr(found: Type, expected: Type): String = {
121-
if (ctx.settings.explainTypes.value)
122-
i"""
123-
|${ctx.typerState.constraint}
124-
|${TypeComparer.explained(_.isSubType(found, expected))}"""
125-
else
126-
""
127-
}
120+
def whyNoMatchStr(found: Type, expected: Type): String =
121+
val header =
122+
i"""I tried to show that
123+
| $found
124+
|conforms to
125+
| $expected
126+
|but the comparison trace ended with `false`:
127+
"""
128+
val c = ctx.typerState.constraint
129+
val constraintText =
130+
if c.domainLambdas.isEmpty then
131+
"the empty constraint"
132+
else
133+
i"""a constraint with:
134+
|${c.contentsToString}"""
135+
i"""
136+
|${TypeComparer.explained(_.isSubType(found, expected), header)}
137+
|
138+
|The tests were made under $constraintText"""
128139

129140
/** Format `raw` implicitNotFound or implicitAmbiguous argument, replacing
130141
* all occurrences of `${X}` where `X` is in `paramNames` with the

0 commit comments

Comments
 (0)