Skip to content

Commit ef6ec75

Browse files
authored
Merge pull request #15577 from dotty-staging/fix-15569-2
Reject all explicitly written type references with bad bounds
2 parents b184009 + 7c13eee commit ef6ec75

File tree

15 files changed

+133
-48
lines changed

15 files changed

+133
-48
lines changed

compiler/src/dotty/tools/dotc/reporting/messages.scala

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1446,9 +1446,18 @@ import transform.SymUtils._
14461446
}
14471447

14481448
class DoesNotConformToBound(tpe: Type, which: String, bound: Type)(using Context)
1449-
extends TypeMismatchMsg(tpe, bound)(DoesNotConformToBoundID) {
1450-
def msg = em"Type argument ${tpe} does not conform to $which bound $bound"
1451-
}
1449+
extends TypeMismatchMsg(
1450+
if which == "lower" then bound else tpe,
1451+
if which == "lower" then tpe else bound)(DoesNotConformToBoundID):
1452+
private def isBounds = tpe match
1453+
case TypeBounds(lo, hi) => lo ne hi
1454+
case _ => false
1455+
override def canExplain = !isBounds
1456+
def msg =
1457+
if isBounds then
1458+
em"Type argument ${tpe} does not overlap with $which bound $bound"
1459+
else
1460+
em"Type argument ${tpe} does not conform to $which bound $bound"
14521461

14531462
class DoesNotConformToSelfType(category: String, selfType: Type, cls: Symbol,
14541463
otherSelf: Type, relation: String, other: Symbol)(

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

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import ContextFunctionResults.annotateContextResults
1616
import config.Printers.typr
1717
import util.SrcPos
1818
import reporting._
19+
import NameKinds.WildcardParamName
1920

2021
object PostTyper {
2122
val name: String = "posttyper"
@@ -345,12 +346,6 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
345346
val tree1 @ TypeApply(fn, args) = normalizeTypeArgs(tree)
346347
for arg <- args do
347348
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)
354349
if (fn.symbol != defn.ChildAnnot.primaryConstructor)
355350
// Make an exception for ChildAnnot, which should really have AnyKind bounds
356351
Checking.checkBounds(args, fn.tpe.widen.asInstanceOf[PolyType])
@@ -403,13 +398,20 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
403398
val reference = ctx.settings.sourceroot.value
404399
val relativePath = util.SourceFile.relativePath(ctx.compilationUnit.source, reference)
405400
sym.addAnnotation(Annotation.makeSourceFile(relativePath))
406-
else (tree.rhs, sym.info) match
407-
case (rhs: LambdaTypeTree, bounds: TypeBounds) =>
408-
VarianceChecker.checkLambda(rhs, bounds)
409-
if sym.isOpaqueAlias then
410-
VarianceChecker.checkLambda(rhs, TypeBounds.upper(sym.opaqueAlias))
411-
case _ =>
401+
else
402+
if !sym.is(Param) && !sym.owner.isOneOf(AbstractOrTrait) then
403+
Checking.checkGoodBounds(tree.symbol)
404+
(tree.rhs, sym.info) match
405+
case (rhs: LambdaTypeTree, bounds: TypeBounds) =>
406+
VarianceChecker.checkLambda(rhs, bounds)
407+
if sym.isOpaqueAlias then
408+
VarianceChecker.checkLambda(rhs, TypeBounds.upper(sym.opaqueAlias))
409+
case _ =>
412410
processMemberDef(super.transform(tree))
411+
case tree: Bind =>
412+
if tree.symbol.isType && !tree.symbol.name.is(WildcardParamName) then
413+
Checking.checkGoodBounds(tree.symbol)
414+
super.transform(tree)
413415
case tree: New if isCheckable(tree) =>
414416
Checking.checkInstantiable(tree.tpe, tree.srcPos)
415417
super.transform(tree)

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

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,10 @@ object Checking {
7373
showInferred(MissingTypeParameterInTypeApp(arg.tpe), app, tpt))
7474
}
7575
for (arg, which, bound) <- TypeOps.boundsViolations(args, boundss, instantiate, app) do
76-
if checkGoodBounds(arg.tpe, arg.srcPos.focus) then
77-
report.error(
78-
showInferred(DoesNotConformToBound(arg.tpe, which, bound),
79-
app, tpt),
80-
arg.srcPos.focus)
76+
report.error(
77+
showInferred(DoesNotConformToBound(arg.tpe, which, bound),
78+
app, tpt),
79+
arg.srcPos.focus)
8180

