diff --git a/compiler/src/dotty/tools/dotc/reporting/Reporter.scala b/compiler/src/dotty/tools/dotc/reporting/Reporter.scala index 9eadb826aed1..2a4ca680496e 100644 --- a/compiler/src/dotty/tools/dotc/reporting/Reporter.scala +++ b/compiler/src/dotty/tools/dotc/reporting/Reporter.scala @@ -327,4 +327,7 @@ abstract class Reporter extends interfaces.ReporterResult { /** Issue all error messages in this reporter to next outer one, or make sure they are written. */ def flush()(implicit ctx: Context): Unit = removeBufferedMessages.foreach(ctx.reporter.report) + + /** If this reporter buffers messages, all buffered messages, otherwise Nil */ + def pendingMessages(using Context): List[MessageContainer] = Nil } diff --git a/compiler/src/dotty/tools/dotc/reporting/StoreReporter.scala b/compiler/src/dotty/tools/dotc/reporting/StoreReporter.scala index 1148c2d25687..fa5dd6e01138 100644 --- a/compiler/src/dotty/tools/dotc/reporting/StoreReporter.scala +++ b/compiler/src/dotty/tools/dotc/reporting/StoreReporter.scala @@ -38,5 +38,7 @@ class StoreReporter(outer: Reporter) extends Reporter { if (infos != null) try infos.toList finally infos = null else Nil + override def pendingMessages(using Context): List[MessageContainer] = infos.toList + override def errorsReported: Boolean = hasErrors || (outer != null && outer.errorsReported) } diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 6a0862eb172f..4c9cc4d56883 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1066,6 +1066,17 @@ trait Applications extends Compatibility { if (!tree.tpe.isError && tree.tpe.isErroneous) tree else errorTree(tree, NotAnExtractor(qual)) + /** Does `state` contain a single "NotAMember" message as pending error message + * that says `$membername is not a member of ...` ? + */ + def saysNotAMember(state: TyperState, memberName: TermName): Boolean = + state.reporter.pendingMessages match + case msg :: Nil => + msg.contained match + case NotAMember(_, name, _, _) => name == memberName + case _ => false + case _ => false + /** Report errors buffered in state. * @pre state has errors to report * If there is a single error stating that "unapply" is not a member, print @@ -1073,15 +1084,10 @@ trait Applications extends Compatibility { */ def reportErrors(tree: Tree, state: TyperState): Tree = assert(state.reporter.hasErrors) - val msgs = state.reporter.removeBufferedMessages - msgs match - case msg :: Nil => - msg.contained match - case NotAMember(_, nme.unapply, _, _) => return notAnExtractor(tree) - case _ => - case _ => - msgs.foreach(ctx.reporter.report) - tree + if saysNotAMember(state, nme.unapply) then notAnExtractor(tree) + else + state.reporter.flush() + tree /** If this is a term ref tree, try to typecheck with its type name. * If this refers to a type alias, follow the alias, and if @@ -1145,7 +1151,12 @@ trait Applications extends Compatibility { tryWithName(nme.unapply) { (sel, state) => tryWithName(nme.unapplySeq) { - (_, _) => fallBack(sel, state) + (sel2, state2) => + // if both fail, return unapply error, unless that is simply a + // "not a member", and the unapplySeq error is more refined. + if saysNotAMember(state, nme.unapply) && !saysNotAMember(state2, nme.unapplySeq) + then fallBack(sel2, state2) + else fallBack(sel, state) } } } diff --git a/tests/neg-custom-args/explicit-nulls/i7883.check b/tests/neg-custom-args/explicit-nulls/i7883.check new file mode 100644 index 000000000000..dd2a9ce6ae02 --- /dev/null +++ b/tests/neg-custom-args/explicit-nulls/i7883.check @@ -0,0 +1,20 @@ +-- [E134] Type Mismatch Error: tests/neg-custom-args/explicit-nulls/i7883.scala:6:11 ----------------------------------- +6 | case r(hd, tl) => Some((hd, tl)) // error // error // error + | ^ + | None of the overloaded alternatives of method unapplySeq in class Regex with types + | (m: scala.util.matching.Regex.Match): Option[List[String]] + | (c: Char): Option[List[Char]] + | (s: CharSequence): Option[List[String]] + | match arguments (String | UncheckedNull) +-- [E006] Unbound Identifier Error: tests/neg-custom-args/explicit-nulls/i7883.scala:6:30 ------------------------------ +6 | case r(hd, tl) => Some((hd, tl)) // error // error // error + | ^^ + | Not found: hd + +longer explanation available when compiling with `-explain` +-- [E006] Unbound Identifier Error: tests/neg-custom-args/explicit-nulls/i7883.scala:6:34 ------------------------------ +6 | case r(hd, tl) => Some((hd, tl)) // error // error // error + | ^^ + | Not found: tl + +longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/explicit-nulls/i7883.scala b/tests/neg-custom-args/explicit-nulls/i7883.scala new file mode 100644 index 000000000000..9ee92553b60d --- /dev/null +++ b/tests/neg-custom-args/explicit-nulls/i7883.scala @@ -0,0 +1,9 @@ +import scala.util.matching.Regex + +object Test extends App { + def head(s: String, r: Regex): Option[(String, String)] = + s.trim match { + case r(hd, tl) => Some((hd, tl)) // error // error // error + case _ => None + } +} \ No newline at end of file