Skip to content

Add match type #4964

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 49 commits into from
Sep 20, 2018
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
9ef3898
Add MatchType as a type form
odersky Aug 20, 2018
1c17274
Represent literals used as types with SingletonTypeTrees
odersky Aug 20, 2018
24a2f73
Syntax, parsing, and type-checking of match types
odersky Aug 20, 2018
6d472f5
Fix unpickling of match types
odersky Aug 20, 2018
0877a36
Blacklist match type fromtasty test
odersky Aug 20, 2018
bd8b3cd
Classify type defs with matches as RHS as abstract
odersky Aug 22, 2018
993c40c
Implement subtyping for match types
odersky Aug 22, 2018
151a37c
Cache match reduce results
odersky Aug 22, 2018
ffac30e
Cache results of attempts to reduce match types
odersky Aug 23, 2018
e2d8bc3
Use a special type for match aliases
odersky Aug 24, 2018
b261ff7
Applications of erased functions are always pure
odersky Aug 24, 2018
a144c35
Allow user-defined error diagnostics when rewriting
odersky Aug 24, 2018
5147d32
Fix two issues when comparing match types
odersky Aug 24, 2018
fb5c554
MatchType reorg
odersky Aug 27, 2018
400495f
Typelevel natural numbers
odersky Aug 27, 2018
3d3d595
Reduce matches on creation
odersky Aug 27, 2018
38312e0
Handle MatchTypeTrees in ExtractAPI
odersky Aug 29, 2018
a9f9ced
Refine matchtype reduction caching
odersky Aug 29, 2018
2408b54
Coarser variance checking for match types
odersky Aug 29, 2018
7a51259
Add constValue function
odersky Aug 29, 2018
dc669a6
Add NonEmptyTuple abstract class
odersky Aug 29, 2018
d924ef1
More precise derivesFrom for MatchTypes
odersky Aug 29, 2018
0d4a118
Base Tuple computations on types
odersky Aug 29, 2018
189973b
Make MatchTypes value types
odersky Aug 29, 2018
8307685
Fix inlining of parameters of singleton type
odersky Aug 29, 2018
8219a83
Test tuples2 needs to run with -Yno-deep-subtypes
odersky Aug 29, 2018
56759c8
Add constValueOpt method
odersky Aug 29, 2018
45fbf3e
Fix unpickling of match type aliases
odersky Aug 29, 2018
e41bf9a
Survive bottom values in pattern matches
odersky Aug 29, 2018
2d83885
Report rewrite errors at outermost rewrite call
odersky Aug 29, 2018
b53bf1e
Harden Tuple operations against wrong inputs
odersky Aug 29, 2018
71fbc15
Reduce sizes of tuples of tuples2.scala
odersky Aug 29, 2018
2d76382
Allow additional arguments for typelevel.error
odersky Sep 6, 2018
10f2acb
GenericSignatures needs to consult erasedToObject
odersky Sep 6, 2018
03f5c4e
Properly erase NonEmptyTuple
odersky Sep 6, 2018
3657799
Make Tuple types covariant
odersky Sep 6, 2018
d53b3fa
Allow generic tuple operations to be dynamic
odersky Sep 6, 2018
a7ea105
Equate TupleN(...) and *: types
odersky Sep 6, 2018
9897d22
Description and informal spec for match types
odersky Sep 8, 2018
7a0c31f
Fix typo
odersky Sep 8, 2018
0ca095f
Add related work section to match-types.md
odersky Sep 10, 2018
194d85d
Fix matchtype termination
odersky Sep 10, 2018
c7ee07c
Fix `Elem` method for sizes > 23
odersky Sep 12, 2018
00de2f8
Print max constraint under -Ydetailed-stats
odersky Sep 12, 2018
c51a2c0
Normalize when simplifying
odersky Sep 12, 2018
16d1856
Propagate bound into nested match types
odersky Sep 12, 2018
536c350
Match cases in parallel
odersky Sep 12, 2018
5a8ac63
Update rules on match term/type checking
odersky Sep 13, 2018
7b548db
Go back to reducing match types sequentially
odersky Sep 13, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/ast/TreeInfo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,7 @@ trait UntypedTreeInfo extends TreeInfo[Untyped] { self: Trees.Instance[Untyped]
case mdef: TypeDef =>
def isBounds(rhs: Tree): Boolean = rhs match {
case _: TypeBoundsTree => true
case _: Match => true // Typedefs with Match rhs classify as abstract
case LambdaTypeTree(_, body) => isBounds(body)
case _ => false
}
Expand Down
10 changes: 5 additions & 5 deletions compiler/src/dotty/tools/dotc/ast/tpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,10 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
}

