|
| 1 | +package dotty.tools |
| 2 | +package dotc |
| 3 | +package core |
| 4 | + |
| 5 | +import Decorators._ |
| 6 | +import Symbols._ |
| 7 | +import Types._ |
| 8 | +import Flags._ |
| 9 | +import dotty.tools.dotc.reporting.trace |
| 10 | +import config.Printers._ |
| 11 | + |
| 12 | +trait PatternTypeConstrainer { self: TypeComparer => |
| 13 | + |
| 14 | + /** Derive type and GADT constraints that necessarily follow from a pattern with the given type matching |
| 15 | + * a scrutinee of the given type. |
| 16 | + * |
| 17 | + * This function breaks down scrutinee and pattern types into subcomponents between which there must be |
| 18 | + * a subtyping relationship, and derives constraints from those relationships. We have the following situation |
| 19 | + * in case of a (dynamic) pattern match: |
| 20 | + * |
| 21 | + * StaticScrutineeType PatternType |
| 22 | + * \ / |
| 23 | + * DynamicScrutineeType |
| 24 | + * |
| 25 | + * In simple cases, it must hold that `PatternType <: StaticScrutineeType`: |
| 26 | + * |
| 27 | + * StaticScrutineeType |
| 28 | + * | \ |
| 29 | + * | PatternType |
| 30 | + * | / |
| 31 | + * DynamicScrutineeType |
| 32 | + * |
| 33 | + * A good example of a situation where the above must hold is when static scrutinee type is the root of an enum, |
| 34 | + * and the pattern is an unapply of a case class, or a case object literal (of that enum). |
| 35 | + * |
| 36 | + * In slightly more complex cases, we may need to upcast `StaticScrutineeType`: |
| 37 | + * |
| 38 | + * SharedPatternScrutineeSuperType |
| 39 | + * / \ |
| 40 | + * StaticScrutineeType PatternType |
| 41 | + * \ / |
| 42 | + * DynamicScrutineeType |
| 43 | + * |
| 44 | + * This may be the case if the scrutinee is a singleton type or a path-dependent type. It is also the case |
| 45 | + * for the following definitions: |
| 46 | + * |
| 47 | + * trait Expr[T] |
| 48 | + * trait IntExpr extends Expr[T] |
| 49 | + * trait Const[T] extends Expr[T] |
| 50 | + * |
| 51 | + * StaticScrutineeType = Const[T] |
| 52 | + * PatternType = IntExpr |
| 53 | + * |
| 54 | + * Union and intersection types are an additional complication - if either scrutinee or pattern are a union type, |
| 55 | + * then the above relationships only need to hold for the "leaves" of the types. |
| 56 | + * |
| 57 | + * Finally, if pattern type contains hk-types applied to concrete types (as opposed to type variables), |
| 58 | + * or either scrutinee or pattern type contain type member refinements, the above relationships do not need |
| 59 | + * to hold at all. Consider (where `T1`, `T2` are unrelated traits): |
| 60 | + * |
| 61 | + * StaticScrutineeType = { type T <: T1 } |
| 62 | + * PatternType = { type T <: T2 } |
| 63 | + * |
| 64 | + * In the above situation, DynamicScrutineeType can equal { type T = T1 & T2 }, but there is no useful relationship |
| 65 | + * between StaticScrutineeType and PatternType (nor any of their subcomponents). Similarly: |
| 66 | + * |
| 67 | + * StaticScrutineeType = Option[T1] |
| 68 | + * PatternType = Some[T2] |
| 69 | + * |
| 70 | + * Again, DynamicScrutineeType may equal Some[T1 & T2], and there's no useful relationship between the static |
| 71 | + * scrutinee and pattern types. This does not apply if the pattern type is only applied to type variables, |
| 72 | + * in which case the subtyping relationship "heals" the type. |
| 73 | + */ |
| 74 | + def constrainPatternType(pat: Type, scrut: Type): Boolean = trace(i"constrainPatternType($scrut, $pat)", gadts) { |
| 75 | + |
| 76 | + def classesMayBeCompatible: Boolean = { |
| 77 | + import Flags._ |
| 78 | + val patClassSym = pat.widenSingleton.classSymbol |
| 79 | + val scrutClassSym = scrut.widenSingleton.classSymbol |
| 80 | + !patClassSym.exists || !scrutClassSym.exists || { |
| 81 | + if (patClassSym.is(Final)) patClassSym.derivesFrom(scrutClassSym) |
| 82 | + else if (scrutClassSym.is(Final)) scrutClassSym.derivesFrom(patClassSym) |
| 83 | + else if (!patClassSym.is(Flags.Trait) && !scrutClassSym.is(Flags.Trait)) |
| 84 | + patClassSym.derivesFrom(scrutClassSym) || scrutClassSym.derivesFrom(patClassSym) |
| 85 | + else true |
| 86 | + } |
| 87 | + } |
| 88 | + |
| 89 | + def stripRefinement(tp: Type): Type = tp match { |
| 90 | + case tp: RefinedOrRecType => stripRefinement(tp.parent) |
| 91 | + case tp => tp |
| 92 | + } |
| 93 | + |
| 94 | + def constrainUpcasted(scrut: Type): Boolean = trace(i"constrainUpcasted($scrut)", gadts) { |
| 95 | + val upcasted: Type = scrut match { |
| 96 | + case scrut: TypeRef if scrut.symbol.isClass => |
| 97 | + // we do not infer constraints following from all parents for performance reasons |
| 98 | + // in principle however, if `A extends B, C`, then `A` can be treated as `B & C` |
| 99 | + scrut.firstParent |
| 100 | + case scrut @ AppliedType(tycon: TypeRef, _) if tycon.symbol.isClass => |
| 101 | + val patClassSym = pat.classSymbol |
| 102 | + // as above, we do not consider all parents for performance reasons |
| 103 | + def firstParentSharedWithPat(tp: Type, tpClassSym: ClassSymbol): Symbol = { |
| 104 | + var parents = tpClassSym.info.parents |
| 105 | + parents match { |
| 106 | + case first :: rest => |
| 107 | + if (first.classSymbol == defn.ObjectClass) parents = rest |
| 108 | + case _ => ; |
| 109 | + } |
| 110 | + parents match { |
| 111 | + case first :: _ => |
| 112 | + val firstClassSym = first.classSymbol.asClass |
| 113 | + val res = if (patClassSym.derivesFrom(firstClassSym)) firstClassSym |
| 114 | + else firstParentSharedWithPat(first, firstClassSym) |
| 115 | + res |
| 116 | + case _ => NoSymbol |
| 117 | + } |
| 118 | + } |
| 119 | + val sym = firstParentSharedWithPat(tycon, tycon.symbol.asClass) |
| 120 | + if (sym.exists) scrut.baseType(sym) else NoType |
| 121 | + case scrut: TypeProxy => scrut.superType |
| 122 | + case _ => NoType |
| 123 | + } |
| 124 | + if (upcasted.exists) |
| 125 | + constrainSimplePatternType(pat, upcasted) || constrainUpcasted(upcasted) |
| 126 | + else true |
| 127 | + } |
| 128 | + |
| 129 | + scrut.dealias match { |
| 130 | + case OrType(scrut1, scrut2) => |
| 131 | + either(constrainPatternType(pat, scrut1), constrainPatternType(pat, scrut2)) |
| 132 | + case AndType(scrut1, scrut2) => |
| 133 | + constrainPatternType(pat, scrut1) && constrainPatternType(pat, scrut2) |
| 134 | + case scrut: RefinedOrRecType => |
| 135 | + constrainPatternType(pat, stripRefinement(scrut)) |
| 136 | + case scrut => pat.dealias match { |
| 137 | + case OrType(pat1, pat2) => |
| 138 | + either(constrainPatternType(pat1, scrut), constrainPatternType(pat2, scrut)) |
| 139 | + case AndType(pat1, pat2) => |
| 140 | + constrainPatternType(pat1, scrut) && constrainPatternType(pat2, scrut) |
| 141 | + case scrut: RefinedOrRecType => |
| 142 | + constrainPatternType(stripRefinement(scrut), pat) |
| 143 | + case pat => |
| 144 | + constrainSimplePatternType(pat, scrut) || classesMayBeCompatible && constrainUpcasted(scrut) |
| 145 | + } |
| 146 | + } |
| 147 | + } |
| 148 | + |
| 149 | + /** Constrain "simple" patterns (see `constrainPatternType`). |
| 150 | + * |
| 151 | + * This function attempts to modify pattern and scrutinee type s.t. the pattern must be a subtype of the scrutinee, |
| 152 | + * or otherwise it cannot possibly match. In order to do that, we: |
| 153 | + * |
| 154 | + * 1. Rely on `constrainPatternType` to break the actual scrutinee/pattern types into subcomponents |
| 155 | + * 2. Widen type parameters of scrutinee type that are not invariantly refined (see below) by the pattern type. |
| 156 | + * 3. Wrap the pattern type in a skolem to avoid overconstraining top-level abstract types in scrutinee type |
| 157 | + * 4. Check that `WidenedScrutineeType <: NarrowedPatternType` |
| 158 | + * |
| 159 | + * Importantly, note that the pattern type may contain type variables. |
| 160 | + * |
| 161 | + * ## Invariant refinement |
| 162 | + * Essentially, we say that `D[B] extends C[B]` s.t. refines parameter `A` of `trait C[A]` invariantly if |
| 163 | + * when `c: C[T]` and `c` is instance of `D`, then necessarily `c: D[T]`. This is violated if `A` is variant: |
| 164 | + * |
| 165 | + * trait C[+A] |
| 166 | + * trait D[+B](val b: B) extends C[B] |
| 167 | + * trait E extends D[Any](0) with C[String] |
| 168 | + * |
| 169 | + * `E` is a counter-example to the above - if `e: E`, then `e: C[String]` and `e` is instance of `D`, but |
| 170 | + * it is false that `e: D[String]`! This is a problem if we're constraining a pattern like the below: |
| 171 | + * |
| 172 | + * def foo[T](c: C[T]): T = c match { |
| 173 | + * case d: D[t] => d.b |
| 174 | + * } |
| 175 | + * |
| 176 | + * It'd be unsound for us to say that `t <: T`, even though that follows from `D[t] <: C[T]`. |
| 177 | + * Note, however, that if `D` was a final class, we *could* rely on that relationship. |
| 178 | + * To support typical case classes, we also assume that this relationship holds for them and their parent traits. |
| 179 | + * This is enforced by checking that classes inheriting from case classes do not extend the parent traits of those |
| 180 | + * case classes without also appropriately extending the relevant case class |
| 181 | + * (see `RefChecks#checkCaseClassInheritanceInvariant`). |
| 182 | + */ |
| 183 | + def constrainSimplePatternType(patternTp: Type, scrutineeTp: Type): Boolean = { |
| 184 | + def refinementIsInvariant(tp: Type): Boolean = tp match { |
| 185 | + case tp: ClassInfo => tp.cls.is(Final) || tp.cls.is(Case) |
| 186 | + case tp: TypeProxy => refinementIsInvariant(tp.underlying) |
| 187 | + case _ => false |
| 188 | + } |
| 189 | + |
| 190 | + def widenVariantParams = new TypeMap { |
| 191 | + def apply(tp: Type) = mapOver(tp) match { |
| 192 | + case tp @ AppliedType(tycon, args) => |
| 193 | + val args1 = args.zipWithConserve(tycon.typeParams)((arg, tparam) => |
| 194 | + if (tparam.paramVariance != 0) TypeBounds.empty else arg |
| 195 | + ) |
| 196 | + tp.derivedAppliedType(tycon, args1) |
| 197 | + case tp => |
| 198 | + tp |
| 199 | + } |
| 200 | + } |
| 201 | + |
| 202 | + val widePt = if (ctx.scala2Mode || refinementIsInvariant(patternTp)) scrutineeTp else widenVariantParams(scrutineeTp) |
| 203 | + val narrowTp = SkolemType(patternTp) |
| 204 | + trace(i"constraining simple pattern type $narrowTp <:< $widePt", gadts, res => s"$res\ngadt = ${ctx.gadt.debugBoundsDescription}") { |
| 205 | + isSubType(narrowTp, widePt) |
| 206 | + } |
| 207 | + } |
| 208 | +} |
0 commit comments