8281
/** Check that type arguments `args` conform to corresponding bounds in `tl`
8382
* Note: This does not check the bounds of AppliedTypeTrees. These
@@ -86,17 +85,19 @@ object Checking {
8685
def checkBounds(args: List[tpd.Tree], tl: TypeLambda)(using Context): Unit =
8786
checkBounds(args, tl.paramInfos, _.substParams(tl, _))
8887

89-
def checkGoodBounds(tpe: Type, pos: SrcPos)(using Context): Boolean =
90-
def recur(tp: Type) = tp.dealias match
91-
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)
96-
false
97-
case _ =>
98-
true
99-
recur(tpe)
88+
def checkGoodBounds(sym: Symbol)(using Context): Boolean =
89+
val bad = findBadBounds(sym.typeRef)
90+
if bad.exists then
91+
report.error(em"$sym has possibly conflicting bounds $bad", sym.srcPos)
92+
!bad.exists
93+
94+
/** If `tp` dealiases to a typebounds L..H where not L <:< H
95+
* return the potentially conflicting bounds, otherwise return NoType.
96+
*/
97+
private def findBadBounds(tp: Type)(using Context): Type = tp.dealias match
98+
case tp: TypeRef => findBadBounds(tp.info)
99+
case tp @ TypeBounds(lo, hi) if !(lo <:< hi) => tp
100+
case _ => NoType
100101

