From 0855b071d913e7acd5271ab34062c69e25cd93cd Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 19 Mar 2016 19:40:47 +0100 Subject: [PATCH 01/18] Fixes to checkNonCyclic Simplified logic and now check prefixes of TypeRefs. Without the simplified logic we would get false cyclic errors for ski.scala. Test case: flowops.scala Fixes #1185. --- src/dotty/tools/dotc/typer/Checking.scala | 38 +++++++++++------------ tests/pos/flowops.scala | 31 ++++++++++++++++++ 2 files changed, 49 insertions(+), 20 deletions(-) create mode 100644 tests/pos/flowops.scala diff --git a/src/dotty/tools/dotc/typer/Checking.scala b/src/dotty/tools/dotc/typer/Checking.scala index 9b1f756b7ddf..22d2407bc558 100644 --- a/src/dotty/tools/dotc/typer/Checking.scala +++ b/src/dotty/tools/dotc/typer/Checking.scala @@ -156,16 +156,24 @@ object Checking { tp } - def apply(tp: Type) = tp match { + private def apply(tp: Type, cycleOK: Boolean, nestedCycleOK: Boolean): Type = { + val savedCycleOK = this.cycleOK + val savedNestedCycleOK = this.nestedCycleOK + this.cycleOK = cycleOK + this.nestedCycleOK = nestedCycleOK + try apply(tp) + finally { + this.cycleOK = savedCycleOK + this.nestedCycleOK = savedNestedCycleOK + } + } + + def apply(tp: Type): Type = tp match { case tp: TermRef => this(tp.info) mapOver(tp) case tp @ RefinedType(parent, name) => - val parent1 = this(parent) - val saved = cycleOK - cycleOK = nestedCycleOK - try tp.derivedRefinedType(parent1, name, this(tp.refinedInfo)) - finally cycleOK = saved + tp.derivedRefinedType(this(parent), name, this(tp.refinedInfo, nestedCycleOK, nestedCycleOK)) case tp @ TypeRef(pre, name) => try { // A prefix is interesting if it might contain (transitively) a reference @@ -182,19 +190,12 @@ object Checking { case _: RefinedType => true case _ => false } - // If prefix is interesting, check info of typeref recursively, marking the referred symbol - // with NoCompleter. This provokes a CyclicReference when the symbol - // is hit again. Without this precaution we could stackoverflow here. if (isInteresting(pre)) { - val info = tp.info - val sym = tp.symbol - if (sym.infoOrCompleter == SymDenotations.NoCompleter) throw CyclicReference(sym) - val symInfo = sym.info - if (sym.exists) sym.info = SymDenotations.NoCompleter - try checkInfo(info) - finally if (sym.exists) sym.info = symInfo + val pre1 = this(pre, false, false) + checkInfo(tp.info) + if (pre1 eq pre) tp else tp.newLikeThis(pre1) } - tp + else tp } catch { case ex: CyclicReference => ctx.debuglog(i"cycle detected for $tp, $nestedCycleOK, $cycleOK") @@ -210,9 +211,6 @@ object Checking { * @pre sym is not yet initialized (i.e. its type is a Completer). * @return `info` where every legal F-bounded reference is proctected * by a `LazyRef`, or `ErrorType` if a cycle was detected and reported. - * Furthermore: Add an #Apply to a fully instantiated type lambda, if none was - * given before. This is necessary here because sometimes type lambdas are not - * recognized when they are first formed. */ def checkNonCyclic(sym: Symbol, info: Type, reportErrors: Boolean)(implicit ctx: Context): Type = { val checker = new CheckNonCyclicMap(sym, reportErrors)(ctx.addMode(Mode.CheckCyclic)) diff --git a/tests/pos/flowops.scala b/tests/pos/flowops.scala new file mode 100644 index 000000000000..6aead26bedf7 --- /dev/null +++ b/tests/pos/flowops.scala @@ -0,0 +1,31 @@ +object Test { + import language.higherKinds + + class NotUsed + + trait FO[+Out, +Mat] { self => + type Repr[+O] <: FO[O, Mat] { + type Repr[+OO] = self.Repr[OO] + } + def map[T](f: Out => T): Repr[T] = ??? + } + + class Source[+O, +M] extends FO[O, M] { + type Repr[+OO] <: Source[OO, M] + } + + class Flow[-I, +O, +M] extends FO[O, M] { + type Repr[+OO] <: Flow[I, OO, M] + } + + implicit class x[O, M, F[o, m] <: FO[o, m]](val f: F[O, M]) extends AnyVal { + def xx(i: Int): f.Repr[O] = f.map(identity) + } + + type IntFlow[O, M] = Flow[Int, O, M] + + val s1 = new Source[Int, NotUsed].xx(12) + val s2: Source[Int, NotUsed] = s1 + val f1 = x[Int, NotUsed, IntFlow](new Flow[Int, Int, NotUsed]).xx(12) + val f2: Flow[Int, Int, NotUsed] = f1 +} From b49034715ac5dc5d3f8427b39e497d3e20c4c192 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 19 Mar 2016 19:46:47 +0100 Subject: [PATCH 02/18] Simplify and fix avoid logic The previous formulation broke for named parameters. Test case in flowops1.scala. --- src/dotty/tools/dotc/core/Types.scala | 8 ----- src/dotty/tools/dotc/typer/TypeAssigner.scala | 29 ++++++---------- tests/pos/flowops1.scala | 33 +++++++++++++++++++ 3 files changed, 43 insertions(+), 27 deletions(-) create mode 100644 tests/pos/flowops1.scala diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 3801f191449e..ffb662490320 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -841,14 +841,6 @@ object Types { case _ => this } - /** If this is a refinement type, the unrefined parent, - * else the type itself. - */ - final def unrefine(implicit ctx: Context): Type = stripTypeVar match { - case tp @ RefinedType(tycon, _) => tycon.unrefine - case _ => this - } - /** If this is a (possibly aliased, annotated, and/or parameterized) reference to * a class, the class type ref, otherwise NoType. * @param refinementOK If `true` we also skip non-parameter refinements. diff --git a/src/dotty/tools/dotc/typer/TypeAssigner.scala b/src/dotty/tools/dotc/typer/TypeAssigner.scala index 84951fd2b3e9..840346536612 100644 --- a/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -6,6 +6,7 @@ import core._ import ast._ import Scopes._, Contexts._, Constants._, Types._, Symbols._, Names._, Flags._, Decorators._ import ErrorReporting._, Annotations._, Denotations._, SymDenotations._, StdNames._, TypeErasure._ +import TypeApplications.AppliedType import util.Positions._ import config.Printers._ import ast.Trees._ @@ -93,27 +94,17 @@ trait TypeAssigner { case _ => mapOver(tp) } + case tp @ AppliedType(tycon, args) if toAvoid(tycon) => + val base = apply(tycon) + apply(base.appliedTo(tp.baseArgInfos(base.typeSymbol))) case tp @ RefinedType(parent, name) if variance > 0 => - // The naive approach here would be to first approximate the parent, - // but if the base type of the approximated parent is different from - // the current base type, then the current refinement won't be valid - // if it's a type parameter refinement. - // Therefore we first approximate the base type, then use `baseArgInfos` - // to get correct refinements for the approximated base type, then - // recursively approximate the resulting type. - val base = tp.unrefine - if (toAvoid(base)) { - val base1 = apply(base) - apply(base1.appliedTo(tp.baseArgInfos(base1.typeSymbol))) + val parent1 = apply(tp.parent) + val refinedInfo1 = apply(tp.refinedInfo) + if (toAvoid(refinedInfo1)) { + typr.println(s"dropping refinement from $tp") + parent1 } else { - val parent1 = apply(tp.parent) - val refinedInfo1 = apply(tp.refinedInfo) - if (toAvoid(refinedInfo1)) { - typr.println(s"dropping refinement from $tp") - parent1 - } else { - tp.derivedRefinedType(parent1, name, refinedInfo1) - } + tp.derivedRefinedType(parent1, name, refinedInfo1) } case tp: TypeVar if ctx.typerState.constraint.contains(tp) => val lo = ctx.typerState.constraint.fullLowerBound(tp.origin) diff --git a/tests/pos/flowops1.scala b/tests/pos/flowops1.scala new file mode 100644 index 000000000000..2acd515afa70 --- /dev/null +++ b/tests/pos/flowops1.scala @@ -0,0 +1,33 @@ +object Test { + class NotUsed + + trait FO[type +Out, type +Mat] { self => + type Repr <: FO[Mat = self.Mat] { + type Repr = self.Repr + } + def map[T](f: Out => T): Repr[Out = T] = ??? + } + + class Source[type +Out, type +Mat] extends FO[Out, Mat] { + type Repr <: Source[Out, Mat] + } + + class Flow[type -In, type +Out, type +Mat] extends FO[Out, Mat] { + type Repr <: Flow[In, Out, Mat] + } + + implicit class x[O, M, F <: FO](val f: F[Out = O, Mat = M]) extends AnyVal { + def xx(i: Int): f.Repr[Out = O] = f.map(identity) + } + + val s1 = new Source[Int, NotUsed].xx(12) + val s2: Source[Int, NotUsed] = s1 + val f1 = x[Int, NotUsed, Flow[In = Int]](new Flow[Int, Int, NotUsed]).xx(12) + val f2: Flow[Int, Int, NotUsed] = f1 + + + val f3 = x(new Flow[Int, Int, NotUsed]).xx(12) + val f4: Flow[Int, Int, NotUsed] = f3 + val f5 = new Flow[Int, Int, NotUsed].xx(12) + val f6: Flow[Int, Int, NotUsed] = f5 +} From a44082fd7d97e7adb839540d770b5f77b4a150a1 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 19 Mar 2016 19:50:18 +0100 Subject: [PATCH 03/18] Add test for #1181 to pending --- tests/pending/pos/i1181.scala | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 tests/pending/pos/i1181.scala diff --git a/tests/pending/pos/i1181.scala b/tests/pending/pos/i1181.scala new file mode 100644 index 000000000000..057c938d3265 --- /dev/null +++ b/tests/pending/pos/i1181.scala @@ -0,0 +1,12 @@ +object Test { + def foo[M[_]](x: M[Int]) = x + + type Alias[A] = (A, A) + val x: Alias[Int] = (1, 2) + + foo[Alias](x) // ok + foo(x) // ok in scalac but fails in dotty with: + // error: type mismatch: + // found : (Int, Int) + // required: M[Int] +} From e7e81d00fe701245f1bc16aff630a67260665cdd Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 20 Mar 2016 10:58:18 +0100 Subject: [PATCH 04/18] Fix type-shifting problem in vcInlineMethods vcInlineMethods could produce a different type on rewire which led to a -Ycheck failure. We now insert a cast when that happens. Test case: pos/flowops1.scala with -Ycheck:vcInline. --- src/dotty/tools/dotc/transform/VCInlineMethods.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/transform/VCInlineMethods.scala b/src/dotty/tools/dotc/transform/VCInlineMethods.scala index 1c2b015a1fe0..ddd4144171b9 100644 --- a/src/dotty/tools/dotc/transform/VCInlineMethods.scala +++ b/src/dotty/tools/dotc/transform/VCInlineMethods.scala @@ -90,7 +90,7 @@ class VCInlineMethods extends MiniPhaseTransform with IdentityDenotTransformer { tree // The rewiring will be handled by a fully-applied parent node case _ => if (isMethodWithExtension(tree.symbol)) - rewire(tree) + rewire(tree).ensureConforms(tree.tpe) else tree } From 04be0341f8fc7a871914dcab4e359ce6890d36f4 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 21 Mar 2016 13:45:58 +0100 Subject: [PATCH 05/18] Fix error message output. Type was printed in raw form. --- src/dotty/tools/dotc/typer/TypeAssigner.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/typer/TypeAssigner.scala b/src/dotty/tools/dotc/typer/TypeAssigner.scala index 840346536612..821de607e941 100644 --- a/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -416,7 +416,7 @@ trait TypeAssigner { else NoSymbol } if (tparam.exists) RefinedType(tycon, name, argtpt.tpe.toBounds(tparam)) - else errorType(s"$tycon does not have a parameter or abstract type member named $name", arg.pos) + else errorType(i"$tycon does not have a parameter or abstract type member named $name", arg.pos) case _ => errorType(s"named and positional type arguments may not be mixed", arg.pos) } From bbbb6620dabb2a247f74e4cdfbffd178654decba Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 22 Mar 2016 12:26:24 +0100 Subject: [PATCH 06/18] Fix test case. The intent is that Repr implementations should not bind the Out parameter. --- tests/pos/flowops1.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/pos/flowops1.scala b/tests/pos/flowops1.scala index 2acd515afa70..7cf8eb6eff30 100644 --- a/tests/pos/flowops1.scala +++ b/tests/pos/flowops1.scala @@ -8,12 +8,12 @@ object Test { def map[T](f: Out => T): Repr[Out = T] = ??? } - class Source[type +Out, type +Mat] extends FO[Out, Mat] { - type Repr <: Source[Out, Mat] + class Source[type +Out, type +Mat] extends FO[Out, Mat] { self => + type Repr <: Source[Mat = self.Mat] } - class Flow[type -In, type +Out, type +Mat] extends FO[Out, Mat] { - type Repr <: Flow[In, Out, Mat] + class Flow[type -In, type +Out, type +Mat] extends FO[Out, Mat] { self => + type Repr <: Flow[In = self.In, Mat = self.Mat] } implicit class x[O, M, F <: FO](val f: F[Out = O, Mat = M]) extends AnyVal { From 0474de635bee32f6d78726072230c9d572de0d52 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 22 Mar 2016 15:37:25 +0100 Subject: [PATCH 07/18] Fix bug in printing New nodes Explicitly given type parameters were printed twice. --- src/dotty/tools/dotc/printing/RefinedPrinter.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/src/dotty/tools/dotc/printing/RefinedPrinter.scala index e21f12410928..27e42fddff93 100644 --- a/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -345,7 +345,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { "new " ~ { tpt match { case tpt: Template => toTextTemplate(tpt, ofNew = true) - case _ => toTextLocal(tpt) + case _ => toTextLocal(tpt.typeOpt.underlyingClassRef(refinementOK = false)) } } case Pair(l, r) => From d0f9848486be2c8cec61acb94a592e8c9d4f842f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 25 Mar 2016 10:34:54 +0100 Subject: [PATCH 08/18] Add methods for expressing named type params Add methods for expressing what the named type parameters of a class or type are. Also, add a method that widens a type so that is has a specified set of named type parameters. --- .../tools/dotc/core/SymDenotations.scala | 15 +++++ .../tools/dotc/core/TypeApplications.scala | 60 +++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index a83e7726af0d..b808335fe192 100644 --- a/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/src/dotty/tools/dotc/core/SymDenotations.scala @@ -1069,6 +1069,9 @@ object SymDenotations { /** The type parameters of a class symbol, Nil for all other symbols */ def typeParams(implicit ctx: Context): List[TypeSymbol] = Nil + /** The named type parameters declared or inherited by this symbol */ + def namedTypeParams(implicit ctx: Context): Set[TypeSymbol] = Set() + /** The type This(cls), where cls is this class, NoPrefix for all other symbols */ def thisType(implicit ctx: Context): Type = NoPrefix @@ -1202,6 +1205,8 @@ object SymDenotations { /** TODO: Document why caches are supposedly safe to use */ private[this] var myTypeParams: List[TypeSymbol] = _ + private[this] var myNamedTypeParams: Set[TypeSymbol] = _ + /** The type parameters of this class */ override final def typeParams(implicit ctx: Context): List[TypeSymbol] = { def computeTypeParams = { @@ -1214,6 +1219,16 @@ object SymDenotations { myTypeParams } + /** The named type parameters declared or inherited by this class */ + override final def namedTypeParams(implicit ctx: Context): Set[TypeSymbol] = { + def computeNamedTypeParams: Set[TypeSymbol] = + if (ctx.erasedTypes || is(Module)) Set() // fast return for modules to avoid scanning package decls + else memberNames(abstractTypeNameFilter).map(name => + info.member(name).symbol.asType).filter(_.is(TypeParam, butNot = ExpandedName)).toSet + if (myNamedTypeParams == null) myNamedTypeParams = computeNamedTypeParams + myNamedTypeParams + } + override protected[dotc] final def info_=(tp: Type) = { super.info_=(tp) myTypeParams = null // changing the info might change decls, and with it typeParams diff --git a/src/dotty/tools/dotc/core/TypeApplications.scala b/src/dotty/tools/dotc/core/TypeApplications.scala index 8f8a7dbdda6a..817aaee15e69 100644 --- a/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/src/dotty/tools/dotc/core/TypeApplications.scala @@ -273,6 +273,66 @@ class TypeApplications(val self: Type) extends AnyVal { } } + /** The named type parameters declared or inherited by this type. + * These are all uninstantiated named type parameters of this type or one + * of its base types. + */ + final def namedTypeParams(implicit ctx: Context): Set[TypeSymbol] = self match { + case self: ClassInfo => + self.cls.namedTypeParams + case self: RefinedType => + self.parent.namedTypeParams.filterNot(_.name == self.refinedName) + case self: SingletonType => + Set() + case self: TypeProxy => + self.underlying.namedTypeParams + case _ => + Set() + } + + /** The smallest supertype of this type that instantiated none of the named type parameters + * in `params`. That is, for each named type parameter `p` in `params`, either there is + * no type field named `p` in this type, or `p` is a named type parameter of this type. + * The first case is important for the recursive case of AndTypes, because some of their operands might + * be missing the named parameter altogether, but the AndType as a whole can still + * contain it. + */ + final def widenToNamedTypeParams(params: Set[TypeSymbol])(implicit ctx: Context): Type = { + + /** Is widening not needed for `tp`? */ + def isOK(tp: Type) = { + val ownParams = tp.namedTypeParams + def isMissingOrOpen(param: TypeSymbol) = { + val ownParam = tp.nonPrivateMember(param.name).symbol + !ownParam.exists || ownParams.contains(ownParam.asType) + } + params.forall(isMissingOrOpen) + } + + /** Widen type by forming the intersection of its widened parents */ + def widenToParents(tp: Type) = { + val parents = tp.parents.map(p => tp.baseTypeWithArgs(p.symbol)) + ctx.typeComparer.glb(parents.map(_.widenToNamedTypeParams(params))) + } + + if (isOK(self)) self + else self match { + case self @ AppliedType(tycon, args) if !isOK(tycon) => + widenToParents(self) + case self: TypeRef if self.symbol.isClass => + widenToParents(self) + case self: RefinedType => + val parent1 = self.parent.widenToNamedTypeParams(params) + if (params.exists(_.name == self.refinedName)) parent1 + else self.derivedRefinedType(parent1, self.refinedName, self.refinedInfo) + case self: TypeProxy => + self.underlying.widenToNamedTypeParams(params) + case self: AndOrType => + self.derivedAndOrType( + self.tp1.widenToNamedTypeParams(params), self.tp2.widenToNamedTypeParams(params)) + } + } + /** The Lambda trait underlying a type lambda */ def LambdaTrait(implicit ctx: Context): Symbol = self.stripTypeVar match { case RefinedType(parent, tpnme.hkApply) => From c03c8ca068b61bc7cee97dbc9e183bb3e410b839 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 25 Mar 2016 10:37:28 +0100 Subject: [PATCH 09/18] Take named type parameters into account when instantiating variables. When instantiating a type variable, make the instance has the same named type parameters as the upper bound. This is the analogue of kind-correctness for named type parameters. --- src/dotty/tools/dotc/core/Types.scala | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index ffb662490320..c39af1430995 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -2643,7 +2643,7 @@ object Types { // First, solve the constraint. var inst = ctx.typeComparer.approximation(origin, fromBelow) - // Then, approximate by (1.) and (2.) and simplify as follows. + // Then, approximate by (1.) - (3.) and simplify as follows. // 1. If instance is from below and is a singleton type, yet // upper bound is not a singleton type, widen the instance. if (fromBelow && isSingleton(inst) && !isSingleton(upperBound)) @@ -2657,6 +2657,10 @@ object Types { if (fromBelow && isOrType(inst) && isFullyDefined(inst) && !isOrType(upperBound)) inst = inst.approximateUnion + // 3. If instance is from below, and upper bound has open named parameters + // make sure the instance has all named parameters of the bound. + if (fromBelow) inst = inst.widenToNamedTypeParams(this.namedTypeParams) + if (ctx.typerState.isGlobalCommittable) assert(!inst.isInstanceOf[PolyParam], i"bad inst $this := $inst, constr = ${ctx.typerState.constraint}") // If this fails, you might want to turn on Config.debugCheckConstraintsClosed From 506c10613a4f5223472f76226c37cad1701e9610 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 25 Mar 2016 10:45:26 +0100 Subject: [PATCH 10/18] Don't lambda expand and/or types over named type parameters Do it only if at least one of the types has unnamed parameters. This is a fundamental conflict with how we deal with intersections and unions. --- src/dotty/tools/dotc/core/TypeComparer.scala | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index 4e7a4a75db70..833adf008c90 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -1075,15 +1075,29 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { } } - /** `op(tp1, tp2)` unless `tp1` and `tp2` are type-constructors. + /** `op(tp1, tp2)` unless `tp1` and `tp2` are type-constructors with at least + * some unnamed type parameters. * In the latter case, combine `tp1` and `tp2` under a type lambda like this: * * [X1, ..., Xn] -> op(tp1[X1, ..., Xn], tp2[X1, ..., Xn]) + * + * Note: There is a tension between named and positional parameters here, which + * is impossible to resolve completely. Say you have + * + * C[type T], D[type U] + * + * Then do you expand `C & D` to `[T, U] -> C[T, U] & D[T, U]` or not? Under the named + * type parameter interpretation, this would be wrong whereas under the traditional + * higher-kinded interpretation this would be required. The problem arises from + * allowing both interpretations. A possible remedy is to be somehow stricter + * in where we allow which interpretation. */ private def liftIfHK(tp1: Type, tp2: Type, op: (Type, Type) => Type) = { val tparams1 = tp1.typeParams val tparams2 = tp2.typeParams - if (tparams1.isEmpty || tparams2.isEmpty) op(tp1, tp2) + def onlyNamed(tparams: List[TypeSymbol]) = tparams.forall(!_.is(ExpandedName)) + if (tparams1.isEmpty || tparams2.isEmpty || + onlyNamed(tparams1) && onlyNamed(tparams2)) op(tp1, tp2) else if (tparams1.length != tparams2.length) mergeConflict(tp1, tp2) else hkCombine(tp1, tp2, tparams1, tparams2, op) } From 6d2a3d341128eddb99c0f52bca154f9e8b87eb55 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 25 Mar 2016 10:46:07 +0100 Subject: [PATCH 11/18] Test cases --- tests/pos/flowops1.scala | 6 +++++ tests/pos/named-params.scala | 48 ++++++++++++++++++++++++++++++++++-- 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/tests/pos/flowops1.scala b/tests/pos/flowops1.scala index 7cf8eb6eff30..649a9b18c477 100644 --- a/tests/pos/flowops1.scala +++ b/tests/pos/flowops1.scala @@ -20,6 +20,10 @@ object Test { def xx(i: Int): f.Repr[Out = O] = f.map(identity) } + class xalt[O, M, F <: FO](val f: F[Out = O, Mat = M]) extends AnyVal { + def xx(i: Int): FO[Out = O, Mat = M] = ??? + } + val s1 = new Source[Int, NotUsed].xx(12) val s2: Source[Int, NotUsed] = s1 val f1 = x[Int, NotUsed, Flow[In = Int]](new Flow[Int, Int, NotUsed]).xx(12) @@ -30,4 +34,6 @@ object Test { val f4: Flow[Int, Int, NotUsed] = f3 val f5 = new Flow[Int, Int, NotUsed].xx(12) val f6: Flow[Int, Int, NotUsed] = f5 + val f7 = new xalt(new Flow[Int, Int, NotUsed]).xx(12) + val f8: FO[Int, NotUsed] = f7 } diff --git a/tests/pos/named-params.scala b/tests/pos/named-params.scala index bcd64ea4b911..3fab24cd2a36 100644 --- a/tests/pos/named-params.scala +++ b/tests/pos/named-params.scala @@ -29,9 +29,39 @@ object Test { val z3 = d2[E = Int](1) val z4 = d2[V = Int]("AAA") val z5 = d2[E = Int][V = String](1) + +// Testing type inference + + def f[X <: C](x: X[Int, Int]): X[String, String] = ??? + val arg1: C[Int, Int] = ??? + val res1 = f(arg1) + val chk1: C[String, String] = res1 + + class C1[type Elem, type Value](x: Elem) extends C[Elem, Value](x) + class CC extends C1[Int, Int](1) + val arg2: CC = ??? + val res2 = f(arg2) + val chk2: C[String, String] = res2 + + class D1[type Elem, type Value](x: Elem) extends C[Elem, Value](x) + class DD extends D1[Int, Int](2) + val arg3: CC & DD = ??? + val res3 = f(arg3) + val chk3: (C1 & D1) { type Elem = String; type Value = String } = res3 + val arg4: CC | DD = ??? + val res4 = f(arg4) + val chk4: C[String, String] = ??? + + class CX[type Elem](x: Elem) extends C1[Elem, Int](x) + class DX[type Value]() extends D1[Int, Value](2) + val arg5: CX[Int] & DX[Int] = ??? + val res5 = f(arg5) + val chk5: (C1 & D1) { type Elem = String; type Value = String } = res5 + val chk6: C1[String, String] & D1[String, String] = chk5 + val chk7: (C1 & D1) { type Elem = String; type Value = String } = chk6 } -// Adapated from i94-nada +// Adapted from i94-nada, somewhat non-sensical trait Test1 { trait Monad[type Elem] { def unit: Elem @@ -40,7 +70,21 @@ trait Test1 { case class Left[A,B](unit: A) extends Either[A,B] with Monad[A] case class Right[A,B](unit: B) extends Either[A,B] with Monad[B] def flatMap[X,Y,M <: Monad](m: M[Elem = X], f: X => M[Elem = Y]): M[Elem = Y] = f(m.unit) - println(flatMap(Left(1), {x: Int => Left(x)})) + val res = flatMap(Left(1), {x: Int => Left(x)}) + val chk: Either[Int, Nothing] & Monad & Product1[Int] = res +} + +// Adapted from i94-nada, this time with more sense +trait Test2 { + trait Monad[type Elem] { + def unit: Elem + } + sealed abstract class Either[A,B] + case class Left[type Elem, B](unit: Elem) extends Either[Elem,B] with Monad[Elem] + case class Right[A, type Elem](unit: Elem) extends Either[A,Elem] with Monad[Elem] + def flatMap[X,Y,M <: Monad](m: M[Elem = X], f: X => M[Elem = Y]): M[Elem = Y] = f(m.unit) + val res = flatMap(Left(1), {x: Int => Left(x)}) + val chk: Left[Int, Nothing] = res } From de870e26da30e6d2b8f5dcab08a4f3db3334c41c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 25 Mar 2016 10:47:06 +0100 Subject: [PATCH 12/18] Adapt type assignment for AppliedTypeTrees to new named params --- src/dotty/tools/dotc/typer/TypeAssigner.scala | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/dotty/tools/dotc/typer/TypeAssigner.scala b/src/dotty/tools/dotc/typer/TypeAssigner.scala index 821de607e941..995fa43ca7b1 100644 --- a/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -96,7 +96,8 @@ trait TypeAssigner { } case tp @ AppliedType(tycon, args) if toAvoid(tycon) => val base = apply(tycon) - apply(base.appliedTo(tp.baseArgInfos(base.typeSymbol))) + val args = tp.baseArgInfos(base.typeSymbol) + if (base.typeParams.length == args.length) base.appliedTo(args) else base case tp @ RefinedType(parent, name) if variance > 0 => val parent1 = apply(tp.parent) val refinedInfo1 = apply(tp.refinedInfo) @@ -404,16 +405,13 @@ trait TypeAssigner { def assignType(tree: untpd.AppliedTypeTree, tycon: Tree, args: List[Tree])(implicit ctx: Context) = { val tparams = tycon.tpe.typeParams + lazy val ntparams = tycon.tpe.namedTypeParams def refineNamed(tycon: Type, arg: Tree) = arg match { case ast.Trees.NamedArg(name, argtpt) => // Dotty deviation: importing ast.Trees._ and matching on NamedArg gives a cyclic ref error val tparam = tparams.find(_.name == name) match { case Some(tparam) => tparam - case none => - val sym = tycon.member(name).symbol - if (sym.isAbstractType) sym - else if (sym.is(ParamAccessor)) sym.info.dealias.typeSymbol - else NoSymbol + case none => ntparams.find(_.name == name).getOrElse(NoSymbol) } if (tparam.exists) RefinedType(tycon, name, argtpt.tpe.toBounds(tparam)) else errorType(i"$tycon does not have a parameter or abstract type member named $name", arg.pos) From 78d2c9a00e1fcf20491a3f24423b6df40d6f14c1 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 25 Mar 2016 19:58:49 +0100 Subject: [PATCH 13/18] Simplify widenToParents No need to form the glb. --- src/dotty/tools/dotc/core/TypeApplications.scala | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeApplications.scala b/src/dotty/tools/dotc/core/TypeApplications.scala index 817aaee15e69..17573466f496 100644 --- a/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/src/dotty/tools/dotc/core/TypeApplications.scala @@ -295,7 +295,7 @@ class TypeApplications(val self: Type) extends AnyVal { * no type field named `p` in this type, or `p` is a named type parameter of this type. * The first case is important for the recursive case of AndTypes, because some of their operands might * be missing the named parameter altogether, but the AndType as a whole can still - * contain it. + * contain it. */ final def widenToNamedTypeParams(params: Set[TypeSymbol])(implicit ctx: Context): Type = { @@ -311,10 +311,11 @@ class TypeApplications(val self: Type) extends AnyVal { /** Widen type by forming the intersection of its widened parents */ def widenToParents(tp: Type) = { - val parents = tp.parents.map(p => tp.baseTypeWithArgs(p.symbol)) - ctx.typeComparer.glb(parents.map(_.widenToNamedTypeParams(params))) + val parents = tp.parents.map(p => + tp.baseTypeWithArgs(p.symbol).widenToNamedTypeParams(params)) + parents.reduceLeft(ctx.typeComparer.andType(_, _)) } - + if (isOK(self)) self else self match { case self @ AppliedType(tycon, args) if !isOK(tycon) => From 2474c1bd32258a76ccecf31c4dad463448bcf822 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 25 Mar 2016 19:59:15 +0100 Subject: [PATCH 14/18] Fix a case in deskolemize. We should not return a ClassInfo as a value type. --- src/dotty/tools/dotc/core/TypeOps.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/core/TypeOps.scala b/src/dotty/tools/dotc/core/TypeOps.scala index 371be1586891..ff974400dd3d 100644 --- a/src/dotty/tools/dotc/core/TypeOps.scala +++ b/src/dotty/tools/dotc/core/TypeOps.scala @@ -150,7 +150,7 @@ trait TypeOps { this: Context => // TODO: Make standalone object. ctx.log(s"deskolem: $tp: ${tp.info}") tp.info match { case TypeBounds(lo, hi) => approx(lo, hi) - case info => approx(defn.NothingType, info) + case info => NoType } } } From f8aa4b3b56bae1cb0bf0f94a2f2b80b4443aa447 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 25 Mar 2016 21:18:49 +0100 Subject: [PATCH 15/18] Fix documentation of liftIfHK --- src/dotty/tools/dotc/core/TypeComparer.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index 833adf008c90..c5321572cca7 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -1086,7 +1086,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { * * C[type T], D[type U] * - * Then do you expand `C & D` to `[T, U] -> C[T, U] & D[T, U]` or not? Under the named + * Then do you expand `C & D` to `[T] -> C[T] & D[T]` or not? Under the named * type parameter interpretation, this would be wrong whereas under the traditional * higher-kinded interpretation this would be required. The problem arises from * allowing both interpretations. A possible remedy is to be somehow stricter From 78545bbca89bafbee31fc5e4d9818e3173fb7131 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 26 Mar 2016 14:48:38 +0100 Subject: [PATCH 16/18] Add ApproximatingTypeMap class Also: In a TypeMap, the variance of the prefix is unchanged (was: always 0). This brings it in line with TypeAccumulator and the subtyping rules. --- src/dotty/tools/dotc/core/Types.scala | 116 +++++++++++++++++++++----- 1 file changed, 94 insertions(+), 22 deletions(-) diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index c39af1430995..9161ece981af 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -3072,64 +3072,86 @@ object Types { protected var variance = 1 + protected def derivedSelect(tp: NamedType, pre: Type): Type = + tp.derivedSelect(pre) + protected def derivedRefinedType(tp: RefinedType, parent: Type, info: Type): Type = + tp.derivedRefinedType(parent, tp.refinedName, info) + protected def derivedTypeAlias(tp: TypeAlias, alias: Type): Type = + tp.derivedTypeAlias(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 = + tp.derivedSuperType(thistp, supertp) + protected def derivedAndOrType(tp: AndOrType, tp1: Type, tp2: Type): Type = + tp.derivedAndOrType(tp1, tp2) + protected def derivedSkolemType(tp: SkolemType, info: Type): Type = + tp.derivedSkolemType(info) + protected def derivedAnnotatedType(tp: AnnotatedType, underlying: Type, annot: Annotation): Type = + tp.derivedAnnotatedType(underlying, annot) + protected def derivedWildcardType(tp: WildcardType, bounds: Type): Type = + tp.derivedWildcardType(bounds) + protected def derivedClassInfo(tp: ClassInfo, pre: Type): Type = + tp.derivedClassInfo(pre) + protected def derivedJavaArrayType(tp: JavaArrayType, elemtp: Type): Type = + tp.derivedJavaArrayType(elemtp) + protected def derivedMethodType(tp: MethodType, formals: List[Type], restpe: Type): Type = + tp.derivedMethodType(tp.paramNames, formals, restpe) + protected def derivedExprType(tp: ExprType, restpe: Type): Type = + tp.derivedExprType(restpe) + protected def derivedPolyType(tp: PolyType, pbounds: List[TypeBounds], restpe: Type): Type = + tp.derivedPolyType(tp.paramNames, pbounds, restpe) + /** Map this function over given type */ def mapOver(tp: Type): Type = { implicit val ctx: Context = this.ctx // Dotty deviation: implicits need explicit type tp match { case tp: NamedType => if (stopAtStatic && tp.symbol.isStatic) tp - else { - val saved = variance - variance = 0 - val result = tp.derivedSelect(this(tp.prefix)) - variance = saved - result - } + else derivedSelect(tp, this(tp.prefix)) case _: ThisType | _: BoundType | NoPrefix => tp case tp: RefinedType => - tp.derivedRefinedType(this(tp.parent), tp.refinedName, this(tp.refinedInfo)) + derivedRefinedType(tp, this(tp.parent), this(tp.refinedInfo)) case tp: TypeAlias => val saved = variance variance = variance * tp.variance val alias1 = this(tp.alias) variance = saved - tp.derivedTypeAlias(alias1) + derivedTypeAlias(tp, alias1) case tp: TypeBounds => variance = -variance val lo1 = this(tp.lo) variance = -variance - tp.derivedTypeBounds(lo1, this(tp.hi)) + derivedTypeBounds(tp, lo1, this(tp.hi)) case tp: MethodType => def mapOverMethod = { variance = -variance val ptypes1 = tp.paramTypes mapConserve this variance = -variance - tp.derivedMethodType(tp.paramNames, ptypes1, this(tp.resultType)) + derivedMethodType(tp, ptypes1, this(tp.resultType)) } mapOverMethod case tp: ExprType => - tp.derivedExprType(this(tp.resultType)) + derivedExprType(tp, this(tp.resultType)) case tp: PolyType => def mapOverPoly = { variance = -variance val bounds1 = tp.paramBounds.mapConserve(this).asInstanceOf[List[TypeBounds]] variance = -variance - tp.derivedPolyType( - tp.paramNames, bounds1, this(tp.resultType)) + derivedPolyType(tp, bounds1, this(tp.resultType)) } mapOverPoly case tp @ SuperType(thistp, supertp) => - tp.derivedSuperType(this(thistp), this(supertp)) + derivedSuperType(tp, this(thistp), this(supertp)) case tp: LazyRef => LazyRef(() => this(tp.ref)) @@ -3142,20 +3164,21 @@ object Types { if (inst.exists) apply(inst) else tp case tp: AndOrType => - tp.derivedAndOrType(this(tp.tp1), this(tp.tp2)) + derivedAndOrType(tp, this(tp.tp1), this(tp.tp2)) case tp: SkolemType => - tp.derivedSkolemType(this(tp.info)) + derivedSkolemType(tp, this(tp.info)) case tp @ AnnotatedType(underlying, annot) => val underlying1 = this(underlying) - if (underlying1 eq underlying) tp else tp.derivedAnnotatedType(underlying1, mapOver(annot)) + if (underlying1 eq underlying) tp + else derivedAnnotatedType(tp, underlying1, mapOver(annot)) case tp @ WildcardType => - tp.derivedWildcardType(mapOver(tp.optBounds)) + derivedWildcardType(tp, mapOver(tp.optBounds)) case tp: JavaArrayType => - tp.derivedJavaArrayType(this(tp.elemType)) + derivedJavaArrayType(tp, this(tp.elemType)) case tp: ProtoType => tp.map(this) @@ -3182,8 +3205,8 @@ object Types { def mapOver(tree: Tree): Tree = treeTypeMap(tree) /** Can be overridden. By default, only the prefix is mapped. */ - protected def mapClassInfo(tp: ClassInfo): ClassInfo = - tp.derivedClassInfo(this(tp.prefix)) + protected def mapClassInfo(tp: ClassInfo): Type = + derivedClassInfo(tp, this(tp.prefix)) def andThen(f: Type => Type): TypeMap = new TypeMap { override def stopAtStatic = thisMap.stopAtStatic @@ -3209,6 +3232,55 @@ object Types { def apply(tp: Type) = tp } + abstract class ApproximatingTypeMap(implicit ctx: Context) extends TypeMap { thisMap => + def approx(lo: Type = defn.NothingType, hi: Type = defn.AnyType) = + if (variance == 0) NoType + else apply(if (variance < 0) lo else hi) + + override protected def derivedSelect(tp: NamedType, pre: Type) = + if (pre eq tp.prefix) tp + else tp.info match { + case TypeAlias(alias) => apply(alias) // try to heal by following aliases + case _ => + if (pre.exists && !pre.isRef(defn.NothingClass) && variance > 0) tp.derivedSelect(pre) + else tp.info match { + case TypeBounds(lo, hi) => approx(lo, hi) + case _ => approx() + } + } + override protected def derivedRefinedType(tp: RefinedType, parent: Type, info: Type) = + if (parent.exists && info.exists) tp.derivedRefinedType(parent, tp.refinedName, info) + else approx(hi = parent) + override protected def derivedTypeAlias(tp: TypeAlias, alias: Type) = + if (alias.exists) tp.derivedTypeAlias(alias) + else approx(NoType, TypeBounds.empty) + override protected def derivedTypeBounds(tp: TypeBounds, lo: Type, hi: Type) = + if (lo.exists && hi.exists) tp.derivedTypeBounds(lo, hi) + else approx(NoType, + if (lo.exists) TypeBounds.lower(lo) + else if (hi.exists) TypeBounds.upper(hi) + else TypeBounds.empty) + override protected def derivedSuperType(tp: SuperType, thistp: Type, supertp: Type) = + if (thistp.exists && supertp.exists) tp.derivedSuperType(thistp, supertp) + else NoType + override protected def derivedAndOrType(tp: AndOrType, tp1: Type, tp2: Type) = + if (tp1.exists && tp2.exists) tp.derivedAndOrType(tp1, tp2) + else if (tp.isAnd) approx(hi = tp1 & tp2) // if one of tp1d, tp2d exists, it is the result of tp1d & tp2d + else approx(lo = tp1 & tp2) + override protected def derivedSkolemType(tp: SkolemType, info: Type) = + if (info.exists) tp.derivedSkolemType(info) + else NoType + override protected def derivedAnnotatedType(tp: AnnotatedType, underlying: Type, annot: Annotation) = + if (underlying.exists) tp.derivedAnnotatedType(underlying, annot) + else NoType + override protected def derivedWildcardType(tp: WildcardType, bounds: Type) = + if (bounds.exists) tp.derivedWildcardType(bounds) + else WildcardType + override protected def derivedClassInfo(tp: ClassInfo, pre: Type): Type = + if (pre.exists) tp.derivedClassInfo(pre) + else NoType + } + // ----- TypeAccumulators ---------------------------------------------------- abstract class TypeAccumulator[T](implicit protected val ctx: Context) extends ((T, Type) => T) { From d89767858c4e3a7ad37d9a98ea1e87f58bd0eb02 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 26 Mar 2016 14:49:17 +0100 Subject: [PATCH 17/18] Base deskolemize on ApproximatingTypeMap --- src/dotty/tools/dotc/core/TypeOps.scala | 97 +++---------------------- 1 file changed, 9 insertions(+), 88 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeOps.scala b/src/dotty/tools/dotc/core/TypeOps.scala index ff974400dd3d..1288c0b23bd6 100644 --- a/src/dotty/tools/dotc/core/TypeOps.scala +++ b/src/dotty/tools/dotc/core/TypeOps.scala @@ -122,100 +122,21 @@ trait TypeOps { this: Context => // TODO: Make standalone object. def currentVariance = variance } - /** Approximate a type `tp` with a type that does not contain skolem types. - */ - final def deskolemize(tp: Type): Type = deskolemize(tp, 1, Set()) - - private def deskolemize(tp: Type, variance: Int, seen: Set[SkolemType]): Type = { - def approx(lo: Type = defn.NothingType, hi: Type = defn.AnyType, newSeen: Set[SkolemType] = seen) = - if (variance == 0) NoType - else deskolemize(if (variance < 0) lo else hi, variance, newSeen) - tp match { + /** Approximate a type `tp` with a type that does not contain skolem types. */ + object deskolemize extends ApproximatingTypeMap { + private var seen: Set[SkolemType] = Set() + def apply(tp: Type) = tp match { case tp: SkolemType => if (seen contains tp) NoType - else approx(hi = tp.info, newSeen = seen + tp) - case tp: NamedType => - val sym = tp.symbol - if (sym.isStatic) tp else { - val pre1 = deskolemize(tp.prefix, variance, seen) - if (pre1 eq tp.prefix) tp - else { - val d = tp.prefix.member(tp.name) - d.info match { - case TypeAlias(alias) => deskolemize(alias, variance, seen) - case _ => - if (pre1.exists && !pre1.isRef(defn.NothingClass)) tp.derivedSelect(pre1) - else { - ctx.log(s"deskolem: $tp: ${tp.info}") - tp.info match { - case TypeBounds(lo, hi) => approx(lo, hi) - case info => NoType - } - } - } - } + val saved = seen + seen += tp + try approx(hi = tp.info) + finally seen = saved } - case _: ThisType | _: BoundType | _: SuperType | NoType | NoPrefix => - tp - case tp: RefinedType => - val parent1 = deskolemize(tp.parent, variance, seen) - if (parent1.exists) { - val refinedInfo1 = deskolemize(tp.refinedInfo, variance, seen) - if (refinedInfo1.exists) - tp.derivedRefinedType(parent1, tp.refinedName, refinedInfo1) - else - approx(hi = parent1) - } - else approx() - case tp: TypeAlias => - val alias1 = deskolemize(tp.alias, variance * tp.variance, seen) - if (alias1.exists) tp.derivedTypeAlias(alias1) - else approx(hi = TypeBounds.empty) - case tp: TypeBounds => - val lo1 = deskolemize(tp.lo, -variance, seen) - val hi1 = deskolemize(tp.hi, variance, seen) - if (lo1.exists && hi1.exists) tp.derivedTypeBounds(lo1, hi1) - else approx(hi = - if (lo1.exists) TypeBounds.lower(lo1) - else if (hi1.exists) TypeBounds.upper(hi1) - else TypeBounds.empty) - case tp: ClassInfo => - val pre1 = deskolemize(tp.prefix, variance, seen) - if (pre1.exists) tp.derivedClassInfo(pre1) - else NoType - case tp: AndOrType => - val tp1d = deskolemize(tp.tp1, variance, seen) - val tp2d = deskolemize(tp.tp2, variance, seen) - if (tp1d.exists && tp2d.exists) - tp.derivedAndOrType(tp1d, tp2d) - else if (tp.isAnd) - approx(hi = tp1d & tp2d) // if one of tp1d, tp2d exists, it is the result of tp1d & tp2d - else - approx(lo = tp1d & tp2d) - case tp: WildcardType => - val bounds1 = deskolemize(tp.optBounds, variance, seen) - if (bounds1.exists) tp.derivedWildcardType(bounds1) - else WildcardType case _ => if (tp.isInstanceOf[MethodicType]) assert(variance != 0, tp) - deskolemizeMap.mapOver(tp, variance, seen) - } - } - - object deskolemizeMap extends TypeMap { - private var seen: Set[SkolemType] = _ - def apply(tp: Type) = deskolemize(tp, variance, seen) - def mapOver(tp: Type, variance: Int, seen: Set[SkolemType]) = { - val savedVariance = this.variance - val savedSeen = this.seen - this.variance = variance - this.seen = seen - try super.mapOver(tp) - finally { - this.variance = savedVariance - this.seen = savedSeen - } + mapOver(tp) } } From f675ad9507089f8b912357fab86740653c1b8789 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 30 Mar 2016 12:16:03 +0200 Subject: [PATCH 18/18] Domain checking for named type parameters Now verifies that the named type parameters of an overriding type or class are the same as the named type parameters of an overridden type. --- src/dotty/tools/dotc/typer/RefChecks.scala | 5 +++++ tests/neg/named-params.scala | 3 +++ tests/pos/CollectionStrawMan3.scala | 20 ++++++++++---------- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/dotty/tools/dotc/typer/RefChecks.scala b/src/dotty/tools/dotc/typer/RefChecks.scala index afbb43faf37d..37c2fe48f90a 100644 --- a/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/src/dotty/tools/dotc/typer/RefChecks.scala @@ -246,6 +246,8 @@ object RefChecks { isDefaultGetter(member.name) || // default getters are not checked for compatibility memberTp.overrides(otherTp) + def domain(sym: Symbol): Set[Name] = sym.info.namedTypeParams.map(_.name) + //Console.println(infoString(member) + " overrides " + infoString(other) + " in " + clazz);//DEBUG // return if we already checked this combination elsewhere @@ -342,6 +344,9 @@ object RefChecks { overrideError("cannot be used here - only term macros can override term macros") } else if (!compatibleTypes) { overrideError("has incompatible type" + err.whyNoMatchStr(memberTp, otherTp)) + } else if (member.isType && domain(member) != domain(other)) { + overrideError("has different named type parameters: "+ + i"[${domain(member).toList}%, %] instead of [${domain(other).toList}%, %]") } else { checkOverrideDeprecated() } diff --git a/tests/neg/named-params.scala b/tests/neg/named-params.scala index 9ef4ed066d69..5a2375b1599b 100644 --- a/tests/neg/named-params.scala +++ b/tests/neg/named-params.scala @@ -32,3 +32,6 @@ object Test { val z5 = d2[Elem = Int][Value = String](1) //error // error } + + + diff --git a/tests/pos/CollectionStrawMan3.scala b/tests/pos/CollectionStrawMan3.scala index 47d3b52d65ab..c21a73f0021f 100644 --- a/tests/pos/CollectionStrawMan3.scala +++ b/tests/pos/CollectionStrawMan3.scala @@ -114,13 +114,13 @@ object CollectionStrawMan1 { /* --------- Concrete collection types ------------------------------- */ /** Concrete collection type: List */ - sealed trait List[+A] extends Seq[A] with FromIterator[List] { + sealed trait List[type +Elem] extends Seq[Elem] with FromIterator[List] { def isEmpty: Boolean - def head: A - def tail: List[A] - def iterator = new ListIterator[A](this) + def head: Elem + def tail: List[Elem] + def iterator = new ListIterator[Elem](this) def fromIterator[B](it: Iterator[B]): List[B] = List.fromIterator(it) - def apply(i: Int): A = { + def apply(i: Int): Elem = { require(!isEmpty) if (i == 0) head else tail.apply(i - 1) } @@ -155,17 +155,17 @@ object CollectionStrawMan1 { } /** Concrete collection type: ArrayBuffer */ - class ArrayBuffer[A] private (initElems: Array[AnyRef], initLength: Int) extends Seq[A] with FromIterator[ArrayBuffer] { + class ArrayBuffer[type Elem] private (initElems: Array[AnyRef], initLength: Int) extends Seq[Elem] with FromIterator[ArrayBuffer] { def this() = this(new Array[AnyRef](16), 0) private var elems: Array[AnyRef] = initElems private var start = 0 private var limit = initLength - def apply(i: Int) = elems(start + i).asInstanceOf[A] + def apply(i: Int) = elems(start + i).asInstanceOf[Elem] def length = limit - start - def iterator = new ArrayBufferIterator[A](elems, start, length) + def iterator = new ArrayBufferIterator[Elem](elems, start, length) def fromIterator[B](it: Iterator[B]): ArrayBuffer[B] = ArrayBuffer.fromIterator(it) - def +=(elem: A): this.type = { + def +=(elem: Elem): this.type = { if (limit == elems.length) { if (start > 0) { Array.copy(elems, start, elems, 0, length) @@ -213,7 +213,7 @@ object CollectionStrawMan1 { } /** Concrete collection type: View */ - class View[+A](it: => Iterator[A]) extends CanIterate[A] { + class View[type +Elem](it: => Iterator[Elem]) extends CanIterate[Elem] { def iterator = it }