Skip to content

Commit 9a25a93

Browse files
committed
Implement match type amendment: extractors follow aliases and singletons
This implements the change proposed in scala/improvement-proposals#84. The added pos test case presents motivating examples, the added neg test cases demonstrate that errors are correctly reported when cycles are present. The potential for cycle is no worse than with the existing extraction logic as demonstrated by the existing test in `tests/neg/mt-deskolemize.scala`.
1 parent dfff8f6 commit 9a25a93

File tree

3 files changed

+145
-5
lines changed

3 files changed

+145
-5
lines changed

Diff for: compiler/src/dotty/tools/dotc/core/TypeComparer.scala

+60-5
Original file line numberDiff line numberDiff line change
@@ -3518,20 +3518,75 @@ class MatchReducer(initctx: Context) extends TypeComparer(initctx) {
35183518
false
35193519

35203520
case MatchTypeCasePattern.TypeMemberExtractor(typeMemberName, capture) =>
3521+
/** Try to remove references to `skolem` from a type in accordance with the spec.
3522+
*
3523+
* If any reference to `skolem` remains in the result type,
3524+
* `refersToSkolem` is set to true.
3525+
*/
3526+
class DropSkolemMap(skolem: SkolemType) extends TypeMap:
3527+
var refersToSkolem = false
3528+
def apply(tp: Type): Type =
3529+
tp match
3530+
case `skolem` =>
3531+
refersToSkolem = true
3532+
tp
3533+
case tp: NamedType =>
3534+
var savedRefersToSkolem = refersToSkolem
3535+
refersToSkolem = false
3536+
try
3537+
val pre1 = apply(tp.prefix)
3538+
if refersToSkolem then
3539+
tp match
3540+
case tp: TermRef => tp.info.widenExpr.dealias match
3541+
case info: SingletonType =>
3542+
refersToSkolem = false
3543+
apply(info)
3544+
case _ =>
3545+
tp.derivedSelect(pre1)
3546+
case tp: TypeRef => tp.info match
3547+
case info: AliasingBounds =>
3548+
refersToSkolem = false
3549+
apply(info.alias)
3550+
case _ =>
3551+
tp.derivedSelect(pre1)
3552+
else
3553+
tp.derivedSelect(pre1)
3554+
finally
3555+
refersToSkolem |= savedRefersToSkolem
3556+
case tp: LazyRef =>
3557+
// By default, TypeMap maps LazyRefs lazily. We need to
3558+
// force it for `refersToSkolem` to be correctly set.
3559+
apply(tp.ref)
3560+
case _ =>
3561+
mapOver(tp)
3562+
end DropSkolemMap
3563+
/** Try to remove references to `skolem` from `u` in accordance with the spec.
3564+
*
3565+
* If any reference to `skolem` remains in the result type, return
3566+
* NoType instead.
3567+
*/
3568+
def dropSkolem(u: Type, skolem: SkolemType): Type =
3569+
val dmap = DropSkolemMap(skolem)
3570+
val res = dmap(u)
3571+
if dmap.refersToSkolem then NoType else res
3572+
35213573
val stableScrut: SingletonType = scrut match
35223574
case scrut: SingletonType => scrut
35233575
case _ => SkolemType(scrut)
3576+
35243577
stableScrut.member(typeMemberName) match
35253578
case denot: SingleDenotation if denot.exists =>
35263579
val info = denot.info match
35273580
case alias: AliasingBounds => alias.alias // Extract the alias
35283581
case ClassInfo(prefix, cls, _, _, _) => prefix.select(cls) // Re-select the class from the prefix
35293582
case info => info // Notably, RealTypeBounds, which will eventually give a MatchResult.NoInstances
3530-
val infoRefersToSkolem = stableScrut.isInstanceOf[SkolemType] && stableScrut.occursIn(info)
3531-
val info1 = info match
3532-
case info: TypeBounds => info // Will already trigger a MatchResult.NoInstances
3533-
case _ if infoRefersToSkolem => RealTypeBounds(info, info) // Explicitly trigger a MatchResult.NoInstances
3534-
case _ => info // We have a match
3583+
val info1 = stableScrut match
3584+
case skolem: SkolemType =>
3585+
dropSkolem(info, skolem).orElse:
3586+
info match
3587+
case info: TypeBounds => info // Will already trigger a MatchResult.NoInstances
3588+
case _ => RealTypeBounds(info, info) // Explicitly trigger a MatchResult.NoInstances
3589+
case _ => info
35353590
rec(capture, info1, variance = 0, scrutIsWidenedAbstract)
35363591
case _ =>
35373592
false

Diff for: tests/neg/mt-deskolemize.scala

+36
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,39 @@ class SimpleLoop2 extends Expr:
1414

1515
object Test1:
1616
val x: ExtractValue[SimpleLoop1] = 1 // error
17+
18+
trait Description:
19+
type Elem <: Tuple
20+
21+
class PrimBroken extends Expr:
22+
type Value = Alias
23+
type Alias = Value // error
24+
25+
class Prim extends Expr:
26+
type Value = BigInt
27+
28+
class VecExpr[E <: Expr] extends Expr:
29+
type Value = Vector[ExtractValue[E]]
30+
31+
trait ProdExpr extends Expr:
32+
val description: Description
33+
type Value = Tuple.Map[description.Elem, [X] =>> ExtractValue[X & Expr]]
34+
35+
36+
class MyExpr1 extends ProdExpr:
37+
final val description = new Description:
38+
type Elem = (VecExpr[Prim], MyExpr2)
39+
40+
class MyExpr2 extends ProdExpr:
41+
final val description = new Description:
42+
type Elem = (VecExpr[VecExpr[MyExpr1]], Prim)
43+
44+
object Test2:
45+
def fromLiteral[E <: Expr](v: ExtractValue[E]): E = ???
46+
val x0: ExtractValue[Prim] = "" // error
47+
val x1: ExtractValue[PrimBroken] = 1 // error
48+
49+
val foo: MyExpr2 = new MyExpr2
50+
val v: foo.Value = (Vector(Vector()), 1) // error: Recursion limit exceeded
51+
val c: MyExpr2 = fromLiteral:
52+
(Vector(Vector()), 1) // error: Recursion limit exceeded

Diff for: tests/pos/mt-deskolemize.scala

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
trait Expr:
2+
type Value
3+
4+
object Expr:
5+
type Of[V] = Expr { type Value = V }
6+
type ExtractValue[F <: Expr] = F match
7+
case Expr.Of[v] => v
8+
import Expr.ExtractValue
9+
10+
class Prim extends Expr:
11+
type Value = Alias
12+
type Alias = BigInt
13+
14+
class VecExpr[E <: Expr] extends Expr:
15+
type Value = Vector[ExtractValue[E]]
16+
17+
trait Description:
18+
type Elem <: Tuple
19+
20+
trait ProdExpr extends Expr:
21+
val description: Description
22+
type Value = Tuple.Map[description.Elem, [X] =>> ExtractValue[X & Expr]]
23+
24+
class MyExpr1 extends ProdExpr:
25+
final val description = new Description:
26+
type Elem = (VecExpr[Prim], Prim)
27+
28+
class MyExpr2 extends ProdExpr:
29+
final val description = new Description:
30+
type Elem = (VecExpr[VecExpr[MyExpr1]], Prim)
31+
32+
trait ProdExprAlt[T <: Tuple] extends Expr:
33+
type Value = Tuple.Map[T, [X] =>> ExtractValue[X & Expr]]
34+
35+
class MyExpr3 extends ProdExprAlt[(Prim, VecExpr[Prim], Prim)]
36+
37+
object Test:
38+
def fromLiteral[E <: Expr](v: ExtractValue[E]): E = ???
39+
val a: Prim = fromLiteral(1)
40+
val b: VecExpr[Prim] = fromLiteral(Vector(1))
41+
val c: MyExpr1 = fromLiteral((Vector(1), 1))
42+
val d: MyExpr2 = fromLiteral(Vector(Vector((Vector(1), 1))), 2)
43+
val e: MyExpr3 = fromLiteral((1, Vector(1), 1))
44+
val f: ProdExprAlt[(MyExpr1, VecExpr[MyExpr3])] = fromLiteral:
45+
(
46+
(Vector(1), 1),
47+
Vector((1, Vector(1), 1), (2, Vector(1), 2))
48+
)
49+
val g: Expr { type Alias = Int; type Value = Alias } = fromLiteral(1)

0 commit comments

Comments
 (0)