Skip to content

Commit 7104d2e

Browse files
committed
Reject all explicitly written type references with bad bounds
... except for type parameters, since these will need to be instantiated with good types themselves. Fixes further unsoundness examples added to neg/i15569.scala.
1 parent 298ef15 commit 7104d2e

File tree

11 files changed

+43
-32
lines changed

11 files changed

+43
-32
lines changed

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

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,8 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
280280
ctx
281281
super.transform(tree)(using gadtCtx)
282282
case tree: Ident =>
283-
if tree.isType then
283+
if tree.isType && !tree.symbol.is(Param) 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/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/init/neg/i11572.scala renamed to tests/neg/i11572.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ class A {
55
trait Bounded {
66
type T >: Cov[Int] <: Cov[String]
77
}
8-
val t: Bounded = new Bounded { // error
8+
val t: Bounded = new Bounded {
99
// Note: using this instead of t produces an error (as expected)
10-
override type T >: t.T <: t.T
10+
override type T >: t.T <: t.T // error // error (conflicting bounds)
1111
}
1212

1313
val covInt = new Cov[Int] {
1414
override def get: Int = 3
1515
}
16-
val str: String = ((covInt: t.T): Cov[String]).get // ClassCastException: class Integer cannot be cast to class String
16+
val str: String = ((covInt: t.T): Cov[String]).get // error, was ClassCastException: class Integer cannot be cast to class String
1717
}

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/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)