Skip to content

Commit f58e245

Browse files
committed
Fix #8569: Better error message for erroneous creator applications
Don't just fall-back to say "not a member".
1 parent c7d4187 commit f58e245

File tree

5 files changed

+48
-24
lines changed

5 files changed

+48
-24
lines changed

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

Lines changed: 24 additions & 19 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.toString
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
@@ -1066,25 +1082,14 @@ trait Applications extends Compatibility {
10661082
if (!tree.tpe.isError && tree.tpe.isErroneous) tree
10671083
else errorTree(tree, NotAnExtractor(qual))
10681084

1069-
/** Does `state` contain a single "NotAMember" message as pending error message
1070-
* that says `$membername is not a member of ...` ?
1071-
*/
1072-
def saysNotAMember(state: TyperState, memberName: TermName): Boolean =
1073-
state.reporter.pendingMessages match
1074-
case msg :: Nil =>
1075-
msg.contained match
1076-
case NotAMember(_, name, _, _) => name == memberName
1077-
case _ => false
1078-
case _ => false
1079-
10801085
/** Report errors buffered in state.
10811086
* @pre state has errors to report
10821087
* If there is a single error stating that "unapply" is not a member, print
10831088
* the more informative "notAnExtractor" message instead.
10841089
*/
10851090
def reportErrors(tree: Tree, state: TyperState): Tree =
10861091
assert(state.reporter.hasErrors)
1087-
if saysNotAMember(state, nme.unapply) then notAnExtractor(tree)
1092+
if saysNotFound(state, nme.unapply) then notAnExtractor(tree)
10881093
else
10891094
state.reporter.flush()
10901095
tree
@@ -1154,7 +1159,7 @@ trait Applications extends Compatibility {
11541159
(sel2, state2) =>
11551160
// if both fail, return unapply error, unless that is simply a
11561161
// "not a member", and the unapplySeq error is more refined.
1157-
if saysNotAMember(state, nme.unapply) && !saysNotAMember(state2, nme.unapplySeq)
1162+
if saysNotFound(state, nme.unapply) && !saysNotFound(state2, nme.unapplySeq)
11581163
then fallBack(sel2, state2)
11591164
else fallBack(sel, state)
11601165
}

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: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -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

tests/neg/i8569.check

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
-- Error: tests/neg/i8569.scala:8:2 ------------------------------------------------------------------------------------
2+
8 | outer.Inner(2) // error
3+
| ^^^^^
4+
| (Test.outer : => Outer) is not a valid path
5+
-- Error: tests/neg/i8569.scala:9:6 ------------------------------------------------------------------------------------
6+
9 | new outer.Inner(2) // error
7+
| ^^^^^
8+
| (Test.outer : => Outer) is not a valid path

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)