aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Odersky <odersky@gmail.com>2016-07-09 19:46:39 +0200
committerMartin Odersky <odersky@gmail.com>2016-07-09 21:27:14 +0200
commit490cc5ff508b45e5b32bced5f4e760258450358e (patch)
tree81bfdfe51d2ce52c890d4e6dc4092737ee8845eb
parent17191782afa2cdc2f0b018ecf4429ba18eeafc73 (diff)
downloaddotty-490cc5ff508b45e5b32bced5f4e760258450358e.tar.gz
dotty-490cc5ff508b45e5b32bced5f4e760258450358e.tar.bz2
dotty-490cc5ff508b45e5b32bced5f4e760258450358e.zip
Fix 1365: Fix bindings in patterns
We need to compare pattern types with expected types in order to derive knowledge about pattern-bound variables. This is done use the mechanism of gadt bounds.
-rw-r--r--src/dotty/tools/dotc/core/TypeComparer.scala26
-rw-r--r--src/dotty/tools/dotc/typer/Typer.scala67
-rw-r--r--tests/pos/i1365.scala13
3 files changed, 84 insertions, 22 deletions
diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala
index 2523c6b9a..7ff98f727 100644
--- a/src/dotty/tools/dotc/core/TypeComparer.scala
+++ b/src/dotty/tools/dotc/core/TypeComparer.scala
@@ -813,14 +813,24 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
private def narrowGADTBounds(tr: NamedType, bound: Type, isUpper: Boolean): Boolean =
ctx.mode.is(Mode.GADTflexible) && {
val tparam = tr.symbol
- typr.println(s"narrow gadt bound of $tparam: ${tparam.info} from ${if (isUpper) "above" else "below"} to $bound ${bound.isRef(tparam)}")
- !bound.isRef(tparam) && {
- val oldBounds = ctx.gadt.bounds(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 }
+ typr.println(i"narrow gadt bound of $tparam: ${tparam.info} from ${if (isUpper) "above" else "below"} to $bound ${bound.isRef(tparam)}")
+ if (bound.isRef(tparam)) false
+ else bound match {
+ case bound: TypeRef
+ if bound.symbol.is(BindDefinedType) && ctx.gadt.bounds.contains(bound.symbol) &&
+ !tr.symbol.is(BindDefinedType) =>
+ // Avoid having pattern-bound types in gadt bounds,
+ // as these might be eliminated once the pattern is typechecked.
+ // Pattern-bound type symbols should be narrowed first, only if that fails
+ // should symbols in the environment be constrained.
+ narrowGADTBounds(bound, tr, !isUpper)
+ case _ =>
+ val oldBounds = ctx.gadt.bounds(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 }
}
}
diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala
index 268020ec5..10279ba8e 100644
--- a/src/dotty/tools/dotc/typer/Typer.scala
+++ b/src/dotty/tools/dotc/typer/Typer.scala
@@ -448,11 +448,12 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
return typed(untpd.Apply(untpd.TypedSplice(arg), tree.expr), pt)
case _ =>
}
- case tref: TypeRef if tref.symbol.isClass && !ctx.isAfterTyper =>
- val setBefore = ctx.mode is Mode.GADTflexible
- tpt1.tpe.<:<(pt)(ctx.addMode(Mode.GADTflexible))
- if (!setBefore) ctx.retractMode(Mode.GADTflexible)
case _ =>
+ if (!ctx.isAfterTyper) {
+ val setBefore = ctx.mode is Mode.GADTflexible
+ tpt1.tpe.<:<(pt)(ctx.addMode(Mode.GADTflexible))
+ if (!setBefore) ctx.retractMode(Mode.GADTflexible)
+ }
}
ascription(tpt1, isWildcard = true)
}
@@ -762,17 +763,37 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
def typedCase(tree: untpd.CaseDef, pt: Type, selType: Type, gadtSyms: Set[Symbol])(implicit ctx: Context): CaseDef = track("typedCase") {
val originalCtx = ctx
- def caseRest(pat: Tree)(implicit ctx: Context) = {
- pat foreachSubTree {
- case b: Bind =>
- if (ctx.scope.lookup(b.name) == NoSymbol) ctx.enter(b.symbol)
- else ctx.error(d"duplicate pattern variable: ${b.name}", b.pos)
- case _ =>
+ /** - replace all references to symbols associated with wildcards by their GADT bounds
+ * - enter all symbols introduced by a Bind in current scope
+ */
+ val indexPattern = new TreeMap {
+ val elimWildcardSym = new TypeMap {
+ def apply(t: Type) = t match {
+ case ref @ TypeRef(_, tpnme.WILDCARD) if ctx.gadt.bounds.contains(ref.symbol) =>
+ ctx.gadt.bounds(ref.symbol)
+ case TypeAlias(ref @ TypeRef(_, tpnme.WILDCARD)) if ctx.gadt.bounds.contains(ref.symbol) =>
+ ctx.gadt.bounds(ref.symbol)
+ case _ =>
+ mapOver(t)
+ }
}
+ override def transform(tree: Tree)(implicit ctx: Context) =
+ super.transform(tree.withType(elimWildcardSym(tree.tpe))) match {
+ case b: Bind =>
+ if (ctx.scope.lookup(b.name) == NoSymbol) ctx.enter(b.symbol)
+ else ctx.error(d"duplicate pattern variable: ${b.name}", b.pos)
+ b.symbol.info = elimWildcardSym(b.symbol.info)
+ b
+ case t => t
+ }
+ }
+
+ def caseRest(pat: Tree)(implicit ctx: Context) = {
+ val pat1 = indexPattern.transform(pat)
val guard1 = typedExpr(tree.guard, defn.BooleanType)
val body1 = ensureNoLocalRefs(typedExpr(tree.body, pt), pt, ctx.scope.toList)
.ensureConforms(pt)(originalCtx) // insert a cast if body does not conform to expected type if we disregard gadt bounds
- assignType(cpy.CaseDef(tree)(pat, guard1, body1), body1)
+ assignType(cpy.CaseDef(tree)(pat1, guard1, body1), body1)
}
val gadtCtx =
@@ -963,11 +984,30 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
assignType(cpy.ByNameTypeTree(tree)(result1), result1)
}
+ /** Define a new symbol associated with a Bind or pattern wildcard and
+ * make it gadt narrowable.
+ */
+ private def newPatternBoundSym(name: Name, info: Type, pos: Position)(implicit ctx: Context) = {
+ val flags = if (name.isTypeName) BindDefinedType else EmptyFlags
+ val sym = ctx.newSymbol(ctx.owner, name, flags | Case, info, coord = pos)
+ if (name.isTypeName) ctx.gadt.setBounds(sym, info.bounds)
+ sym
+ }
+
def typedTypeBoundsTree(tree: untpd.TypeBoundsTree)(implicit ctx: Context): TypeBoundsTree = track("typedTypeBoundsTree") {
val TypeBoundsTree(lo, hi) = desugar.typeBoundsTree(tree)
val lo1 = typed(lo)
val hi1 = typed(hi)
- assignType(cpy.TypeBoundsTree(tree)(lo1, hi1), lo1, hi1)
+ val tree1 = assignType(cpy.TypeBoundsTree(tree)(lo1, hi1), lo1, hi1)
+ if (ctx.mode.is(Mode.Pattern)) {
+ // Associate a pattern-bound type symbol with the wildcard.
+ // The bounds of the type symbol can be constrained when comparing a pattern type
+ // with an expected type in typedTyped. The type symbol is eliminated once
+ // the enclosing pattern has been typechecked; see `indexPattern` in `typedCase`.
+ val wildcardSym = newPatternBoundSym(tpnme.WILDCARD, tree1.tpe, tree.pos)
+ tree1.withType(wildcardSym.typeRef)
+ }
+ else tree1
}
def typedBind(tree: untpd.Bind, pt: Type)(implicit ctx: Context): Tree = track("typedBind") {
@@ -983,8 +1023,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
tpd.cpy.UnApply(body1)(fn, Nil,
typed(untpd.Bind(tree.name, arg).withPos(tree.pos), arg.tpe) :: Nil)
case _ =>
- val flags = if (tree.isType) BindDefinedType else EmptyFlags
- val sym = ctx.newSymbol(ctx.owner, tree.name, flags | Case, body1.tpe, coord = tree.pos)
+ val sym = newPatternBoundSym(tree.name, body1.tpe, tree.pos)
assignType(cpy.Bind(tree)(tree.name, body1), sym)
}
}
diff --git a/tests/pos/i1365.scala b/tests/pos/i1365.scala
new file mode 100644
index 000000000..e7d47da4b
--- /dev/null
+++ b/tests/pos/i1365.scala
@@ -0,0 +1,13 @@
+import scala.collection.mutable.ArrayBuffer
+
+trait Message[M]
+class Script[S] extends ArrayBuffer[Message[S]] with Message[S]
+
+class Test[A] {
+ def f(cmd: Message[A]): Unit = cmd match {
+ case s: Script[_] => s.iterator.foreach(x => f(x))
+ }
+ def g(cmd: Message[A]): Unit = cmd match {
+ case s: Script[z] => s.iterator.foreach(x => g(x))
+ }
+}