Skip to content

Commit 03d5f4e

Browse files
authored
Merge pull request scala#5804 from jvican/stub-errors-2.11.8
Backport 2.11.9: Improve stub error messages (SCP-009 proposal)
2 parents 99f41a1 + 9cfa239 commit 03d5f4e

31 files changed

+465
-36
lines changed

src/compiler/scala/tools/nsc/Global.scala

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,20 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
9696

9797
def erasurePhase: Phase = if (currentRun.isDefined) currentRun.erasurePhase else NoPhase
9898

99+
/* Override `newStubSymbol` defined in `SymbolTable` to provide us access
100+
* to the last tree to typer, whose position is the trigger of stub errors. */
101+
override def newStubSymbol(owner: Symbol,
102+
name: Name,
103+
missingMessage: String,
104+
isPackage: Boolean = false): Symbol = {
105+
val stubSymbol = super.newStubSymbol(owner, name, missingMessage, isPackage)
106+
val stubErrorPosition = {
107+
val lastTreeToTyper = analyzer.lastTreeToTyper
108+
if (lastTreeToTyper != EmptyTree) lastTreeToTyper.pos else stubSymbol.pos
109+
}
110+
stubSymbol.setPos(stubErrorPosition)
111+
}
112+
99113
// platform specific elements
100114

