Skip to content

Commit ec3a321

Browse files
authored
Fix logic when comparing var/def bindings with val refinements (#18049)
We always widened def to val, which means it made no difference in a comparison { val/var/def x: T } <: { val/def x: T} which kinds the bindings were. That clearly overlooks something important.
2 parents 171849f + a4e22b1 commit ec3a321

File tree

7 files changed

+63
-19
lines changed

7 files changed

+63
-19
lines changed

compiler/src/dotty/tools/dotc/core/TypeComparer.scala

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1998,7 +1998,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
19981998
def tp1IsSingleton: Boolean = tp1.isInstanceOf[SingletonType]
19991999

20002000
// A relaxed version of isSubType, which compares method types
2001-
// under the standard arrow rule which is contravarient in the parameter types,
2001+
// under the standard arrow rule which is contravariant in the parameter types,
20022002
// but under the condition that signatures might have to match (see sigsOK)
20032003
// This relaxed version is needed to correctly compare dependent function types.
20042004
// See pos/i12211.scala.
@@ -2015,10 +2015,21 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
20152015
case _ => inFrozenGadtIf(tp1IsSingleton) { isSubType(info1, info2) }
20162016

20172017
def qualifies(m: SingleDenotation): Boolean =
2018-
val info1 = m.info.widenExpr
2019-
isSubInfo(info1, tp2.refinedInfo.widenExpr, m.symbol.info.orElse(info1))
2018+
val info2 = tp2.refinedInfo
2019+
val isExpr2 = info2.isInstanceOf[ExprType]
2020+
val info1 = m.info match
2021+
case info1: ValueType if isExpr2 || m.symbol.is(Mutable) =>
2022+
// OK: { val x: T } <: { def x: T }
2023+
// OK: { var x: T } <: { def x: T }
2024+
// NO: { var x: T } <: { val x: T }
2025+
ExprType(info1)
2026+
case info1 @ MethodType(Nil) if isExpr2 && m.symbol.is(JavaDefined) =>
2027+
// OK{ { def x(): T } <: { def x: T} // if x is Java defined
2028+
ExprType(info1.resType)
2029+
case info1 => info1
2030+
isSubInfo(info1, info2, m.symbol.info.orElse(info1))
20202031
|| matchAbstractTypeMember(m.info)
2021-
|| (tp1.isStable && isSubType(TermRef(tp1, m.symbol), tp2.refinedInfo))
2032+
|| (tp1.isStable && m.symbol.isStableMember && isSubType(TermRef(tp1, m.symbol), tp2.refinedInfo))
20222033

20232034
tp1.member(name) match // inlined hasAltWith for performance
20242035
case mbr: SingleDenotation => qualifies(mbr)

tests/neg/i13703.check

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,10 @@
33
| ^^^^^^^^^^
44
| refinement cannot be a mutable var.
55
| You can use an explicit getter i and setter i_= instead
6+
-- [E007] Type Mismatch Error: tests/neg/i13703.scala:5:78 -------------------------------------------------------------
7+
5 |val f2: Foo { val i: Int; def i_=(x: Int): Unit } = new Foo { var i: Int = 0 } // error
8+
| ^
9+
| Found: Object with Foo {...}
10+
| Required: Foo{val i: Int; def i_=(x: Int): Unit}
11+
|
12+
| longer explanation available when compiling with `-explain`

tests/neg/i13703.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,6 @@ trait Foo extends reflect.Selectable
22

33
val f: Foo { var i: Int } = new Foo { var i: Int = 0 } // error
44

5-
val f2: Foo { val i: Int; def i_=(x: Int): Unit } = new Foo { var i: Int = 0 } // OK
5+
val f2: Foo { val i: Int; def i_=(x: Int): Unit } = new Foo { var i: Int = 0 } // error
6+
7+
val f3: Foo { def i: Int; def i_=(x: Int): Unit } = new Foo { var i: Int = 0 } // OK

tests/neg/i18047.scala

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
def foo(x: Any { def foo: Int }): Any { val foo: Int } = x // error
2+
def foo1(x: Any { val foo: Int }): Any { def foo: Int } = x // ok
3+
def foo2(x: Any { val foo: Int }): Any { val foo: Int } = x // ok
4+
def foo3(x: Any { def foo: Int }): Any { def foo: Int } = x // ok
5+
6+
class Foo:
7+
val foo: Int = 1
8+
class Foo1:
9+
def foo: Int = 1
10+
class Foo2:
11+
var foo: Int = 1
12+
13+
def foo4(x: Foo): Any { val foo: Int } = x // ok
14+
def foo4(x: Foo1): Any { val foo: Int } = x // error
15+
def foo4(x: Foo2): Any { val foo: Int } = x // error

tests/neg/i4496b.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ object TestStructuralVar {
1010
type T = {val a: Int; def a_=(x: Int): Unit}
1111
def upcast1(v: Foo1): T = v // error
1212
def upcast2(v: Foo2): T = v // error
13-
def upcast3(v: Foo3): T = v
13+
def upcast3(v: Foo3): T = v // error
1414
def verify(v: T) = ()
1515
def test(): Unit = {
1616
verify(upcast1(new Foo1 { val a = 10 }))

tests/run/i4496a.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ class Foo3 { var a: Int = 10 }
55
object Test {
66
def main(args: Array[String]): Unit = {
77
assert((new Foo1 : {val a: Int}).a == 10)
8-
assert((new Foo2 : {val a: Int}).a == 10)
9-
assert((new Foo3 : {val a: Int}).a == 10)
8+
assert((new Foo2 : {def a: Int}).a == 10)
9+
assert((new Foo3 : {def a: Int}).a == 10)
1010
}
1111
}

tests/run/i4496b.scala

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,10 @@ object Test {
1818

1919
// Consider one module upcasting all these instances to T. These casts are clearly well-typed.
2020
type T = {val a: Int}
21+
type T2 = {def a: Int}
2122
def upcast1(v: Foo1): T = v
22-
def upcast2(v: Foo2): T = v
23-
def upcast3(v: Foo3): T = v
23+
def upcast2(v: Foo2): T2 = v
24+
def upcast3(v: Foo3): T2 = v
2425

2526
// These accesses are also clearly well-typed
2627
def consume(v: T) = v.a
@@ -31,24 +32,32 @@ object Test {
3132
assert(v.a == 10)
3233
}
3334

35+
def consume2(v: T2) = v.a
36+
inline def consumeInl2(v: T2) = v.a
37+
def verify2(v: T2) = {
38+
assert(consume2(v) == 10)
39+
assert(consumeInl2(v) == 10)
40+
assert(v.a == 10)
41+
}
42+
3443
def test(): Unit = {
3544
// These calls are also clearly well-typed, hence can't be rejected.
3645
verify(upcast1(new Foo1 { val a = 10 }))
37-
verify(upcast2(new Foo2 { val a = 10 }))
38-
verify(upcast3(new Foo3 { var a = 10 }))
46+
verify2(upcast2(new Foo2 { val a = 10 }))
47+
verify2(upcast3(new Foo3 { var a = 10 }))
3948
// Ditto, so we must override access control to the class.
4049
verify(upcast1(new FooBar1))
41-
verify(upcast2(new FooBar2))
42-
verify(upcast3(new FooBar3))
50+
verify2(upcast2(new FooBar2))
51+
verify2(upcast3(new FooBar3))
4352

4453
// Other testcases
4554
verify(new {val a = 10} : T)
46-
verify(new {var a = 10} : T)
47-
verify(new {def a = 10} : T)
55+
verify2(new {var a = 10} : T2)
56+
verify2(new {def a = 10} : T2)
4857

4958
verify(new Bar1 : T)
50-
verify(new Bar2 : T)
51-
verify(new Bar3 : T)
59+
verify2(new Bar2 : T2)
60+
verify2(new Bar3 : T2)
5261
}
5362
}
5463

@@ -85,7 +94,7 @@ object Test {
8594
}
8695

8796
object TestStructuralVar {
88-
type T = {val a: Int; def a_=(x: Int): Unit}
97+
type T = {def a: Int; def a_=(x: Int): Unit}
8998
def upcast3(v: Foo3): T = v
9099
def consume(v: T) = v.a
91100
inline def consumeInl(v: T) = v.a

0 commit comments

Comments
 (0)