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 1 commit
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
Prev Previous commit
Next Next commit
Use a special type for match aliases
This is a more principled implementation of the situation that
match aliases are not full type aliases because one side cannot
be freely substituted for the other. Match aliases are checked
in some respects like abstract type bounds (in particular,
nested cycles are allowed), but are treated as aliases in others
(in particular, lower and upper "bound" are always guaranteed
to be the same).
  • Loading branch information
odersky committed Sep 6, 2018
commit e2d8bc35591817e27aba472af7bf1ff5ee24b02b
10 changes: 5 additions & 5 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 @@ -440,7 +440,7 @@ class TypeApplications(val self: Type) extends AnyVal {
*/
final def toBounds(implicit ctx: Context): TypeBounds = self match {
case self: TypeBounds => self // this can happen for wildcard args
case _ => if (self.isMatch) TypeBounds.upper(self) else 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
8 changes: 4 additions & 4 deletions compiler/src/dotty/tools/dotc/core/TypeComparer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling {
recur(tp1.underlying, tp2)
case tp1: WildcardType =>
def compareWild = tp1.optBounds match {
case bounds: TypeBounds => recur(bounds.effectiveLo, tp2)
case bounds: TypeBounds => recur(bounds.lo, tp2)
case _ => true
}
compareWild
Expand Down Expand Up @@ -384,7 +384,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling {
narrowGADTBounds(tp2, tp1, approx, isUpper = false)) &&
GADTusage(tp2.symbol)
}
isSubApproxHi(tp1, info2.effectiveLo) || compareGADT || fourthTry
isSubApproxHi(tp1, info2.lo) || compareGADT || fourthTry

case _ =>
val cls2 = tp2.symbol
Expand Down Expand Up @@ -428,7 +428,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling {
// So if the constraint is not yet frozen, we do the same comparison again
// with a frozen constraint, which means that we get a chance to do the
// widening in `fourthTry` before adding to the constraint.
if (frozenConstraint) isSubType(tp1, bounds(tp2).effectiveLo)
if (frozenConstraint) isSubType(tp1, bounds(tp2).lo)
else isSubTypeWhenFrozen(tp1, tp2)
alwaysTrue || {
if (canConstrain(tp2) && !approx.low)
Expand Down Expand Up @@ -814,7 +814,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling {
if (tyconIsTypeRef) recur(tp1, tp2.superType)
else isSubApproxHi(tp1, tycon2bounds.lo.applyIfParameterized(args2))
else
fallback(tycon2bounds.effectiveLo)
fallback(tycon2bounds.lo)

tycon2 match {
case param2: TypeParamRef =>
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
91 changes: 58 additions & 33 deletions compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ object Types {
t.symbol.is(Provisional) ||
apply(x, t.prefix) || {
t.info match {
case TypeAlias(alias) => apply(x, alias)
case info: AliasingBounds => apply(x, info.alias)
case TypeBounds(lo, hi) => apply(apply(x, lo), hi)
case _ => false
}
Expand Down Expand Up @@ -319,7 +319,7 @@ object Types {
}

/** Is this an alias TypeBounds? */
final def isAlias: Boolean = this.isInstanceOf[TypeAlias]
final def isTypeAlias: Boolean = this.isInstanceOf[TypeAlias]

/** Is this a MethodType which is from Java */
def isJavaMethod: Boolean = false
Expand Down Expand Up @@ -628,8 +628,8 @@ object Types {
val rinfo = tp.refinedInfo
if (name.isTypeName && !pinfo.isInstanceOf[ClassInfo]) { // simplified case that runs more efficiently
val jointInfo =
if (rinfo.isAlias) rinfo
else if (pinfo.isAlias) pinfo
if (rinfo.isTypeAlias) rinfo
else if (pinfo.isTypeAlias) pinfo
else if (ctx.base.pendingMemberSearches.contains(name)) pinfo safe_& rinfo
else pinfo recoverable_& rinfo
pdenot.asSingleDenotation.derivedSingleDenotation(pdenot.symbol, jointInfo)
Expand Down Expand Up @@ -1238,7 +1238,7 @@ object Types {
*/
@tailrec final def normalizedPrefix(implicit ctx: Context): Type = this match {
case tp: NamedType =>
if (tp.symbol.info.isAlias) tp.info.normalizedPrefix else tp.prefix
if (tp.symbol.info.isTypeAlias) tp.info.normalizedPrefix else tp.prefix
case tp: ClassInfo =>
tp.prefix
case tp: TypeProxy =>
Expand Down Expand Up @@ -3085,8 +3085,8 @@ object Types {

def derivedLambdaAbstraction(paramNames: List[TypeName], paramInfos: List[TypeBounds], resType: Type)(implicit ctx: Context): Type =
resType match {
case resType @ TypeAlias(alias) =>
resType.derivedTypeAlias(newLikeThis(paramNames, paramInfos, alias))
case resType: AliasingBounds =>
resType.derivedAlias(newLikeThis(paramNames, paramInfos, resType.alias))
case resType @ TypeBounds(lo, hi) =>
resType.derivedTypeBounds(
if (lo.isRef(defn.NothingClass)) lo else newLikeThis(paramNames, paramInfos, lo),
Expand Down Expand Up @@ -3187,8 +3187,8 @@ object Types {
override def fromParams[PI <: ParamInfo.Of[TypeName]](params: List[PI], resultType: Type)(implicit ctx: Context): Type = {
def expand(tp: Type) = super.fromParams(params, tp)
resultType match {
case rt: TypeAlias =>
rt.derivedTypeAlias(expand(rt.alias))
case rt: AliasingBounds =>
rt.derivedAlias(expand(rt.alias))
case rt @ TypeBounds(lo, hi) =>
rt.derivedTypeBounds(
if (lo.isRef(defn.NothingClass)) lo else expand(lo), expand(hi))
Expand Down Expand Up @@ -3526,6 +3526,14 @@ object Types {

// ------ MatchType ---------------------------------------------------------------

/** scrutinee match { case_1 ... case_n }
*
* where
*
* case_i = [X1, ..., Xn] patternType => resultType
*
* and `X_1,...X_n` are the type variables bound in `patternType`
*/
abstract case class MatchType(scrutinee: Type, cases: List[Type]) extends CachedProxyType with TermType {
override def computeHash(bs: Binders) = doHash(bs, scrutinee, cases)

Expand Down Expand Up @@ -3781,51 +3789,64 @@ object Types {
case _ => super.| (that)
}

def effectiveLo(implicit ctx: Context) =
if (hi.isMatch) hi else lo

override def computeHash(bs: Binders) = doHash(bs, lo, hi)
override def stableHash = lo.stableHash && hi.stableHash

override def equals(that: Any): Boolean = equals(that, null)

override def iso(that: Any, bs: BinderPairs): Boolean = that match {
case that: TypeAlias => false
case that: AliasingBounds => false
case that: TypeBounds => lo.equals(that.lo, bs) && hi.equals(that.hi, bs)
case _ => false
}

override def eql(that: Type) = that match {
case that: TypeAlias => false
case that: AliasingBounds => false
case that: TypeBounds => lo.eq(that.lo) && hi.eq(that.hi)
case _ => false
}
}

class RealTypeBounds(lo: Type, hi: Type) extends TypeBounds(lo, hi)

abstract class TypeAlias(val alias: Type) extends TypeBounds(alias, alias) {
/** Common supertype of `TypeAlias` and `MatchAlias` */
abstract class AliasingBounds(val alias: Type) extends TypeBounds(alias, alias) {

/** pre: this is a type alias */
def derivedTypeAlias(alias: Type)(implicit ctx: Context) =
if (alias eq this.alias) this else TypeAlias(alias)
def derivedAlias(alias: Type)(implicit ctx: Context): AliasingBounds

override def computeHash(bs: Binders) = doHash(bs, alias)
override def stableHash = alias.stableHash

override def iso(that: Any, bs: BinderPairs): Boolean = that match {
case that: TypeAlias => alias.equals(that.alias, bs)
case that: AliasingBounds => this.isTypeAlias == that.isTypeAlias && alias.equals(that.alias, bs)
case _ => false
}
// equals comes from case class; no matching override is needed

override def eql(that: Type): Boolean = that match {
case that: TypeAlias => alias.eq(that.alias)
case that: AliasingBounds => this.isTypeAlias == that.isTypeAlias && alias.eq(that.alias)
case _ => false
}
}

class CachedTypeAlias(alias: Type) extends TypeAlias(alias)
/** = T
*/
class TypeAlias(alias: Type) extends AliasingBounds(alias) {
def derivedAlias(alias: Type)(implicit ctx: Context) =
if (alias eq this.alias) this else TypeAlias(alias)
}

/** = T where `T` is a `MatchType`
*
* Match aliases are treated differently from type aliases. Their sides are mutually
* subtypes of each other but one side is not generally substitutable for the other.
* If we assumed full substitutivity, we would have to reject all recursive match
* aliases (or else take the jump and allow full recursive types).
*/
class MatchAlias(alias: Type) extends AliasingBounds(alias) {
def derivedAlias(alias: Type)(implicit ctx: Context) =
if (alias eq this.alias) this else MatchAlias(alias)
}

object TypeBounds {
def apply(lo: Type, hi: Type)(implicit ctx: Context): TypeBounds =
Expand All @@ -3836,11 +3857,15 @@ object Types {
}

object TypeAlias {
def apply(alias: Type)(implicit ctx: Context) =
unique(new CachedTypeAlias(alias))
def apply(alias: Type)(implicit ctx: Context) = unique(new TypeAlias(alias))
def unapply(tp: TypeAlias): Option[Type] = Some(tp.alias)
}

object MatchAlias {
def apply(alias: Type)(implicit ctx: Context) = unique(new MatchAlias(alias))
def unapply(tp: MatchAlias): Option[Type] = Some(tp.alias)
}

// ----- Annotated and Import types -----------------------------------------------

/** An annotated type tpe @ annot */
Expand Down Expand Up @@ -4042,8 +4067,8 @@ object Types {
def apply(tp: Type): Type = tp match {
case tp: TypeRef if tp.symbol.is(ClassTypeParam) && tp.symbol.owner == cls =>
tp.info match {
case TypeAlias(alias) =>
mapOver(alias)
case info: AliasingBounds =>
mapOver(info.alias)
case TypeBounds(lo, hi) =>
range(atVariance(-variance)(apply(lo)), apply(hi))
case _ =>
Expand Down Expand Up @@ -4099,8 +4124,8 @@ object Types {
tp.derivedRefinedType(parent, tp.refinedName, info)
protected def derivedRecType(tp: RecType, parent: Type): Type =
tp.rebind(parent)
protected def derivedTypeAlias(tp: TypeAlias, alias: Type): Type =
tp.derivedTypeAlias(alias)
protected def derivedAlias(tp: AliasingBounds, alias: Type): Type =
tp.derivedAlias(alias)
protected def derivedTypeBounds(tp: TypeBounds, lo: Type, hi: Type): Type =
tp.derivedTypeBounds(lo, hi)
protected def derivedSuperType(tp: SuperType, thistp: Type, supertp: Type): Type =
Expand Down Expand Up @@ -4167,8 +4192,8 @@ object Types {
case tp: RefinedType =>
derivedRefinedType(tp, this(tp.parent), this(tp.refinedInfo))

case tp: TypeAlias =>
derivedTypeAlias(tp, atVariance(0)(this(tp.alias)))
case tp: AliasingBounds =>
derivedAlias(tp, atVariance(0)(this(tp.alias)))

case tp: TypeBounds =>
variance = -variance
Expand Down Expand Up @@ -4387,7 +4412,7 @@ object Types {
else info match {
case Range(infoLo: TypeBounds, infoHi: TypeBounds) =>
assert(variance == 0)
if (!infoLo.isAlias && !infoHi.isAlias) propagate(infoLo, infoHi)
if (!infoLo.isTypeAlias && !infoHi.isTypeAlias) propagate(infoLo, infoHi)
else range(defn.NothingType, tp.parent)
case Range(infoLo, infoHi) =>
propagate(infoLo, infoHi)
Expand All @@ -4403,13 +4428,13 @@ object Types {
case _ => tp.rebind(parent)
}

override protected def derivedTypeAlias(tp: TypeAlias, alias: Type) =
override protected def derivedAlias(tp: AliasingBounds, alias: Type) =
if (alias eq tp.alias) tp
else alias match {
case Range(lo, hi) =>
if (variance > 0) TypeBounds(lo, hi)
else range(TypeAlias(lo), TypeAlias(hi))
case _ => tp.derivedTypeAlias(alias)
else range(tp.derivedAlias(lo), tp.derivedAlias(hi))
case _ => tp.derivedAlias(alias)
}

override protected def derivedTypeBounds(tp: TypeBounds, lo: Type, hi: Type) =
Expand Down
5 changes: 4 additions & 1 deletion compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,10 @@ class TreeUnpickler(reader: TastyReader,
case APPLIEDtype =>
readType().appliedTo(until(end)(readType()))
case TYPEBOUNDS =>
TypeBounds(readType(), readType())
val lo = readType()
val hi = readType()
if (lo.isMatch && (lo `eq` hi)) MatchAlias(lo)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this check be performed in TypeBounds?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't have a constructor in TypeBounds. Maybe we need a refactoring, I am not sure. Anyway at the moment MatchType as whole needs more work but this will be in future PRs.

else TypeBounds(lo, hi)
case ANNOTATEDtype =>
AnnotatedType(readType(), Annotation(readTerm()))
case ANDtype =>
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ class PlainPrinter(_ctx: Context) extends Printer {
val bounds =
if (constr.contains(tp)) constr.fullBounds(tp.origin)(ctx.addMode(Mode.Printing))
else TypeBounds.empty
if (bounds.isAlias) toText(bounds.lo) ~ (Str("^") provided ctx.settings.YprintDebug.value)
if (bounds.isTypeAlias) toText(bounds.lo) ~ (Str("^") provided ctx.settings.YprintDebug.value)
else if (ctx.settings.YshowVarBounds.value) "(" ~ toText(tp.origin) ~ "?" ~ toText(bounds) ~ ")"
else toText(tp.origin)
}
Expand Down Expand Up @@ -316,7 +316,7 @@ class PlainPrinter(_ctx: Context) extends Printer {
/** String representation of a definition's type following its name */
protected def toTextRHS(tp: Type): Text = controlled {
homogenize(tp) match {
case tp: TypeAlias =>
case tp: AliasingBounds =>
" = " ~ toText(tp.alias)
case tp @ TypeBounds(lo, hi) =>
(if (lo isRef defn.NothingClass) Text() else " >: " ~ toText(lo)) ~
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/printing/ReplPrinter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class ReplPrinter(_ctx: Context) extends DecompilerPrinter(_ctx) {
override def dclText(sym: Symbol): Text = {
toText(sym) ~ {
if (sym.is(Method)) toText(sym.info)
else if (sym.isType && sym.info.isInstanceOf[TypeAlias]) toText(sym.info)
else if (sym.isType && sym.info.isTypeAlias) toText(sym.info)
else if (sym.isType || sym.isClass) ""
else ":" ~~ toText(sym.info)
}
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -877,7 +877,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
val ttree =
typedType(untpd.rename(tree, tree.name.toTypeName))(nestedCtx)
ttree.tpe match {
case alias: TypeRef if alias.info.isAlias && !nestedCtx.reporter.hasErrors =>
case alias: TypeRef if alias.info.isTypeAlias && !nestedCtx.reporter.hasErrors =>
companionRef(alias) match {
case companion: TermRef => return untpd.ref(companion) withPos tree.pos
case _ =>
Expand Down
Loading