101115
protected class GlobalPlatform extends {

src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ abstract class ClassfileParser {
366366
// - better owner than `NoSymbol`
367367
// - remove eager warning
368368
val msg = s"Class $name not found - continuing with a stub."
369-
if (!settings.isScaladoc) warning(msg)
369+
if ((!settings.isScaladoc) && (settings.verbose || settings.developer)) warning(msg)
370370
return NoSymbol.newStubSymbol(name.toTypeName, msg)
371371
}
372372
val completer = new loaders.ClassfileLoader(file)
@@ -1030,8 +1030,11 @@ abstract class ClassfileParser {
10301030
val sflags = jflags.toScalaFlags
10311031
val owner = ownerForFlags(jflags)
10321032
val scope = getScope(jflags)
1033-
def newStub(name: Name) =
1034-
owner.newStubSymbol(name, s"Class file for ${entry.externalName} not found").setFlag(JAVA)
1033+
def newStub(name: Name) = {
1034+
val stub = owner.newStubSymbol(name, s"Class file for ${entry.externalName} not found")
1035+
stub.setPos(owner.pos)
1036+
stub.setFlag(JAVA)
1037+
}
10351038

10361039
val (innerClass, innerModule) = if (file == NoAbstractFile) {
10371040
(newStub(name.toTypeName), newStub(name.toTermName))
@@ -1152,7 +1155,11 @@ abstract class ClassfileParser {
11521155
if (enclosing == clazz) entry.scope lookup name
11531156
else lookupMemberAtTyperPhaseIfPossible(enclosing, name)
11541157
)
1155-
def newStub = enclosing.newStubSymbol(name, s"Unable to locate class corresponding to inner class entry for $name in owner ${entry.outerName}")
1158+
def newStub = {
1159+
enclosing
1160+
.newStubSymbol(name, s"Unable to locate class corresponding to inner class entry for $name in owner ${entry.outerName}")
1161+
.setPos(enclosing.pos)
1162+
}
11561163
member.orElse(newStub)
11571164
}
11581165
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package scala.tools.partest
2+
3+
trait StubErrorMessageTest extends StoreReporterDirectTest {
4+
// Stub to feed to partest, unused
5+
def code = throw new Error("Use `userCode` instead of `code`.")
6+
7+
val classpath = List(sys.props("partest.lib"), testOutput.path)
8+
.mkString(sys.props("path.separator"))
9+
10+
def compileCode(codes: String*) = {
11+
val global = newCompiler("-cp", classpath, "-d", testOutput.path)
12+
val sourceFiles = newSources(codes: _*)
13+
withRun(global)(_ compileSources sourceFiles)
14+
}
15+
16+
def removeClasses(inPackage: String, classNames: Seq[String]): Unit = {
17+
val pkg = new File(testOutput.path, inPackage)
18+
classNames.foreach { className =>
19+
val classFile = new File(pkg, s"$className.class")
20+
assert(classFile.exists)
21+
assert(classFile.delete())
22+
}
23+
}
24+
25+
def removeFromClasspath(): Unit
26+
def codeA: String
27+
def codeB: String
28+
def userCode: String
29+
def extraUserCode: String = ""
30+
31+
def show(): Unit = {
32+
compileCode(codeA)
33+
assert(filteredInfos.isEmpty, filteredInfos)
34+
35+
compileCode(codeB)
36+
assert(filteredInfos.isEmpty, filteredInfos)
37+
removeFromClasspath()
38+
39+
if (extraUserCode == "") compileCode(userCode)
40+
else compileCode(userCode, extraUserCode)
41+
import scala.reflect.internal.util.Position
42+
filteredInfos.map { report =>
43+
print(if (report.severity == storeReporter.ERROR) "error: " else "")
44+
println(Position.formatMessage(report.pos, report.msg, true))
45+
}
46+
}
47+
}

src/reflect/scala/reflect/internal/Symbols.scala

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,17 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
182182

183183
private[reflect] case class SymbolKind(accurate: String, sanitized: String, abbreviation: String)
184184

185+
protected def newStubSymbol(owner: Symbol,
186+
name: Name,
187+
missingMessage: String,
188+
isPackage: Boolean = false): Symbol = {
189+
name match {
190+
case n: TypeName => if (isPackage) new StubPackageClassSymbol(owner, n, missingMessage)
191+
else new StubClassSymbol(owner, n, missingMessage)
192+
case _ => new StubTermSymbol(owner, name.toTermName, missingMessage)
193+
}
194+
}
195+
185196
/** The class for all symbols */
186197
abstract class Symbol protected[Symbols] (initOwner: Symbol, initPos: Position, initName: Name)
187198
extends SymbolContextApiImpl
@@ -505,9 +516,9 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
505516
* failure to the point when that name is used for something, which is
506517
* often to the point of never.
507518
*/
508-
def newStubSymbol(name: Name, missingMessage: String, isPackage: Boolean = false): Symbol = name match {
509-
case n: TypeName => if (isPackage) new StubPackageClassSymbol(this, n, missingMessage) else new StubClassSymbol(this, n, missingMessage)
510-
case _ => new StubTermSymbol(this, name.toTermName, missingMessage)
519+
def newStubSymbol(name: Name, missingMessage: String, isPackage: Boolean = false): Symbol = {
520+
// Invoke the overriden `newStubSymbol` in Global that gives us access to typer
521+
Symbols.this.newStubSymbol(this, name, missingMessage, isPackage)
511522
}
512523

513524
/** Given a field, construct a term symbol that represents the source construct that gave rise the field */
@@ -3491,7 +3502,7 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
34913502
private def fail[T](alt: T): T = {
34923503
// Avoid issuing lots of redundant errors
34933504
if (!hasFlag(IS_ERROR)) {
3494-
globalError(missingMessage)
3505+
globalError(pos, missingMessage)
34953506
if (settings.debug.value)
34963507
(new Throwable).printStackTrace
34973508

src/reflect/scala/reflect/internal/pickling/UnPickler.scala

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -266,13 +266,15 @@ abstract class UnPickler {
266266
adjust(mirrorThatLoaded(owner).missingHook(owner, name)) orElse {
267267
// (5) Create a stub symbol to defer hard failure a little longer.
268268
val advice = moduleAdvice(s"${owner.fullName}.$name")
269+
val lazyCompletingSymbol = completingStack.headOption.getOrElse(NoSymbol)
269270
val missingMessage =
270-
s"""|missing or invalid dependency detected while loading class file '$filename'.
271-
|Could not access ${name.longString} in ${owner.kindString} ${owner.fullName},
272-
|because it (or its dependencies) are missing. Check your build definition for
273-
|missing or conflicting dependencies. (Re-run with `-Ylog-classpath` to see the problematic classpath.)
271+
s"""|Symbol '${name.nameKind} ${owner.fullName}.$name' is missing from the classpath.
272+
|This symbol is required by '${lazyCompletingSymbol.kindString} ${lazyCompletingSymbol.fullName}'.
273+
|Make sure that ${name.longString} is in your classpath and check for conflicting dependencies with `-Ylog-classpath`.
274274
|A full rebuild may help if '$filename' was compiled against an incompatible version of ${owner.fullName}.$advice""".stripMargin
275-
owner.newStubSymbol(name, missingMessage)
275+
val stubName = if (tag == EXTref) name else name.toTypeName
276+
// The position of the error message is set by `newStubSymbol`
277+
NoSymbol.newStubSymbol(stubName, missingMessage)
276278
}
277279
}
278280
}
@@ -717,11 +719,18 @@ abstract class UnPickler {
717719
new TypeError(e.msg)
718720
}
719721

722+
/** Keep track of the symbols pending to be initialized.
723+
*
724+
* Useful for reporting on stub errors and cyclic errors.
725+
*/
726+
private var completingStack = List.empty[Symbol]
727+
720728
/** A lazy type which when completed returns type at index `i`. */
721729
private class LazyTypeRef(i: Int) extends LazyType with FlagAgnosticCompleter {
722730
private val definedAtRunId = currentRunId
723731
private val p = phase
724732
protected def completeInternal(sym: Symbol) : Unit = try {
733+
completingStack = sym :: completingStack
725734
val tp = at(i, () => readType(sym.isTerm)) // after NMT_TRANSITION, revert `() => readType(sym.isTerm)` to `readType`
726735

727736
// This is a temporary fix allowing to read classes generated by an older, buggy pickler.
@@ -744,7 +753,10 @@ abstract class UnPickler {
744753
}
745754
catch {
746755
case e: MissingRequirementError => throw toTypeError(e)
756+
} finally {
757+
completingStack = completingStack.tail
747758
}
759+
748760
override def complete(sym: Symbol) : Unit = {
749761
completeInternal(sym)
750762
if (!isCompilerUniverse) markAllCompleted(sym)

test/files/neg/t5148.check

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
1-
error: missing or invalid dependency detected while loading class file 'Imports.class'.
2-
Could not access type Wrapper in class scala.tools.nsc.interpreter.IMain.Request,
3-
because it (or its dependencies) are missing. Check your build definition for
4-
missing or conflicting dependencies. (Re-run with `-Ylog-classpath` to see the problematic classpath.)
5-
A full rebuild may help if 'Imports.class' was compiled against an incompatible version of scala.tools.nsc.interpreter.IMain.Request.
6-
error: missing or invalid dependency detected while loading class file 'Imports.class'.
7-
Could not access type Request in class scala.tools.nsc.interpreter.IMain,
8-
because it (or its dependencies) are missing. Check your build definition for
9-
missing or conflicting dependencies. (Re-run with `-Ylog-classpath` to see the problematic classpath.)
10-
A full rebuild may help if 'Imports.class' was compiled against an incompatible version of scala.tools.nsc.interpreter.IMain.
11-
two errors found
1+
t5148.scala:4: error: Symbol 'type <none>.Request.Wrapper' is missing from the classpath.
2+
This symbol is required by 'value scala.tools.nsc.interpreter.Imports.wrapper'.
3+
Make sure that type Wrapper is in your classpath and check for conflicting dependencies with `-Ylog-classpath`.
4+
A full rebuild may help if 'Imports.class' was compiled against an incompatible version of <none>.Request.
5+
class IMain extends Imports
6+
^
7+
one error found
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
error: newSource1.scala:4: Symbol 'type stuberrors.A' is missing from the classpath.
2+
This symbol is required by 'class stuberrors.B'.
3+
Make sure that type A is in your classpath and check for conflicting dependencies with `-Ylog-classpath`.
4+
A full rebuild may help if 'B.class' was compiled against an incompatible version of stuberrors.
5+
new B
6+
^
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
object Test extends scala.tools.partest.StubErrorMessageTest {
2+
def codeA = """
3+
package stuberrors
4+
class A
5+
"""
6+
7+
def codeB = """
8+
package stuberrors
9+
class B extends A
10+
"""
11+
12+
def userCode = """
13+
package stuberrors
14+
class C {
15+
new B
16+
}
17+
"""
18+
19+
def removeFromClasspath(): Unit = {
20+
removeClasses("stuberrors", List("A"))
21+
}
22+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
error: newSource1.scala:9: Symbol 'type stuberrors.A' is missing from the classpath.
2+
This symbol is required by 'class stuberrors.B.BB'.
3+
Make sure that type A is in your classpath and check for conflicting dependencies with `-Ylog-classpath`.
4+
A full rebuild may help if 'B.class' was compiled against an incompatible version of stuberrors.
5+
new b.BB
6+
^
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
object Test extends scala.tools.partest.StubErrorMessageTest {
2+
def codeA = """
3+
package stuberrors
4+
class A
5+
"""
6+
7+
def codeB = """
8+
package stuberrors
9+
class B {
10+
def foo: String = ???
11+
12+
// unused and should fail, but not loaded
13+
def unsafeFoo: A = ???
14+
// used, B.info -> BB.info -> unpickling A -> stub error
15+
class BB extends A
16+
}
17+
"""
18+
19+
def userCode = """
20+
package stuberrors
21+
class C {
22+
def aloha = {
23+
val b = new B
24+
val d = new extra.D
25+
d.foo
26+
println(b.foo)
27+
new b.BB
28+
}
29+
}
30+
"""
31+
32+
override def extraUserCode = """
33+
package extra
34+
class D {
35+
def foo = "Hello, World"
36+
}
37+
""".stripMargin
38+
39+
def removeFromClasspath(): Unit = {
40+
removeClasses("stuberrors", List("A"))
41+
}
42+
}

0 commit comments

Comments
 (0)