Skip to content

Commit 6089e1d

Browse files
authored
Merge pull request #8584 from dotty-staging/fix-#8569
Fix #8569: Better error message for erroneous creator applications
2 parents 8d1a912 + 2427008 commit 6089e1d

File tree

13 files changed

+115
-33
lines changed

13 files changed

+115
-33
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,4 +327,7 @@ abstract class Reporter extends interfaces.ReporterResult {
327327
/** Issue all error messages in this reporter to next outer one, or make sure they are written. */
328328
def flush()(implicit ctx: Context): Unit =
329329
removeBufferedMessages.foreach(ctx.reporter.report)
330+
331+
/** If this reporter buffers messages, all buffered messages, otherwise Nil */
332+
def pendingMessages(using Context): List[MessageContainer] = Nil
330333
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,5 +38,7 @@ class StoreReporter(outer: Reporter) extends Reporter {
3838
if (infos != null) try infos.toList finally infos = null
3939
else Nil
4040

41+
override def pendingMessages(using Context): List[MessageContainer] = infos.toList
42+
4143
override def errorsReported: Boolean = hasErrors || (outer != null && outer.errorsReported)
4244
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ enum ErrorMessageID extends java.lang.Enum[ErrorMessageID] {
9191
ExpectedTopLevelDefID,
9292
AnonymousFunctionMissingParamTypeID,
9393
SuperCallsNotAllowedInlineableID,
94-
UNUSED1, // not used anymore, but left so that error numbers stay the same
94+
NotAPathID,
9595
WildcardOnTypeArgumentNotAllowedOnNewID,
9696
FunctionTypeNeedsNonEmptyParameterListID,
9797
WrongNumberOfParametersID,

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ object messages {
279279
}
280280
}
281281

282-
case class MissingIdent(tree: untpd.Ident, treeKind: String, name: String)(implicit ctx: Context)
282+
case class MissingIdent(tree: untpd.Ident, treeKind: String, name: Name)(implicit ctx: Context)
283283
extends Message(MissingIdentID) {
284284
val kind: String = "Unbound Identifier"
285285
val msg: String = em"Not found: $treeKind$name"
@@ -1701,10 +1701,19 @@ object messages {
17011701
case class SuperCallsNotAllowedInlineable(symbol: Symbol)(implicit ctx: Context)
17021702
extends Message(SuperCallsNotAllowedInlineableID) {
17031703
val kind: String = "Syntax"
1704-
val msg: String = s"Super call not allowed in inlineable $symbol"
1704+
val msg: String = em"Super call not allowed in inlineable $symbol"
17051705
val explanation: String = "Method inlining prohibits calling superclass methods, as it may lead to confusion about which super is being called."
17061706
}
17071707

1708+
case class NotAPath(tp: Type, usage: String)(using Context) extends Message(NotAPathID):
1709+
val kind: String = "Type"
1710+
val msg: String = em"$tp is not a valid $usage, since it is not an immutable path"
1711+
val explanation: String =
1712+
i"""An immutable path is
1713+
| - a reference to an immutable value, or
1714+
| - a reference to `this`, or
1715+
| - a selection of an immutable path with an immutable value."""
1716+
17081717
case class WrongNumberOfParameters(expected: Int)(implicit ctx: Context)
17091718
extends Message(WrongNumberOfParametersID) {
17101719
val kind: String = "Syntax"

compiler/src/dotty/tools/dotc/transform/PCPCheckAndHeal.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages(
239239

240240
tag.tpe match
241241
case tp: TermRef =>
242-
checkStable(tp, pos)
242+
checkStable(tp, pos, "type witness")
243243
Some(ref(getQuoteTypeTags.getTagRef(tp)))
244244
case _: SearchFailureType =>
245245
levelError(sym, tp, pos,

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

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import config.Printers.{overload, typr, unapp}
2929
import TypeApplications._
3030

3131
import reporting.diagnostic.Message
32-
import reporting.diagnostic.messages.{UnexpectedPatternForSummonFrom, NotAMember}
32+
import reporting.diagnostic.messages.{UnexpectedPatternForSummonFrom, NotAMember, MissingIdent}
3333
import reporting.trace
3434
import Constants.{Constant, IntTag, LongTag}
3535
import dotty.tools.dotc.reporting.diagnostic.messages.{UnapplyInvalidReturnType, NotAnExtractor, UnapplyInvalidNumberOfArguments}
@@ -819,12 +819,13 @@ trait Applications extends Compatibility {
819819
tryEither {
820820
typedExpr(fn, pt)
821821
} { (result, tstate) =>
822-
def fallBack = {
823-
tstate.commit()
822+
def fallBack(nuState: TyperState) =
823+
if (nuState ne ctx.typerState) && !saysNotFound(nuState, EmptyTypeName)
824+
then nuState.commit() // nuState messages are more interesting that tstate's "not found"
825+
else tstate.commit() // it's "not found" both ways; keep original message
824826
result
825-
}
826-
if (untpd.isPath(fn)) tryNew(untpd)(fn, pt, fallBack)
827-
else fallBack
827+
if untpd.isPath(fn) then tryNew(untpd)(fn, pt, fallBack)
828+
else fallBack(ctx.typerState)
828829
}
829830

830831
/** Typecheck application. Result could be an `Apply` node,
@@ -1056,6 +1057,21 @@ trait Applications extends Compatibility {
10561057
tree
10571058
}
10581059

1060+
/** Does `state` contain a single "NotAMember" or "MissingIdent" message as
1061+
* pending error message that says `$memberName is not a member of ...` or
1062+
* `Not found: $memberName`? If memberName is empty, any name will do.
1063+
*/
1064+
def saysNotFound(state: TyperState, memberName: Name)(using Context): Boolean =
1065+
state.reporter.pendingMessages match
1066+
case msg :: Nil =>
1067+
msg.contained match
1068+
case NotAMember(_, name, _, _) =>
1069+
memberName.isEmpty || name == memberName
1070+
case MissingIdent(_, _, name) =>
1071+
memberName.isEmpty || name == memberName
1072+
case _ => false
1073+
case _ => false
1074+
10591075
def typedUnApply(tree: untpd.Apply, selType: Type)(implicit ctx: Context): Tree = {
10601076
record("typedUnApply")
10611077
val Apply(qual, args) = tree
@@ -1073,15 +1089,10 @@ trait Applications extends Compatibility {
10731089
*/
10741090
def reportErrors(tree: Tree, state: TyperState): Tree =
10751091
assert(state.reporter.hasErrors)
1076-
val msgs = state.reporter.removeBufferedMessages
1077-
msgs match
1078-
case msg :: Nil =>
1079-
msg.contained match
1080-
case NotAMember(_, nme.unapply, _, _) => return notAnExtractor(tree)
1081-
case _ =>
1082-
case _ =>
1083-
msgs.foreach(ctx.reporter.report)
1084-
tree
1092+
if saysNotFound(state, nme.unapply) then notAnExtractor(tree)
1093+
else
1094+
state.reporter.flush()
1095+
tree
10851096

10861097
/** If this is a term ref tree, try to typecheck with its type name.
10871098
* If this refers to a type alias, follow the alias, and if
@@ -1145,7 +1156,12 @@ trait Applications extends Compatibility {
11451156
tryWithName(nme.unapply) {
11461157
(sel, state) =>
11471158
tryWithName(nme.unapplySeq) {
1148-
(_, _) => fallBack(sel, state)
1159+
(sel2, state2) =>
1160+
// if both fail, return unapply error, unless that is simply a
1161+
// "not a member", and the unapplySeq error is more refined.
1162+
if saysNotFound(state, nme.unapply) && !saysNotFound(state2, nme.unapplySeq)
1163+
then fallBack(sel2, state2)
1164+
else fallBack(sel, state)
11491165
}
11501166
}
11511167
}

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -652,8 +652,8 @@ trait Checking {
652652
Checking.checkNonCyclicInherited(joint, parents, decls, posd)
653653

654654
/** Check that type `tp` is stable. */
655-
def checkStable(tp: Type, pos: SourcePosition)(implicit ctx: Context): Unit =
656-
if (!tp.isStable) ctx.error(ex"$tp is not stable", pos)
655+
def checkStable(tp: Type, pos: SourcePosition, kind: String)(implicit ctx: Context): Unit =
656+
if !tp.isStable then ctx.error(NotAPath(tp, kind), pos)
657657

658658
/** Check that all type members of `tp` have realizable bounds */
659659
def checkRealizableBounds(cls: Symbol, pos: SourcePosition)(implicit ctx: Context): Unit = {
@@ -712,7 +712,7 @@ trait Checking {
712712

713713
/** Check that `path` is a legal prefix for an import or export clause */
714714
def checkLegalImportPath(path: Tree)(implicit ctx: Context): Unit = {
715-
checkStable(path.tpe, path.sourcePos)
715+
checkStable(path.tpe, path.sourcePos, "import prefix")
716716
if (!ctx.isAfterTyper) Checking.checkRealizable(path.tpe, path.posd)
717717
}
718718

@@ -726,7 +726,7 @@ trait Checking {
726726
tp.underlyingClassRef(refinementOK = false) match {
727727
case tref: TypeRef =>
728728
if (traitReq && !tref.symbol.is(Trait)) ctx.error(TraitIsExpected(tref.symbol), pos)
729-
if (stablePrefixReq && ctx.phase <= ctx.refchecksPhase) checkStable(tref.prefix, pos)
729+
if (stablePrefixReq && ctx.phase <= ctx.refchecksPhase) checkStable(tref.prefix, pos, "class prefix")
730730
tp
731731
case _ =>
732732
ctx.error(ex"$tp is not a class type", pos)
@@ -1194,7 +1194,7 @@ trait NoChecking extends ReChecking {
11941194
import tpd._
11951195
override def checkNonCyclic(sym: Symbol, info: TypeBounds, reportErrors: Boolean)(implicit ctx: Context): Type = info
11961196
override def checkNonCyclicInherited(joint: Type, parents: List[Type], decls: Scope, posd: Positioned)(implicit ctx: Context): Unit = ()
1197-
override def checkStable(tp: Type, pos: SourcePosition)(implicit ctx: Context): Unit = ()
1197+
override def checkStable(tp: Type, pos: SourcePosition, kind: String)(implicit ctx: Context): Unit = ()
11981198
override def checkClassType(tp: Type, pos: SourcePosition, traitReq: Boolean, stablePrefixReq: Boolean)(implicit ctx: Context): Type = tp
11991199
override def checkImplicitConversionDefOK(sym: Symbol)(implicit ctx: Context): Unit = ()
12001200
override def checkImplicitConversionUseOK(sym: Symbol, posd: Positioned)(implicit ctx: Context): Unit = ()

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,8 @@ class ReTyper extends Typer with ReChecking {
104104
fallBack
105105

106106
override def tryNew[T >: Untyped <: Type]
107-
(treesInst: Instance[T])(tree: Trees.Tree[T], pt: Type, fallBack: => Tree)(implicit ctx: Context): Tree = fallBack
107+
(treesInst: Instance[T])(tree: Trees.Tree[T], pt: Type, fallBack: TyperState => Tree)(implicit ctx: Context): Tree =
108+
fallBack(ctx.typerState)
108109

109110
override def completeAnnotations(mdef: untpd.MemberDef, sym: Symbol)(implicit ctx: Context): Unit = ()
110111

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

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -451,7 +451,7 @@ class Typer extends Namer
451451
// of a this(...) constructor call
452452
errorType(ex"$tree is not accessible from constructor arguments", tree.sourcePos)
453453
else
454-
errorType(new MissingIdent(tree, kind, name.show), tree.sourcePos)
454+
errorType(new MissingIdent(tree, kind, name), tree.sourcePos)
455455

456456
val tree1 = ownType match {
457457
case ownType: NamedType =>
@@ -491,7 +491,7 @@ class Typer extends Namer
491491
case _ => app
492492
}
493493
case qual =>
494-
if (tree.name.isTypeName) checkStable(qual.tpe, qual.sourcePos)
494+
if (tree.name.isTypeName) checkStable(qual.tpe, qual.sourcePos, "type prefix")
495495
val select = assignType(cpy.Select(tree)(qual, tree.name), qual)
496496

497497
val select1 = toNotNullTermRef(select, pt)
@@ -1429,7 +1429,7 @@ class Typer extends Namer
14291429

14301430
def typedSingletonTypeTree(tree: untpd.SingletonTypeTree)(implicit ctx: Context): SingletonTypeTree = {
14311431
val ref1 = typedExpr(tree.ref)
1432-
checkStable(ref1.tpe, tree.sourcePos)
1432+
checkStable(ref1.tpe, tree.sourcePos, "singleton type")
14331433
assignType(cpy.SingletonTypeTree(tree)(ref1), ref1)
14341434
}
14351435

@@ -2466,7 +2466,7 @@ class Typer extends Namer
24662466
* is more efficient since it re-uses the prefix `p` in typed form.
24672467
*/
24682468
def tryNew[T >: Untyped <: Type]
2469-
(treesInst: Instance[T])(tree: Trees.Tree[T], pt: Type, fallBack: => Tree)(implicit ctx: Context): Tree = {
2469+
(treesInst: Instance[T])(tree: Trees.Tree[T], pt: Type, fallBack: TyperState => Tree)(implicit ctx: Context): Tree = {
24702470

24712471
def tryWithType(tpt: untpd.Tree): Tree =
24722472
tryEither {
@@ -2486,7 +2486,7 @@ class Typer extends Namer
24862486
.reporting(i"try new $tree -> $result", typr)
24872487
}
24882488
} { (nu, nuState) =>
2489-
if (nu.isEmpty) fallBack
2489+
if (nu.isEmpty) fallBack(nuState)
24902490
else {
24912491
// we found a type constructor, signal the error in its application instead of the original one
24922492
nuState.commit()
@@ -2504,7 +2504,7 @@ class Typer extends Namer
25042504
}
25052505
tryWithType(cpy.Select(tree)(qual1, name.toTypeName))
25062506
case _ =>
2507-
fallBack
2507+
fallBack(ctx.typerState)
25082508
}
25092509
}
25102510

@@ -2550,7 +2550,7 @@ class Typer extends Namer
25502550

25512551
def tryImplicit(fallBack: => Tree) =
25522552
tryInsertImplicitOnQualifier(tree, pt.withContext(ctx), locked)
2553-
.getOrElse(tryNew(tpd)(tree, pt, fallBack))
2553+
.getOrElse(tryNew(tpd)(tree, pt, _ => fallBack))
25542554

25552555
if (ctx.mode.is(Mode.SynthesizeExtMethodReceiver))
25562556
// Suppress insertion of apply or implicit conversion on extension method receiver
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
-- [E134] Type Mismatch Error: tests/neg-custom-args/explicit-nulls/i7883.scala:6:11 -----------------------------------
2+
6 | case r(hd, tl) => Some((hd, tl)) // error // error // error
3+
| ^
4+
| None of the overloaded alternatives of method unapplySeq in class Regex with types
5+
| (m: scala.util.matching.Regex.Match): Option[List[String]]
6+
| (c: Char): Option[List[Char]]
7+
| (s: CharSequence): Option[List[String]]
8+
| match arguments (String | UncheckedNull)
9+
-- [E006] Unbound Identifier Error: tests/neg-custom-args/explicit-nulls/i7883.scala:6:30 ------------------------------
10+
6 | case r(hd, tl) => Some((hd, tl)) // error // error // error
11+
| ^^
12+
| Not found: hd
13+
14+
longer explanation available when compiling with `-explain`
15+
-- [E006] Unbound Identifier Error: tests/neg-custom-args/explicit-nulls/i7883.scala:6:34 ------------------------------
16+
6 | case r(hd, tl) => Some((hd, tl)) // error // error // error
17+
| ^^
18+
| Not found: tl
19+
20+
longer explanation available when compiling with `-explain`
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import scala.util.matching.Regex
2+
3+
object Test extends App {
4+
def head(s: String, r: Regex): Option[(String, String)] =
5+
s.trim match {
6+
case r(hd, tl) => Some((hd, tl)) // error // error // error
7+
case _ => None
8+
}
9+
}

tests/neg/i8569.check

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
-- [E083] Type Error: tests/neg/i8569.scala:8:2 ------------------------------------------------------------------------
2+
8 | outer.Inner(2) // error
3+
| ^^^^^
4+
| (Test.outer : => Outer) is not a valid type prefix, since it is not an immutable path
5+
6+
longer explanation available when compiling with `-explain`
7+
-- [E083] Type Error: tests/neg/i8569.scala:9:6 ------------------------------------------------------------------------
8+
9 | new outer.Inner(2) // error
9+
| ^^^^^
10+
| (Test.outer : => Outer) is not a valid type prefix, since it is not an immutable path
11+
12+
longer explanation available when compiling with `-explain`

tests/neg/i8569.scala

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
class Outer(x: Int) {
2+
class Inner(y: Int) {
3+
}
4+
}
5+
object Test {
6+
def outer = Outer(1)
7+
8+
outer.Inner(2) // error
9+
new outer.Inner(2) // error
10+
}

0 commit comments

Comments
 (0)