def CaseDef(pat: Tree, guard: Tree, body: Tree)(implicit ctx: Context): CaseDef =
ta.assignType(untpd.CaseDef(pat, guard, body), body)
ta.assignType(untpd.CaseDef(pat, guard, body), pat, body)

def Match(selector: Tree, cases: List[CaseDef])(implicit ctx: Context): Match =
ta.assignType(untpd.Match(selector, cases), cases)
ta.assignType(untpd.Match(selector, cases), selector, cases)

def Labeled(bind: Bind, expr: Tree)(implicit ctx: Context): Labeled =
ta.assignType(untpd.Labeled(bind, expr))
Expand Down Expand Up @@ -575,7 +575,6 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
}
}


override def Closure(tree: Tree)(env: List[Tree], meth: Tree, tpt: Tree)(implicit ctx: Context): Closure = {
val tree1 = untpd.cpy.Closure(tree)(env, meth, tpt)
tree match {
Expand All @@ -584,19 +583,20 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
case _ => ta.assignType(tree1, meth, tpt)
}
}

override def Match(tree: Tree)(selector: Tree, cases: List[CaseDef])(implicit ctx: Context): Match = {
val tree1 = untpd.cpy.Match(tree)(selector, cases)
tree match {
case tree: Match if sameTypes(cases, tree.cases) => tree1.withTypeUnchecked(tree.tpe)
case _ => ta.assignType(tree1, cases)
case _ => ta.assignType(tree1, selector, cases)
}
}

