Skip to content

Commit f6a1eb9

Browse files
Run bounds checks on java compilation units
Checking every Select of a java-defined symbol would be too costly and still not perfect. The implemented compromise is to check bounds during the compilation of .java source files. To do this, a new JavaChecks file is added and called by FrontEnd. We can't simply discard java compilation units after PostTyper (instead of after Typer), because sbt.ExtractDependencies (and maybe others) runs before PostTyper and cannot process java sources.
1 parent aa6b172 commit f6a1eb9

File tree

6 files changed

+75
-3
lines changed

6 files changed

+75
-3
lines changed

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,13 @@ class FrontEnd extends Phase {
8484
case ex: CompilationUnit.SuspendException =>
8585
}
8686

87+
def javaCheck(using Context): Unit = monitor("checking java") {
88+
val unit = ctx.compilationUnit
89+
if unit.isJava then
90+
JavaChecks.check(unit.tpdTree)
91+
}
92+
93+
8794
private def firstTopLevelDef(trees: List[tpd.Tree])(using Context): Symbol = trees match {
8895
case PackageDef(_, defs) :: _ => firstTopLevelDef(defs)
8996
case Import(_, _) :: defs => firstTopLevelDef(defs)
@@ -113,6 +120,8 @@ class FrontEnd extends Phase {
113120

114121
unitContexts.foreach(typeCheck(using _))
115122
record("total trees after typer", ast.Trees.ntrees)
123+
unitContexts.foreach(javaCheck(using _)) // after typechecking to avoid cycles
124+
116125
val newUnits = unitContexts.map(_.compilationUnit).filterNot(discardAfterTyper)
117126
val suspendedUnits = ctx.run.suspendedUnits
118127
if newUnits.isEmpty && suspendedUnits.nonEmpty && !ctx.reporter.errorsReported then
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package dotty.tools.dotc
2+
package typer
3+
4+
import core.Contexts._
5+
import ast.tpd._
6+
7+
/** PostTyper doesn't run on java sources,
8+
* but some checks still need to be applied.
9+
*/
10+
object JavaChecks {
11+
/** Check the bounds of AppliedTypeTrees. */
12+
private object AppliedTypeChecker extends TreeTraverser {
13+
def traverse(tree: Tree)(using Context): Unit = tree match
14+
case tpt: TypeTree =>
15+
Checking.checkAppliedTypesIn(tpt)
16+
case tree: AppliedTypeTree =>
17+
Checking.checkAppliedType(tree)
18+
case _ =>
19+
traverseChildren(tree)
20+
}
21+
22+
/** Scan a tree and check it. */
23+
def check(tree: Tree)(using Context): Unit =
24+
report.debuglog("checking type bounds in " + ctx.compilationUnit.source.name)
25+
AppliedTypeChecker.traverse(tree)
26+
}

compiler/test/dotty/tools/vulpix/ParallelTesting.scala

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -229,8 +229,10 @@ trait ParallelTesting extends RunnerOrchestration { self =>
229229
*/
230230
final def checkFile(testSource: TestSource): Option[JFile] = (testSource match {
231231
case ts: JointCompilationSource =>
232-
ts.files.collectFirst { case f if !f.isDirectory => new JFile(f.getPath.replaceFirst("\\.scala$", ".check")) }
233-
232+
ts.files.collectFirst {
233+
case f if !f.isDirectory =>
234+
new JFile(f.getPath.replaceFirst("\\.(scala|java)$", ".check"))
235+
}
234236
case ts: SeparateCompilationSource =>
235237
Option(new JFile(ts.dir.getPath + ".check"))
236238
}).filter(_.exists)
@@ -679,7 +681,7 @@ trait ParallelTesting extends RunnerOrchestration { self =>
679681
def getErrorMapAndExpectedCount(files: Seq[JFile]): (HashMap[String, Integer], Int) = {
680682
val errorMap = new HashMap[String, Integer]()
681683
var expectedErrors = 0
682-
files.filter(_.getName.endsWith(".scala")).foreach { file =>
684+
files.filter(isSourceFile).foreach { file =>
683685
Using(Source.fromFile(file, "UTF-8")) { source =>
684686
source.getLines.zipWithIndex.foreach { case (line, lineNbr) =>
685687
val errors = line.toSeq.sliding("// error".length).count(_.unwrap == "// error")

tests/neg/WrongBounds.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import java.util.*;
2+
3+
class WrongBounds {
4+
class LL<T> extends ArrayList<List<T>> {}
5+
class Wrap<T extends List<List<?>>> extends ArrayList<T> {}
6+
class Wrong<T extends LL<?>> extends Wrap<T> {} // error
7+
}

tests/neg/java-wrong-bounds/D_1.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
class A
2+
3+
class D[T >: A](v: T) {
4+
def getV(): T = v
5+
}

tests/neg/java-wrong-bounds/J_2.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
public class J_2 extends D<String> { // error
2+
3+
public J_2() {
4+
super(null);
5+
}
6+
7+
public static D<String> getDS() { // error
8+
return new D<String>("DS");
9+
}
10+
11+
public static final D<String> fieldDS = new D<String>("DS"); // error
12+
13+
public static void useDS(D<String> ds) {} // error
14+
15+
public static <A extends D<String>> void genericDS() {} // error
16+
17+
public static void useOK(D<?> ds) {}
18+
19+
public static D<?> getOK() {return null;}
20+
21+
public static <A extends D<?>> D<?> genericOK(A a) {return a;}
22+
23+
}

0 commit comments

Comments
 (0)