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/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 74c20812893b..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,23 +1120,15 @@ 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 () + 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) @@ -1211,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/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 => 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