diff options
author | Martin Odersky <odersky@gmail.com> | 2016-07-09 19:46:39 +0200 |
---|---|---|
committer | Martin Odersky <odersky@gmail.com> | 2016-07-09 21:27:14 +0200 |
commit | 490cc5ff508b45e5b32bced5f4e760258450358e (patch) | |
tree | 81bfdfe51d2ce52c890d4e6dc4092737ee8845eb | |
parent | 17191782afa2cdc2f0b018ecf4429ba18eeafc73 (diff) | |
download | dotty-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.scala | 26 | ||||
-rw-r--r-- | src/dotty/tools/dotc/typer/Typer.scala | 67 | ||||
-rw-r--r-- | tests/pos/i1365.scala | 13 |
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)) + } +} |