Skip to content

Commit 8b3924c

Browse files
committed
Enhancement: Implement bounds checking
It turns out that bounds checking was missing so far.
1 parent b34bd61 commit 8b3924c

File tree

5 files changed

+64
-8
lines changed

5 files changed

+64
-8
lines changed

compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import config.{Config, Feature}
1111
import ast.{tpd, untpd, Trees}
1212
import Trees.*
1313
import typer.RefChecks.{checkAllOverrides, checkSelfAgainstParents}
14+
import typer.Checking.{checkBounds, checkAppliedTypesIn}
1415
import util.{SimpleIdentitySet, EqHashMap, SrcPos}
1516
import transform.SymUtils.*
1617
import transform.{Recheck, PreRecheck}
@@ -911,8 +912,27 @@ class CheckCaptures extends Recheck, SymTransformer:
911912
|The type needs to be declared explicitly.""", t.srcPos)
912913
case _ =>
913914
inferred.foreachPart(checkPure, StopAt.Static)
915+
case t @ TypeApply(fun, args) =>
916+
fun.knownType.widen match
917+
case tl: PolyType =>
918+
val normArgs = args.lazyZip(tl.paramInfos).map { (arg, bounds) =>
919+
arg.withType(arg.knownType.forceBoxStatus(
920+
bounds.hi.isBoxedCapturing | bounds.lo.isBoxedCapturing))
921+
}
922+
checkBounds(normArgs, tl)
923+
case _ =>
914924
case _ =>
915925
}
916-
926+
if !ctx.reporter.errorsReported then
927+
// We dont report errors hre if previous errors were reported, because other
928+
// errors often result in bad applied types, but flagging these bad types gives
929+
// often worse error messages than the original errors.
930+
val checkApplied = new TreeTraverser:
931+
def traverse(t: Tree)(using Context) = t match
932+
case tree: InferredTypeTree =>
933+
case tree: New =>
934+
case tree: TypeTree => checkAppliedTypesIn(tree.withKnownType)
935+
case _ => traverseChildren(t)
936+
checkApplied.traverse(unit)
917937
end CaptureChecker
918938
end CheckCaptures

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ object Recheck:
7272
val symd = sym.denot
7373
symd.validFor.firstPhaseId == phase.id + 1 && (sym.originDenotation ne symd)
7474

75-
extension (tree: Tree)
75+
extension [T <: Tree](tree: T)
7676

7777
/** Remember `tpe` as the type of `tree`, which might be different from the
7878
* type stored in the tree itself, unless a type was already remembered for `tree`.
@@ -87,11 +87,15 @@ object Recheck:
8787
if tpe ne tree.tpe then tree.putAttachment(RecheckedType, tpe)
8888

8989
/** The remembered type of the tree, or if none was installed, the original type */
90-
def knownType =
90+
def knownType: Type =
9191
tree.attachmentOrElse(RecheckedType, tree.tpe)
9292

9393
def hasRememberedType: Boolean = tree.hasAttachment(RecheckedType)
9494

95+
def withKnownType(using Context): T = tree.getAttachment(RecheckedType) match
96+
case Some(tpe) => tree.withType(tpe).asInstanceOf[T]
97+
case None => tree
98+
9599
extension (tpe: Type)
96100

97101
/** Map ExprType => T to () ?=> T (and analogously for pure versions).

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,12 @@ object Checking {
6767
*/
6868
def checkBounds(args: List[tpd.Tree], boundss: List[TypeBounds],
6969
instantiate: (Type, List[Type]) => Type, app: Type = NoType, tpt: Tree = EmptyTree)(using Context): Unit =
70-
args.lazyZip(boundss).foreach { (arg, bound) =>
71-
if !bound.isLambdaSub && !arg.tpe.hasSimpleKind then
72-
errorTree(arg,
73-
showInferred(MissingTypeParameterInTypeApp(arg.tpe), app, tpt))
74-
}
70+
if ctx.phase != Phases.checkCapturesPhase then
71+
args.lazyZip(boundss).foreach { (arg, bound) =>
72+
if !bound.isLambdaSub && !arg.tpe.hasSimpleKind then
73+
errorTree(arg,
74+
showInferred(MissingTypeParameterInTypeApp(arg.tpe), app, tpt))
75+
}
7576
for (arg, which, bound) <- TypeOps.boundsViolations(args, boundss, instantiate, app) do
7677
report.error(
7778
showInferred(DoesNotConformToBound(arg.tpe, which, bound), app, tpt),
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
object test {
2+
3+
class Tree
4+
5+
def f[X <: Tree](x: X): Unit = ()
6+
7+
class C[X <: Tree](x: X)
8+
9+
def foo(t: {*} Tree) =
10+
f(t) // error
11+
f[{*} Tree](t) // error
12+
f[Tree](t) // error
13+
val c1 = C(t) // error
14+
val c2 = C[{*} Tree](t) // error
15+
val c3 = C[Tree](t) // error
16+
17+
val foo: C[{*} Tree] = ???
18+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
object test {
2+
3+
class Tree
4+
5+
def f[X <: Tree](x: X): Unit = ()
6+
7+
class C[X <: Tree](x: X)
8+
9+
val foo: C[{*} Tree] = ??? // error
10+
type T = C[{*} Tree] // error
11+
val bar: T -> T = ???
12+
val baz: C[{*} Tree] -> Unit = ??? // error
13+
}

0 commit comments

Comments
 (0)