101102
/** Check applied type trees for well-formedness. This means
102103
* - all arguments are within their corresponding bounds

compiler/test/dotty/tools/dotc/CompilationTests.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ class CompilationTests {
159159
compileFile("tests/neg-custom-args/i12650.scala", allowDeepSubtypes),
160160
compileFile("tests/neg-custom-args/i9517.scala", defaultOptions.and("-Xprint-types")),
161161
compileFile("tests/neg-custom-args/i11637.scala", defaultOptions.and("-explain")),
162+
compileFile("tests/neg-custom-args/i15575.scala", defaultOptions.and("-explain")),
162163
compileFile("tests/neg-custom-args/interop-polytypes.scala", allowDeepSubtypes.and("-Yexplicit-nulls")),
163164
compileFile("tests/neg-custom-args/conditionalWarnings.scala", allowDeepSubtypes.and("-deprecation").and("-Xfatal-warnings")),
164165
compileFilesInDir("tests/neg-custom-args/isInstanceOf", allowDeepSubtypes and "-Xfatal-warnings"),

tests/init/neg/i11572.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ 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 { // error
99
// Note: using this instead of t produces an error (as expected)
1010
override type T >: t.T <: t.T
1111
}

tests/init/neg/i5854.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
class B {
22
val a: String = (((1: Any): b.A): Nothing): String
3-
val b: { type A >: Any <: Nothing } = loop() // error
3+
val b: { type A >: Any <: Nothing } = loop() // error
44
def loop(): Nothing = loop()
55
}

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

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

tests/neg-custom-args/i15575.check

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
-- [E057] Type Mismatch Error: tests/neg-custom-args/i15575.scala:3:27 -------------------------------------------------
2+
3 | def bar[T]: Unit = foo[T & Any] // error
3+
| ^
4+
| Type argument T & Any does not conform to lower bound Any
5+
|---------------------------------------------------------------------------------------------------------------------
6+
| Explanation (enabled by `-explain`)
7+
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
8+
| I tried to show that
9+
| Any
10+
| conforms to
11+
| T & Any
12+
| but the comparison trace ended with `false`:
13+
|
14+
| ==> Any <: T & Any
15+
| ==> Any <: T
16+
| <== Any <: T = false
17+
| <== Any <: T & Any = false
18+
|
19+
| The tests were made under the empty constraint
20+
---------------------------------------------------------------------------------------------------------------------
21+
-- [E057] Type Mismatch Error: tests/neg-custom-args/i15575.scala:7:14 -------------------------------------------------
22+
7 | val _ = foo[String] // error
23+
| ^
24+
| Type argument String does not conform to lower bound CharSequence
25+
|---------------------------------------------------------------------------------------------------------------------
26+
| Explanation (enabled by `-explain`)
27+
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
28+
| I tried to show that
29+
| CharSequence
30+
| conforms to
31+
| String
32+
| but the comparison trace ended with `false`:
33+
|
34+
| ==> CharSequence <: String
35+
| ==> CharSequence <: String
36+
| <== CharSequence <: String = false
37+
| <== CharSequence <: String = false
38+
|
39+
| The tests were made under the empty constraint
40+
---------------------------------------------------------------------------------------------------------------------

tests/neg-custom-args/i15575.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
object Test1:
2+
def foo[T >: Any]: Unit = ()
3+
def bar[T]: Unit = foo[T & Any] // error
4+
5+
object Test2:
6+
def foo[T >: CharSequence]: Unit = ()
7+
val _ = foo[String] // error

tests/neg/i15568.check

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
-- Error: tests/neg/i15568.scala:3:15 ----------------------------------------------------------------------------------
1+
-- [E057] Type Mismatch 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 argument >: Int <: String does not overlap with upper bound String

tests/neg/i15569.scala

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,42 @@ def andThenSub[A, B, C](f: A <:< B, g: B <:< C): A <:< C =
44
f.andThen(g)
55

66
@main def Test = (None: Option[Foo[?]]) match {
7-
case _: Option[Foo[t]] =>
8-
val unsound: Any <:< Nothing = andThenSub[Any, t, Nothing](summon, summon) // error
7+
case _: Option[Foo[t]] => // error
8+
val unsound: Any <:< Nothing = andThenSub[Any, t, Nothing](summon, summon)
99
unsound("hi :)")
1010
}
1111
@main def Test2 =
12-
type t >: Any <: Nothing
13-
val unsound: Any <:< Nothing = andThenSub[Any, t, Nothing](summon, summon) // error
12+
type t >: Any <: Nothing // error
13+
val unsound: Any <:< Nothing = andThenSub[Any, t, Nothing](summon, summon)
1414
unsound("hi :)")
1515

16+
@main def Test3 = (None: Option[Foo[?]]) match {
17+
case _: Option[Foo[t]] => // error
18+
val unsound: Nothing = (5 : Any) : t
19+
(unsound : Unit => Unit).apply(())
20+
}
21+
22+
@main def Test3ok = (None: Option[Foo[?]]) match {
23+
case _: Option[Foo[_]] => // ok
24+
}
25+
26+
@main def Test4 =
27+
type t >: Any <: Nothing // error
28+
val unsound: Nothing = (5 : Any) : t
29+
(unsound : Unit => Unit).apply(())
30+
31+
@main def Test5 =
32+
type t >: Any <: Nothing // error
33+
val unsound: List[Nothing] = List(5 : Any) : List[t]
34+
(unsound.head : Unit => Unit).apply(())
35+
36+
@main def Test6 =
37+
type t[X] >: Any <: Nothing // error
38+
val unsound: List[Nothing] = List(5 : Any) : List[t[String]]
39+
(unsound.head : Unit => Unit).apply(())
40+
41+
@main def Test7 =
42+
trait A:
43+
type t >: Any <: Nothing
44+
val unsound: List[Nothing] = List(5 : Any) : List[A#t] // error
45+
(unsound.head : Unit => Unit).apply(())

tests/pos/i13820.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ trait Expr { type T }
22

33
def foo[A](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

tests/semanticdb/expect/i5854.expect.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@ class B/*<-i5854::B#*/ {
66
// then resolve, or assign same semanticdb symbol for both
77
// fake symbol for b.A, and real symbol of A in b
88
val a/*<-i5854::B#a.*/: String/*->scala::Predef.String#*/ = (((1: Any/*->scala::Any#*/): b/*->i5854::B#b.*/.A): Nothing/*->scala::Nothing#*/): String/*->scala::Predef.String#*/
9-
val b/*<-i5854::B#b.*/: { type A/*<-local0*/ >: Any/*->scala::Any#*/ <: Nothing/*->scala::Nothing#*/ } = loop/*->i5854::B#loop().*/() // error
9+
val b/*<-i5854::B#b.*/: { type A/*<-local0*/ >: Any/*->scala::Any#*/ <: Nothing/*->scala::Nothing#*/ } = loop/*->i5854::B#loop().*/() // error
1010
def loop/*<-i5854::B#loop().*/(): Nothing/*->scala::Nothing#*/ = loop/*->i5854::B#loop().*/()
1111
}

tests/semanticdb/expect/i5854.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@ class B {
66
// then resolve, or assign same semanticdb symbol for both
77
// fake symbol for b.A, and real symbol of A in b
88
val a: String = (((1: Any): b.A): Nothing): String
9-
val b: { type A >: Any <: Nothing } = loop() // error
9+
val b: { type A >: Any <: Nothing } = loop() // error
1010
def loop(): Nothing = loop()
1111
}

0 commit comments

Comments
 (0)