override def CaseDef(tree: Tree)(pat: Tree, guard: Tree, body: Tree)(implicit ctx: Context): CaseDef = {
val tree1 = untpd.cpy.CaseDef(tree)(pat, guard, body)
tree match {
case tree: CaseDef if body.tpe eq tree.body.tpe => tree1.withTypeUnchecked(tree.tpe)
case _ => ta.assignType(tree1, body)
case _ => ta.assignType(tree1, pat, body)
}
}

Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/config/Config.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ object Config {
final val cacheAsSeenFrom = true
final val cacheMemberNames = true
final val cacheImplicitScopes = true
final val cacheMatchReduced = true

final val checkCacheMembersNamed = false

Expand Down
53 changes: 29 additions & 24 deletions compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ trait ConstraintHandling {
/** If the constraint is frozen we cannot add new bounds to the constraint. */
protected var frozenConstraint = false

/** Potentially a type lambda that is still instantiatable, even though the constraint
* is generally frozen.
*/
protected var caseLambda: Type = NoType

/** If set, align arguments `S1`, `S2`when taking the glb
* `T1 { X = S1 } & T2 { X = S2 }` of a constraint upper bound for some type parameter.
* Aligning means computing `S1 =:= S2` which may change the current constraint.
Expand All @@ -47,7 +52,7 @@ trait ConstraintHandling {
*/
protected var comparedTypeLambdas: Set[TypeLambda] = Set.empty

private def addOneBound(param: TypeParamRef, bound: Type, isUpper: Boolean): Boolean =
protected def addOneBound(param: TypeParamRef, bound: Type, isUpper: Boolean): Boolean =
!constraint.contains(param) || {
def occursIn(bound: Type): Boolean = {
val b = bound.dealias
Expand Down Expand Up @@ -167,19 +172,20 @@ trait ConstraintHandling {
isSubType(tp1, tp2)
}

final def isSubTypeWhenFrozen(tp1: Type, tp2: Type): Boolean = {
val saved = frozenConstraint
@forceInline final def inFrozenConstraint[T](op: => T): T = {
val savedFrozen = frozenConstraint
val savedLambda = caseLambda
frozenConstraint = true
try isSubType(tp1, tp2)
finally frozenConstraint = saved
caseLambda = NoType
try op
finally {
frozenConstraint = savedFrozen
caseLambda = savedLambda
}
}

final def isSameTypeWhenFrozen(tp1: Type, tp2: Type): Boolean = {
val saved = frozenConstraint
frozenConstraint = true
try isSameType(tp1, tp2)
finally frozenConstraint = saved
}
final def isSubTypeWhenFrozen(tp1: Type, tp2: Type): Boolean = inFrozenConstraint(isSubType(tp1, tp2))
final def isSameTypeWhenFrozen(tp1: Type, tp2: Type): Boolean = inFrozenConstraint(isSameType(tp1, tp2))

/** Test whether the lower bounds of all parameters in this
* constraint are a solution to the constraint.
Expand Down Expand Up @@ -319,7 +325,7 @@ trait ConstraintHandling {
}

/** The current bounds of type parameter `param` */
final def bounds(param: TypeParamRef): TypeBounds = {
def bounds(param: TypeParamRef): TypeBounds = {
val e = constraint.entry(param)
if (e.exists) e.bounds
else {
Expand Down Expand Up @@ -355,7 +361,7 @@ trait ConstraintHandling {

/** Can `param` be constrained with new bounds? */
final def canConstrain(param: TypeParamRef): Boolean =
!frozenConstraint && (constraint contains param)
(!frozenConstraint || (caseLambda `eq` param.binder)) && constraint.contains(param)

/** Add constraint `param <: bound` if `fromBelow` is false, `param >: bound` otherwise.
* `bound` is assumed to be in normalized form, as specified in `firstTry` and
Expand Down Expand Up @@ -492,19 +498,18 @@ trait ConstraintHandling {
/** Check that constraint is fully propagated. See comment in Config.checkConstraintsPropagated */
def checkPropagated(msg: => String)(result: Boolean): Boolean = {
if (Config.checkConstraintsPropagated && result && addConstraintInvocations == 0) {
val saved = frozenConstraint
frozenConstraint = true
for (p <- constraint.domainParams) {
def check(cond: => Boolean, q: TypeParamRef, ordering: String, explanation: String): Unit =
assert(cond, i"propagation failure for $p $ordering $q: $explanation\n$msg")
for (u <- constraint.upper(p))
check(bounds(p).hi <:< bounds(u).hi, u, "<:", "upper bound not propagated")
for (l <- constraint.lower(p)) {
check(bounds(l).lo <:< bounds(p).hi, l, ">:", "lower bound not propagated")
check(constraint.isLess(l, p), l, ">:", "reverse ordering (<:) missing")
inFrozenConstraint {
for (p <- constraint.domainParams) {
def check(cond: => Boolean, q: TypeParamRef, ordering: String, explanation: String): Unit =
assert(cond, i"propagation failure for $p $ordering $q: $explanation\n$msg")
for (u <- constraint.upper(p))
check(bounds(p).hi <:< bounds(u).hi, u, "<:", "upper bound not propagated")
for (l <- constraint.lower(p)) {
check(bounds(l).lo <:< bounds(p).hi, l, ">:", "lower bound not propagated")
check(constraint.isLess(l, p), l, ">:", "reverse ordering (<:) missing")
}
}
}
frozenConstraint = saved
}
result
}
Expand Down
15 changes: 9 additions & 6 deletions compiler/src/dotty/tools/dotc/core/TypeApplications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -337,8 +337,8 @@ class TypeApplications(val self: Type) extends AnyVal {
tl => arg.paramInfos.map(_.subst(arg, tl).bounds),
tl => arg.resultType.subst(arg, tl)
)
case arg @ TypeAlias(alias) =>
arg.derivedTypeAlias(adaptArg(alias))
case arg: AliasingBounds =>
arg.derivedAlias(adaptArg(arg.alias))
case arg @ TypeBounds(lo, hi) =>
arg.derivedTypeBounds(adaptArg(lo), adaptArg(hi))
case _ =>
Expand Down Expand Up @@ -401,8 +401,8 @@ class TypeApplications(val self: Type) extends AnyVal {
dealiased.derivedAndType(dealiased.tp1.appliedTo(args), dealiased.tp2.appliedTo(args))
case dealiased: OrType =>
dealiased.derivedOrType(dealiased.tp1.appliedTo(args), dealiased.tp2.appliedTo(args))
case dealiased: TypeAlias =>
dealiased.derivedTypeAlias(dealiased.alias.appliedTo(args))
case dealiased: AliasingBounds =>
dealiased.derivedAlias(dealiased.alias.appliedTo(args))
case dealiased: TypeBounds =>
dealiased.derivedTypeBounds(dealiased.lo.appliedTo(args), dealiased.hi.appliedTo(args))
case dealiased: LazyRef =>
Expand Down Expand Up @@ -434,10 +434,13 @@ class TypeApplications(val self: Type) extends AnyVal {
appliedTo(args)
}

/** Turns non-bounds types to type aliases */
/** Turns non-bounds types to type bounds.
* A (possible lambda abstracted) match type is turned into an abstract type.
* Every other type is turned into a type alias
*/
final def toBounds(implicit ctx: Context): TypeBounds = self match {
case self: TypeBounds => self // this can happen for wildcard args
case _ => TypeAlias(self)
case _ => if (self.isMatch) MatchAlias(self) else TypeAlias(self)
}

/** Translate a type of the form From[T] to To[T], keep other types as they are.
Expand Down
103 changes: 96 additions & 7 deletions compiler/src/dotty/tools/dotc/core/TypeComparer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import config.Printers.{typr, constr, subtyping, gadts, noPrinter}
import TypeErasure.{erasedLub, erasedGlb}
import TypeApplications._
import scala.util.control.NonFatal
import typer.ProtoTypes.constrained
import reporting.trace

/** Provides methods to compare types.
Expand Down Expand Up @@ -102,6 +103,9 @@ class TypeComparer(initctx: Context) extends ConstraintHandling {
true
}

protected def gadtBounds(sym: Symbol)(implicit ctx: Context) = ctx.gadt.bounds(sym)
protected def gadtSetBounds(sym: Symbol, b: TypeBounds) = ctx.gadt.setBounds(sym, b)

// Subtype testing `<:<`

def topLevelSubType(tp1: Type, tp2: Type): Boolean = {
Expand Down Expand Up @@ -336,7 +340,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling {
recur(tp1.underlying, tp2)
case tp1: WildcardType =>
def compareWild = tp1.optBounds match {
case TypeBounds(lo, _) => recur(lo, tp2)
case bounds: TypeBounds => recur(bounds.lo, tp2)
case _ => true
}
compareWild
Expand All @@ -362,22 +366,25 @@ class TypeComparer(initctx: Context) extends ConstraintHandling {
false
}
joinOK || recur(tp11, tp2) && recur(tp12, tp2)
case tp1: MatchType =>
val reduced = tp1.reduced
if (reduced.exists) recur(reduced, tp2) else thirdTry
case _: FlexType =>
true
case _ =>
thirdTry
}

def thirdTryNamed(tp2: NamedType): Boolean = tp2.info match {
case TypeBounds(lo2, _) =>
case info2: TypeBounds =>
def compareGADT: Boolean = {
val gbounds2 = ctx.gadt.bounds(tp2.symbol)
val gbounds2 = gadtBounds(tp2.symbol)
(gbounds2 != null) &&
(isSubTypeWhenFrozen(tp1, gbounds2.lo) ||
narrowGADTBounds(tp2, tp1, approx, isUpper = false)) &&
GADTusage(tp2.symbol)
}
isSubApproxHi(tp1, lo2) || compareGADT || fourthTry
isSubApproxHi(tp1, info2.lo) || compareGADT || fourthTry

case _ =>
val cls2 = tp2.symbol
Expand Down Expand Up @@ -532,6 +539,9 @@ class TypeComparer(initctx: Context) extends ConstraintHandling {
case _ =>
}
either(recur(tp1, tp21), recur(tp1, tp22)) || fourthTry
case tp2: MatchType =>
val reduced = tp2.reduced
if (reduced.exists) recur(tp1, reduced) else fourthTry
case tp2: MethodType =>
def compareMethod = tp1 match {
case tp1: MethodType =>
Expand Down Expand Up @@ -594,7 +604,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling {
tp1.info match {
case TypeBounds(_, hi1) =>
def compareGADT = {
val gbounds1 = ctx.gadt.bounds(tp1.symbol)
val gbounds1 = gadtBounds(tp1.symbol)
(gbounds1 != null) &&
(isSubTypeWhenFrozen(gbounds1.hi, tp2) ||
narrowGADTBounds(tp1, tp2, approx, isUpper = true)) &&
Expand Down Expand Up @@ -665,6 +675,14 @@ class TypeComparer(initctx: Context) extends ConstraintHandling {
case _ =>
}
either(recur(tp11, tp2), recur(tp12, tp2))
case tp1: MatchType =>
def compareMatch = tp2 match {
case tp2: MatchType =>
isSameType(tp1.scrutinee, tp2.scrutinee) &&
tp1.cases.corresponds(tp2.cases)(isSubType)
case _ => false
}
recur(tp1.underlying, tp2) || compareMatch
case tp1: AnnotatedType if tp1.isRefining =>
isNewSubType(tp1.parent)
case JavaArrayType(elem1) =>
Expand Down Expand Up @@ -1131,12 +1149,12 @@ class TypeComparer(initctx: Context) extends ConstraintHandling {
gadts.println(i"narrow gadt bound of $tparam: ${tparam.info} from ${if (isUpper) "above" else "below"} to $bound ${bound.toString} ${bound.isRef(tparam)}")
if (bound.isRef(tparam)) false
else {
val oldBounds = ctx.gadt.bounds(tparam)
val oldBounds = gadtBounds(tparam)
val newBounds =
if (isUpper) TypeBounds(oldBounds.lo, oldBounds.hi & bound)
else TypeBounds(oldBounds.lo | bound, oldBounds.hi)
isSubType(newBounds.lo, newBounds.hi) &&
{ ctx.gadt.setBounds(tparam, newBounds); true }
{ gadtSetBounds(tparam, newBounds); true }
}
}
}
Expand Down Expand Up @@ -1719,6 +1737,77 @@ object TypeComparer {
}
}

class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
import state.constraint

val footprint = mutable.Set[Type]()

override def bounds(param: TypeParamRef): TypeBounds = {
if (param.binder `ne` caseLambda) footprint += param
super.bounds(param)
}

override def addOneBound(param: TypeParamRef, bound: Type, isUpper: Boolean): Boolean = {
if (param.binder `ne` caseLambda) footprint += param
super.addOneBound(param, bound, isUpper)
}

override def gadtBounds(sym: Symbol)(implicit ctx: Context) = {
footprint += sym.typeRef
super.gadtBounds(sym)
}

override def gadtSetBounds(sym: Symbol, b: TypeBounds) = {
footprint += sym.typeRef
super.gadtSetBounds(sym, b)
}

def matchCase(scrut: Type, cas: Type, instantiate: Boolean)(implicit ctx: Context): Type = {

def paramInstances = new TypeAccumulator[Array[Type]] {
def apply(inst: Array[Type], t: Type) = t match {
case t @ TypeParamRef(b, n) if b `eq` caseLambda =>
inst(n) = instanceType(t, fromBelow = variance >= 0)
inst
case _ =>
foldOver(inst, t)
}
}

def instantiateParams(inst: Array[Type]) = new TypeMap {
def apply(t: Type) = t match {
case t @ TypeParamRef(b, n) if b `eq` caseLambda => inst(n)
case t: LazyRef => apply(t.ref)
case _ => mapOver(t)
}
}

val saved = constraint
try {
inFrozenConstraint {
val cas1 = cas match {
case cas: HKTypeLambda =>
caseLambda = constrained(cas)
caseLambda.resultType
case _ =>
cas
}
val defn.FunctionOf(pat :: Nil, body, _, _) = cas1
if (isSubType(scrut, pat))
caseLambda match {
case caseLambda: HKTypeLambda if instantiate =>
val instances = paramInstances(new Array(caseLambda.paramNames.length), pat)
instantiateParams(instances)(body)
case _ =>
body
}
else NoType
}
}
finally constraint = saved
}
}

/** A type comparer that can record traces of subtype operations */
class ExplainingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
import TypeComparer._
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/core/TypeOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,8 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
}
case _: ThisType | _: BoundType =>
tp
case tp: TypeAlias =>
tp.derivedTypeAlias(simplify(tp.alias, theMap))
case tp: AliasingBounds =>
tp.derivedAlias(simplify(tp.alias, theMap))
case AndType(l, r) if !ctx.mode.is(Mode.Type) =>
simplify(l, theMap) & simplify(r, theMap)
case OrType(l, r) if !ctx.mode.is(Mode.Type) =>
Expand Down
Loading