Skip to content

Commit e8c78d8

Browse files
committed
Reject all explicitly written type references with bad bounds
Fixes further unsoundness examples added to neg/i15569.scala.
1 parent 298ef15 commit e8c78d8

File tree

14 files changed

+60
-48
lines changed

14 files changed

+60
-48
lines changed

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

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
281281
super.transform(tree)(using gadtCtx)
282282
case tree: Ident =>
283283
if tree.isType then
284+
Checking.checkGoodBounds(tree.tpe, tree.srcPos)
284285
checkNotPackage(tree)
285286
else
286287
if tree.symbol.is(Inline) && !Inlines.inInlineMethod then
@@ -295,6 +296,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
295296
ctx.compilationUnit.needsInlining = true
296297
if name.isTypeName then
297298
Checking.checkRealizable(qual.tpe, qual.srcPos)
299+
Checking.checkGoodBounds(tree.tpe, tree.srcPos)
298300
withMode(Mode.Type)(super.transform(checkNotPackage(tree)))
299301
else
300302
checkNoConstructorProxy(tree)
@@ -345,12 +347,6 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
345347
val tree1 @ TypeApply(fn, args) = normalizeTypeArgs(tree)
346348
for arg <- args do
347349
checkInferredWellFormed(arg)
348-
val isInferred = arg.isInstanceOf[InferredTypeTree] || arg.span.isSynthetic
349-
if !isInferred then
350-
// only check explicit type arguments. We rely on inferred type arguments
351-
// to either have good bounds (if they come from a constraint), or be derived
352-
// from values that recursively need to have good bounds.
353-
Checking.checkGoodBounds(arg.tpe, arg.srcPos)
354350
if (fn.symbol != defn.ChildAnnot.primaryConstructor)
355351
// Make an exception for ChildAnnot, which should really have AnyKind bounds
356352
Checking.checkBounds(args, fn.tpe.widen.asInstanceOf[PolyType])

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,12 +87,12 @@ object Checking {
8787
checkBounds(args, tl.paramInfos, _.substParams(tl, _))
8888

8989
def checkGoodBounds(tpe: Type, pos: SrcPos)(using Context): Boolean =
90-
def recur(tp: Type) = tp.dealias match
90+
def recur(tp: Type): Boolean = tp.dealias match
9191
case tp: TypeRef =>
92-
checkGoodBounds(tp.info, pos)
93-
case TypeBounds(lo, hi) if !(lo <:< hi) =>
94-
val argStr = if tp eq tpe then "" else i" $tpe"
95-
report.error(i"type argument$argStr has potentially unrealizable bounds $tp", pos)
92+
recur(tp.info)
93+
case tp @ TypeBounds(lo, hi) if !(lo <:< hi) =>
94+
val tpStr = if tp eq tpe then "" else i" $tpe"
95+
report.error(i"type$tpStr has potentially conflicting bounds $tp", pos)
9696
false
9797
case _ =>
9898
true

tests/init/neg/i11572.scala

Lines changed: 0 additions & 17 deletions
This file was deleted.

tests/init/neg/i5854.scala

Lines changed: 0 additions & 5 deletions
This file was deleted.

tests/neg-custom-args/fatal-warnings/i13820.scala

Lines changed: 0 additions & 5 deletions
This file was deleted.

tests/neg-strict/i1050.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ object Indirect {
100100
trait U {
101101
trait X {
102102
val q: A & B = ???
103-
type M = q.L
103+
type M = q.L // error: conflicting bounds
104104
}
105105
final lazy val p: X = ???
106106
def brand(x: Any): p.M = x // error: conflicting bounds
@@ -119,7 +119,7 @@ object Indirect2 {
119119
}
120120
trait X {
121121
val q: Y = ???
122-
type M = q.r.L
122+
type M = q.r.L // error: conflicting bounds
123123
}
124124
final lazy val p: X = ???
125125
def brand(x: Any): p.M = x // error: conflicting bounds
@@ -152,7 +152,7 @@ object Rec2 {
152152
}
153153
trait X {
154154
val q: Y = ???
155-
type M = q.r.L
155+
type M = q.r.L // error: conflicting bounds
156156
}
157157
final lazy val p: X = ???
158158
def brand(x: Any): p.M = x // error: conflicting bounds
@@ -171,7 +171,7 @@ object Indirect3 {
171171
}
172172
trait X {
173173
val q: Y = ???
174-
type M = q.r.L
174+
type M = q.r.L // error: conflicting bounds
175175
}
176176
final lazy val p: X = ???
177177
def brand(x: Any): p.M = x // error: conflicting bounds

tests/neg/bounds.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
object Test {
2-
def g[B >: String <: Int](x: B): Int = x
2+
def g[B >: String <: Int](x: B): Int = x // error
33
def main(args: Array[String]): Unit = {
44
g("foo") // error: Type argument String' does not conform to upper bound Int
55
}

tests/neg/i11572.scala

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
class A {
2+
trait Cov[+X] {
3+
def get: X
4+
}
5+
trait Bounded {
6+
type T >: Cov[Int] <: Cov[String]
7+
}
8+
val t: Bounded = new Bounded {
9+
// Note: using this instead of t produces an error (as expected)
10+
override type T >: t.T <: t.T // error: conflicting bounds // error: conflicting bounds
11+
}
12+
13+
val covInt = new Cov[Int] {
14+
override def get: Int = 3
15+
}
16+
val str: String = ((covInt: t.T): Cov[String]).get // error: conflicting bounds, was ClassCastException: class Integer cannot be cast to class String
17+
18+
}

tests/neg/i13820.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
trait Expr { type T }
2+
3+
def foo[A](e: Expr { type T = A }) = e match
4+
case e1: Expr { type T <: Int } =>
5+
val i: Int = ??? : e1.T // error: unrealizable bounds

tests/neg/i15568.check

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
-- Error: tests/neg/i15568.scala:3:15 ----------------------------------------------------------------------------------
22
3 |type Bar = Foo[? >: Int <: String] // error
33
| ^
4-
| type argument has potentially unrealizable bounds >: Int <: String
4+
| type has potentially conflicting bounds >: Int <: String

tests/neg/i15569.scala

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,18 @@ def andThenSub[A, B, C](f: A <:< B, g: B <:< C): A <:< C =
1313
val unsound: Any <:< Nothing = andThenSub[Any, t, Nothing](summon, summon) // error
1414
unsound("hi :)")
1515

16+
@main def Test3 = (None: Option[Foo[?]]) match {
17+
case _: Option[Foo[t]] =>
18+
val unsound: Nothing = (5 : Any) : t // error
19+
(unsound : Unit => Unit).apply(())
20+
}
21+
22+
@main def Test4 =
23+
type t >: Any <: Nothing
24+
val unsound: Nothing = (5 : Any) : t // error
25+
(unsound : Unit => Unit).apply(())
26+
27+
@main def Test5 =
28+
type t >: Any <: Nothing
29+
val unsound: List[Nothing] = List(5 : Any) : List[t] // error
30+
(unsound.head : Unit => Unit).apply(())

tests/neg/i5854.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
class B {
2+
val a: String = (((1: Any): b.A): Nothing): String // error
3+
val b: { type A >: Any <: Nothing } = loop()
4+
def loop(): Nothing = loop()
5+
}

tests/pos/bounds.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ object ListMap {
1010
def apply[A1, B2](elems: Tuple2[A1, B2]*): Map[A1, B2] = empty[A1,B2].++(elems.iterator)
1111
}
1212

13-
class Test[A] {
13+
class Test[A <: AnyRef] {
1414

1515
def f[B >: A <: AnyRef](x: A): AnyRef = (x: B)
16-
def g[B >: String <: Int](x: B): Int = x
16+
def g[B >: String & Int <: Int](x: B): Int = x
1717

1818
}

tests/pos/i13820.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
trait Expr { type T }
22

3-
def foo[A](e: Expr { type T = A }) = e match
3+
def foo[A <: Int](e: Expr { type T = A }) = e match
44
case e1: Expr { type T <: Int } =>
5-
val i: Int = ??? : e1.T
5+
val i: Int = ??? : e1.T

0 commit comments

Comments
 (0)