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..17573466f496 100644 --- a/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/src/dotty/tools/dotc/core/TypeApplications.scala @@ -273,6 +273,67 @@ 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).widenToNamedTypeParams(params)) + parents.reduceLeft(ctx.typeComparer.andType(_, _)) + } + + 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) => diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index 4e7a4a75db70..c5321572cca7 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] -> 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 + * 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) } diff --git a/src/dotty/tools/dotc/core/TypeOps.scala b/src/dotty/tools/dotc/core/TypeOps.scala index 371be1586891..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 => approx(defn.NothingType, info) - } - } - } - } + 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) } } diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 3801f191449e..9161ece981af 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. @@ -2651,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)) @@ -2665,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 @@ -3076,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)) @@ -3146,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) @@ -3186,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 @@ -3213,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) { 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) => 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 } 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/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/src/dotty/tools/dotc/typer/TypeAssigner.scala b/src/dotty/tools/dotc/typer/TypeAssigner.scala index 84951fd2b3e9..995fa43ca7b1 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,18 @@ trait TypeAssigner { case _ => mapOver(tp) } + case tp @ AppliedType(tycon, args) if toAvoid(tycon) => + val base = apply(tycon) + 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 => - // 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) @@ -413,19 +405,16 @@ 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(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) } 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/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] +} 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 } 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 +} diff --git a/tests/pos/flowops1.scala b/tests/pos/flowops1.scala new file mode 100644 index 000000000000..649a9b18c477 --- /dev/null +++ b/tests/pos/flowops1.scala @@ -0,0 +1,39 @@ +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] { self => + type Repr <: Source[Mat = self.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 { + 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) + 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 + 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 }