From 33781d79f9eb7814594a688484f08e85c32397fb Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Sun, 7 Jul 2024 14:52:42 -0700 Subject: [PATCH 1/3] Simplify failure addendum --- .../tools/dotc/reporting/Diagnostic.scala | 2 ++ .../dotty/tools/dotc/typer/Applications.scala | 27 +++++++------------ 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/reporting/Diagnostic.scala b/compiler/src/dotty/tools/dotc/reporting/Diagnostic.scala index 7a8edb233aee..bafc11f78533 100644 --- a/compiler/src/dotty/tools/dotc/reporting/Diagnostic.scala +++ b/compiler/src/dotty/tools/dotc/reporting/Diagnostic.scala @@ -85,6 +85,8 @@ object Diagnostic: pos: SourcePosition ) extends Warning(msg, pos) + def unapply(d: Diagnostic): (Message, SourcePosition, Int) = (d.msg, d.pos, d.level) + class Diagnostic( val msg: Message, val pos: SourcePosition, diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 74c20812893b..12315cae38e8 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1105,23 +1105,16 @@ trait Applications extends Compatibility { if fun1.symbol.name == nme.apply && fun1.span.isSynthetic then fun1 match case Select(qualifier, _) => - def mapMessage(dia: Diagnostic): Diagnostic = - dia match - case dia: Diagnostic.Error => - dia.msg match - case msg: TypeMismatch => - msg.inTree match - case Some(arg) if tree.args.exists(_.span == arg.span) => - val noteText = - i"""The required type comes from a parameter of the automatically - |inserted `apply` method of `${qualifier.tpe}`.""".stripMargin - Diagnostic.Error(msg.appendExplanation("\n\n" + noteText), dia.pos) - case _ => dia - case msg => dia - case dia => dia - failedState.reporter.mapBufferedMessages(mapMessage) - case _ => () - else () + import dotty.tools.dotc.interfaces.Diagnostic.ERROR + failedState.reporter.mapBufferedMessages: + case Diagnostic(msg: TypeMismatch, pos, ERROR) + if msg.inTree.exists(t => tree.args.exists(_.span == t.span)) => + val noteText = + i"""The required type comes from a parameter of the automatically + |inserted `apply` method of `${qualifier.tpe}`.""".stripMargin + Diagnostic.Error(msg.appendExplanation("\n\n" + noteText), pos) + case dia => dia + case _ => fun1.tpe match { case err: ErrorType => cpy.Apply(tree)(fun1, proto.typedArgs()).withType(err) From cfe8305b0dee2578de5c67627736bb200dd36861 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Sun, 7 Jul 2024 11:04:15 -0700 Subject: [PATCH 2/3] Addendum when apply error involves default arg This is useful because the default arg is not visible as the reason the application did not typecheck. It uses the existing mechanism to update messages. --- .../dotc/reporting/ExploringReporter.scala | 4 +- .../dotty/tools/dotc/reporting/Message.scala | 10 +++-- .../tools/dotc/reporting/StoreReporter.scala | 6 +-- .../dotty/tools/dotc/typer/Applications.scala | 40 +++++++++++++------ tests/neg/t4727.check | 25 ++++++++++++ tests/{untried => }/neg/t4727.scala | 4 +- tests/untried/neg/t4727.check | 5 --- 7 files changed, 66 insertions(+), 28 deletions(-) create mode 100644 tests/neg/t4727.check rename tests/{untried => }/neg/t4727.scala (62%) delete mode 100644 tests/untried/neg/t4727.check diff --git a/compiler/src/dotty/tools/dotc/reporting/ExploringReporter.scala b/compiler/src/dotty/tools/dotc/reporting/ExploringReporter.scala index 99720b8e4d29..cd3650118c1d 100644 --- a/compiler/src/dotty/tools/dotc/reporting/ExploringReporter.scala +++ b/compiler/src/dotty/tools/dotc/reporting/ExploringReporter.scala @@ -4,13 +4,13 @@ package reporting import scala.language.unsafeNulls -import collection.mutable +import collection.mutable.ListBuffer import core.Contexts.Context import Diagnostic.* /** A re-usable Reporter used in Contexts#test */ class ExploringReporter extends StoreReporter(null, fromTyperState = false): - infos = new mutable.ListBuffer[Diagnostic] + infos = ListBuffer.empty[Diagnostic] override def hasUnreportedErrors: Boolean = infos.exists(_.isInstanceOf[Error]) diff --git a/compiler/src/dotty/tools/dotc/reporting/Message.scala b/compiler/src/dotty/tools/dotc/reporting/Message.scala index 1ac5c6ecf407..bf91d45ddf10 100644 --- a/compiler/src/dotty/tools/dotc/reporting/Message.scala +++ b/compiler/src/dotty/tools/dotc/reporting/Message.scala @@ -394,14 +394,16 @@ abstract class Message(val errorId: ErrorMessageID)(using Context) { self => def mapMsg(f: String => String): Message = new Message(errorId): val kind = self.kind - def msg(using Context) = f(self.msg) - def explain(using Context) = self.explain + protected def msg(using Context) = f(self.msg) + override protected def msgPostscript(using Context) = self.msgPostscript + protected def explain(using Context) = self.explain override def canExplain = self.canExplain def appendExplanation(suffix: => String): Message = new Message(errorId): val kind = self.kind - def msg(using Context) = self.msg - def explain(using Context) = self.explain ++ suffix + protected def msg(using Context) = self.msg + override protected def msgPostscript(using Context) = self.msgPostscript + protected def explain(using Context) = self.explain ++ suffix override def canExplain = true /** Override with `true` for messages that should always be shown even if their diff --git a/compiler/src/dotty/tools/dotc/reporting/StoreReporter.scala b/compiler/src/dotty/tools/dotc/reporting/StoreReporter.scala index 9395788d4cc7..8f95ca54eb0b 100644 --- a/compiler/src/dotty/tools/dotc/reporting/StoreReporter.scala +++ b/compiler/src/dotty/tools/dotc/reporting/StoreReporter.scala @@ -3,7 +3,7 @@ package dotc package reporting import core.Contexts.* -import collection.mutable +import collection.mutable.ListBuffer import config.Printers.typr import Diagnostic.* @@ -19,11 +19,11 @@ import Diagnostic.* */ class StoreReporter(outer: Reporter | Null = Reporter.NoReporter, fromTyperState: Boolean = false) extends Reporter { - protected var infos: mutable.ListBuffer[Diagnostic] | Null = null + protected var infos: ListBuffer[Diagnostic] | Null = null override def doReport(dia: Diagnostic)(using Context): Unit = { typr.println(s">>>> StoredError: ${dia.message}") // !!! DEBUG - if (infos == null) infos = new mutable.ListBuffer + if (infos == null) infos = ListBuffer.empty infos.uncheckedNN += dia } diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 12315cae38e8..1947692b1637 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -25,8 +25,10 @@ import reporting.* import Nullables.*, NullOpsDecorator.* import config.{Feature, SourceVersion} -import collection.mutable +import collection.mutable.ListBuffer import config.Printers.{overload, typr, unapp} +import inlines.Inlines +import interfaces.Diagnostic.ERROR import TypeApplications.* import Annotations.Annotation @@ -34,8 +36,7 @@ import Constants.{Constant, IntTag} import Denotations.SingleDenotation import annotation.threadUnsafe -import scala.util.control.NonFatal -import dotty.tools.dotc.inlines.Inlines +import scala.util.chaining.given object Applications { import tpd.* @@ -260,7 +261,7 @@ object Applications { end UnapplyArgs - def wrapDefs(defs: mutable.ListBuffer[Tree] | Null, tree: Tree)(using Context): Tree = + def wrapDefs(defs: ListBuffer[Tree] | Null, tree: Tree)(using Context): Tree = if (defs != null && defs.nonEmpty) tpd.Block(defs.toList, tree) else tree /** Optionally, if `sym` is a symbol created by `resolveMapped`, i.e. representing @@ -872,8 +873,8 @@ trait Applications extends Compatibility { extends Application(methRef, fun.tpe, args, resultType) { type TypedArg = Tree def isVarArg(arg: Trees.Tree[T]): Boolean = untpd.isWildcardStarArg(arg) - private var typedArgBuf = new mutable.ListBuffer[Tree] - private var liftedDefs: mutable.ListBuffer[Tree] | Null = null + private var typedArgBuf = ListBuffer.empty[Tree] + private var liftedDefs: ListBuffer[Tree] | Null = null private var myNormalizedFun: Tree = fun init() @@ -911,11 +912,11 @@ trait Applications extends Compatibility { override def liftFun(): Unit = if (liftedDefs == null) { - liftedDefs = new mutable.ListBuffer[Tree] + liftedDefs = ListBuffer.empty[Tree] myNormalizedFun = lifter.liftApp(liftedDefs.uncheckedNN, myNormalizedFun) } - /** The index of the first difference between lists of trees `xs` and `ys` + /** The index of the first difference between lists of trees `xs` and `ys`. * -1 if there are no differences. */ private def firstDiff[T <: Trees.Tree[?]](xs: List[T], ys: List[T], n: Int = 0): Int = xs match { @@ -939,11 +940,25 @@ trait Applications extends Compatibility { isPureExpr(arg) || arg.isInstanceOf[RefTree | Apply | TypeApply] && arg.symbol.name.is(DefaultGetterName) - val result: Tree = { + def defaultsAddendum(args: List[Tree]): Unit = + def check(arg: Tree): Boolean = arg match + case TypeApply(Select(_, name), _) => name.is(DefaultGetterName) + case Apply(Select(_, name), _) => name.is(DefaultGetterName) + case _ => false + val faulties = args.filter(check) + ctx.reporter.mapBufferedMessages: + case Diagnostic(msg: TypeMismatch, pos, ERROR) + if msg.inTree.exists(t => faulties.exists(_.span == t.span)) => + val noteText = i"Error occurred in an application involving default arguments." + val explained = i"Expanded application: ${cpy.Apply(app)(normalizedFun, args)}" + Diagnostic.Error(msg.append(s"\n$noteText").appendExplanation(s"\n\n$explained"), pos) + case dia => dia + + val result: Tree = { var typedArgs = typedArgBuf.toList def app0 = cpy.Apply(app)(normalizedFun, typedArgs) // needs to be a `def` because typedArgs can change later val app1 = - if (!success || typedArgs.exists(_.tpe.isError)) app0.withType(UnspecifiedErrorType) + if !success || typedArgs.exists(_.tpe.isError).tap(if (_) then defaultsAddendum(typedArgs)) then app0.withType(UnspecifiedErrorType) else { if isJavaAnnotConstr(methRef.symbol) then // #19951 Make sure all arguments are NamedArgs for Java annotations @@ -960,7 +975,7 @@ trait Applications extends Compatibility { liftFun() // lift arguments in the definition order - val argDefBuf = mutable.ListBuffer.empty[Tree] + val argDefBuf = ListBuffer.empty[Tree] typedArgs = lifter.liftArgs(argDefBuf, methType, typedArgs) // Lifted arguments ordered based on the original order of typedArgBuf and // with all non-explicit default parameters at the end in declaration order. @@ -1105,7 +1120,6 @@ trait Applications extends Compatibility { if fun1.symbol.name == nme.apply && fun1.span.isSynthetic then fun1 match case Select(qualifier, _) => - import dotty.tools.dotc.interfaces.Diagnostic.ERROR failedState.reporter.mapBufferedMessages: case Diagnostic(msg: TypeMismatch, pos, ERROR) if msg.inTree.exists(t => tree.args.exists(_.span == t.span)) => @@ -1204,7 +1218,7 @@ trait Applications extends Compatibility { val (lhs1, name, rhss) = (tree: @unchecked) match case Apply(Select(lhs, name), rhss) => (typedExpr(lhs), name, rhss) case Apply(untpd.TypedSplice(Select(lhs1, name)), rhss) => (lhs1, name, rhss) - val liftedDefs = new mutable.ListBuffer[Tree] + val liftedDefs = ListBuffer.empty[Tree] val lhs2 = untpd.TypedSplice(LiftComplex.liftAssigned(liftedDefs, lhs1)) val assign = untpd.Assign(lhs2, untpd.Apply(untpd.Select(lhs2, name.asSimpleName.dropRight(1)), rhss)) diff --git a/tests/neg/t4727.check b/tests/neg/t4727.check new file mode 100644 index 000000000000..4520e8add446 --- /dev/null +++ b/tests/neg/t4727.check @@ -0,0 +1,25 @@ +-- [E007] Type Mismatch Error: tests/neg/t4727.scala:7:8 --------------------------------------------------------------- +7 | new C[Int] // error + | ^^^^^^ + | Found: Null + | Required: Int + | Error occurred in an application involving default arguments. + | Note that implicit conversions were not tried because the result of an implicit conversion + | must be more specific than Int + |--------------------------------------------------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | + | Tree: C.$lessinit$greater$default$1[Int] + | I tried to show that + | Null + | conforms to + | Int + | but none of the attempts shown below succeeded: + | + | ==> Null <: Int = false + | + | The tests were made under the empty constraint + | + | Expanded application: new C[Int](C.$lessinit$greater$default$1[Int]) + --------------------------------------------------------------------------------------------------------------------- diff --git a/tests/untried/neg/t4727.scala b/tests/neg/t4727.scala similarity index 62% rename from tests/untried/neg/t4727.scala rename to tests/neg/t4727.scala index 40c06713caa6..e8e456c52ab1 100644 --- a/tests/untried/neg/t4727.scala +++ b/tests/neg/t4727.scala @@ -1,7 +1,9 @@ +//> using options -explain + class C[T](x : T = null) object Test { def main(args: Array[String]): Unit = { - new C[Int] + new C[Int] // error } } diff --git a/tests/untried/neg/t4727.check b/tests/untried/neg/t4727.check deleted file mode 100644 index a17cdde04417..000000000000 --- a/tests/untried/neg/t4727.check +++ /dev/null @@ -1,5 +0,0 @@ -t4727.scala:5: error: an expression of type Null is ineligible for implicit conversion -Error occurred in an application involving default arguments. - new C[Int] - ^ -one error found From 29c03b954fac429cbce0a297e5759ad1a2863e7e Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Tue, 9 Jul 2024 08:04:19 -0700 Subject: [PATCH 3/3] No report for error types --- compiler/src/dotty/tools/dotc/typer/Checking.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 1f82b9ddc084..1cd6cc5a5de4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -917,7 +917,7 @@ trait Checking { enum Reason: case NonConforming, RefutableExtractor - def fail(pat: Tree, pt: Type, reason: Reason): Boolean = { + def fail(pat: Tree, pt: Type, reason: Reason): Boolean = !pat.tpe.isErroneous && !pt.isErroneous && { import Reason.* val message = reason match case NonConforming =>