From a3f1e567753d2faa24cb012fbb9a34a6bdbc19b4 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 11 Jul 2015 12:21:33 +0200 Subject: [PATCH 01/42] Clean up of logic in typeDefSig --- src/dotty/tools/dotc/typer/Namer.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/dotty/tools/dotc/typer/Namer.scala b/src/dotty/tools/dotc/typer/Namer.scala index cdce11f224b9..f051c7ba202d 100644 --- a/src/dotty/tools/dotc/typer/Namer.scala +++ b/src/dotty/tools/dotc/typer/Namer.scala @@ -785,10 +785,10 @@ class Namer { typer: Typer => // // The scheme critically relies on an implementation detail of isRef, which // inspects a TypeRef's info, instead of simply dealiasing alias types. - val rhsType = typedAheadType(tdef.rhs).tpe + val rhsType = abstracted(typedAheadType(tdef.rhs).tpe) val unsafeInfo = rhsType match { - case _: TypeBounds => abstracted(rhsType).asInstanceOf[TypeBounds] - case _ => TypeAlias(abstracted(rhsType), if (sym is Local) sym.variance else 0) + case bounds: TypeBounds => bounds + case alias => TypeAlias(alias, if (sym is Local) sym.variance else 0) } sym.info = NoCompleter checkNonCyclic(sym, unsafeInfo, reportErrors = true) From b07727bade67a496400580f683c5b1950534918f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 12 Jul 2015 14:22:10 +0200 Subject: [PATCH 02/42] Avoid cyclic references in containsRefinedThis Avoid forcing info if the reference goes to a class. This avoided a CyclicReference when reading Scala's standard library form pos/collections when fiddling with the hk logic. --- src/dotty/tools/dotc/core/TypeApplications.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeApplications.scala b/src/dotty/tools/dotc/core/TypeApplications.scala index f466cee7705a..418433f76cdc 100644 --- a/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/src/dotty/tools/dotc/core/TypeApplications.scala @@ -392,9 +392,10 @@ class TypeApplications(val self: Type) extends AnyVal { case RefinedThis(tp) => tp eq target case tp: NamedType => - tp.info match { + if (tp.symbol.isClass) !tp.symbol.isStatic && recur(tp.prefix) + else tp.info match { case TypeAlias(alias) => recur(alias) - case _ => !tp.symbol.isStatic && recur(tp.prefix) + case _ => recur(tp.prefix) } case tp: RefinedType => recur(tp.refinedInfo) || recur(tp.parent) From 2b75c956ecfdfa68b8f1c617cafe9756f30b932a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 14 Jul 2015 11:37:24 +0200 Subject: [PATCH 03/42] Eta expand type arguments corresponding to lambdas In Namer, eta expand any type argument that corresponds to a higher-kinded type parameter. Also, check that all type parameter lists are fully applied. --- src/dotty/tools/dotc/typer/Namer.scala | 26 +++++++++++++++++++++++++- tests/pos/partialApplications.scala | 4 ++-- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/dotty/tools/dotc/typer/Namer.scala b/src/dotty/tools/dotc/typer/Namer.scala index f051c7ba202d..c396d6f44b43 100644 --- a/src/dotty/tools/dotc/typer/Namer.scala +++ b/src/dotty/tools/dotc/typer/Namer.scala @@ -791,6 +791,30 @@ class Namer { typer: Typer => case alias => TypeAlias(alias, if (sym is Local) sym.variance else 0) } sym.info = NoCompleter - checkNonCyclic(sym, unsafeInfo, reportErrors = true) + sym.info = checkNonCyclic(sym, unsafeInfo, reportErrors = true) + etaExpandArgs.apply(sym.info) + } + + /** Eta expand all class types C appearing as arguments to a higher-kinded + * type parameter to type lambdas, e.g. [HK0] => C[HK0] + */ + def etaExpandArgs(implicit ctx: Context) = new TypeMap { + def etaExpandArg(tp: Type, tparam: Symbol): Type = + if (tparam.info.isLambda && tp.typeSymbol.isClass && tp.isLambda) tp.EtaExpand + else tp + def apply(tp: Type) = tp match { + case tp: RefinedType => + val args = tp.argInfos(interpolate = false).mapconserve(this) + if (args.nonEmpty) { + val tycon = tp.withoutArgs(args) + val tparams = tycon.typeParams + assert(args.length == tparams.length, + i"lengths differ in $tp: args = $args%, %, type params of $tycon = $tparams%, %") + this(tycon).appliedTo(args.zipWithConserve(tparams)(etaExpandArg)) + } + else mapOver(tp) + case _ => + mapOver(tp) + } } } diff --git a/tests/pos/partialApplications.scala b/tests/pos/partialApplications.scala index b68c4b945152..c1df1dee2035 100644 --- a/tests/pos/partialApplications.scala +++ b/tests/pos/partialApplications.scala @@ -1,8 +1,8 @@ object Test { - type Histogram = Map[_, Int] + type Histogram[X] = Map[X, Int] - type StringlyHistogram = Histogram[_ >: String] + type StringlyHistogram[X >: String] = Histogram[X] val xs: Histogram[String] = Map[String, Int]() From c11e20d6cb8649f72cab5c994e8b75bdd2091f1b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 14 Jul 2015 12:35:16 +0200 Subject: [PATCH 04/42] Check argument lengths in typedAppliedTypeTree With the hk-types schem changed, we need to make sure that actual and formal argument lists of parameterized types have the same length. --- src/dotty/tools/dotc/typer/Typer.scala | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index 33ec156a1d72..284d71ce5dd0 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -827,8 +827,18 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit val argPts = if (ctx.mode is Mode.Pattern) tpt1.tpe.typeParams.map(_.info) else tree.args.map(_ => WildcardType) - val args1 = tree.args.zipWithConserve(argPts)(typed(_, _)).asInstanceOf[List[Tree]] - // check that arguments conform to bounds is done in phase FirstTransform + val tparams = tpt1.tpe.typeParams + var args = tree.args + if (tparams.isEmpty) { + ctx.error(d"${tpt1.tpe} does not take type parameters") + tpt1 + } + else if (args.length != tparams.length) { + ctx.error(d"wrong number of type arguments for ${tpt1.tpe}, should be ${tparams.length}", tree.pos) + args = args.take(tparams.length) + } + val args1 = args.zipWithConserve(argPts)(typed(_, _)).asInstanceOf[List[Tree]] + // check that arguments conform to bounds is done in phase PostTyper assignType(cpy.AppliedTypeTree(tree)(tpt1, args1), tpt1, args1) } From d02d38317a89abb8f4d55ae054f6e1b1a0af344b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 14 Jul 2015 12:38:05 +0200 Subject: [PATCH 05/42] Better diagnostics for failed sigName calls. It used to be "assertion error: NoType" whenever sigName git a projection of a missing member. Now we find out about what type was projected. --- src/dotty/tools/dotc/core/TypeErasure.scala | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/core/TypeErasure.scala b/src/dotty/tools/dotc/core/TypeErasure.scala index 0ef31015c2de..262fff2ed04b 100644 --- a/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/src/dotty/tools/dotc/core/TypeErasure.scala @@ -441,7 +441,11 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean case tp: TypeRef => if (!tp.denot.exists) throw new MissingType(tp.prefix, tp.name) val sym = tp.symbol - if (!sym.isClass) sigName(tp.info) + if (!sym.isClass) { + val info = tp.info + if (!info.exists) assert(false, "undefined: $tp with symbol $sym") + sigName(info) + } else if (isDerivedValueClass(sym)) sigName(eraseDerivedValueClassRef(tp)) else normalizeClass(sym.asClass).fullName.asTypeName case defn.ArrayType(elem) => From 6b5ff0346ac7786d46ace9dc8e93a387a02dc3e3 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 14 Jul 2015 12:49:09 +0200 Subject: [PATCH 06/42] Black hole detection for LazyRefs Now catches attempts to recursively force a LazyRef type that's in train of being evaluated. --- src/dotty/tools/dotc/core/Types.scala | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 4d6a7e22b719..9417a6581993 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -1808,7 +1808,16 @@ object Types { } case class LazyRef(refFn: () => Type) extends UncachedProxyType with ValueType { - lazy val ref = refFn() + private var myRef: Type = null + private var computed = false + lazy val ref = { + if (computed) assert(myRef != null) + else { + computed = true + myRef = refFn() + } + myRef + } override def underlying(implicit ctx: Context) = ref override def toString = s"LazyRef($ref)" override def equals(other: Any) = other match { From fc8e20a252650fb972fd0c74bff4f2d04c3fb8c8 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 14 Jul 2015 12:50:39 +0200 Subject: [PATCH 07/42] Turn assertion into a test in etaExpandArgs. Erreneous programs could have a difference in lengths between type parameters and type args, but this is tested anyway in Typer. --- src/dotty/tools/dotc/typer/Namer.scala | 28 ++++++++++++++------------ 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/dotty/tools/dotc/typer/Namer.scala b/src/dotty/tools/dotc/typer/Namer.scala index c396d6f44b43..f6a0d2cd401c 100644 --- a/src/dotty/tools/dotc/typer/Namer.scala +++ b/src/dotty/tools/dotc/typer/Namer.scala @@ -802,19 +802,21 @@ class Namer { typer: Typer => def etaExpandArg(tp: Type, tparam: Symbol): Type = if (tparam.info.isLambda && tp.typeSymbol.isClass && tp.isLambda) tp.EtaExpand else tp - def apply(tp: Type) = tp match { - case tp: RefinedType => - val args = tp.argInfos(interpolate = false).mapconserve(this) - if (args.nonEmpty) { - val tycon = tp.withoutArgs(args) - val tparams = tycon.typeParams - assert(args.length == tparams.length, - i"lengths differ in $tp: args = $args%, %, type params of $tycon = $tparams%, %") - this(tycon).appliedTo(args.zipWithConserve(tparams)(etaExpandArg)) - } - else mapOver(tp) - case _ => - mapOver(tp) + def apply(tp: Type): Type = { + tp match { + case tp: RefinedType => + val args = tp.argInfos(interpolate = false).mapconserve(this) + if (args.nonEmpty) { + val tycon = tp.withoutArgs(args) + val tparams = tycon.typeParams + if (args.length == tparams.length) { // if lengths differ, problem is caught in typedTypeApply + val args1 = args.zipWithConserve(tparams)(etaExpandArg) + if (args1 ne args) return this(tycon).appliedTo(args1) + } + } + case _ => + } + mapOver(tp) } } } From ce831f8f751d18504820d98387cbe6992f0297c3 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 14 Jul 2015 17:37:52 +0200 Subject: [PATCH 08/42] Fix argument eta expansion ... and move to TypeApplications. isLambda test was the wrong way before. --- src/dotty/tools/dotc/core/TypeApplications.scala | 3 +++ src/dotty/tools/dotc/typer/Namer.scala | 5 +---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeApplications.scala b/src/dotty/tools/dotc/core/TypeApplications.scala index 418433f76cdc..f581f7955205 100644 --- a/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/src/dotty/tools/dotc/core/TypeApplications.scala @@ -516,6 +516,9 @@ class TypeApplications(val self: Type) extends AnyVal { self.appliedTo(tparams map (_.typeRef)).LambdaAbstract(tparams) } + def EtaExpandIfLambda(bound: Type)(implicit ctx: Context): Type = + if (bound.isLambda && self.typeSymbol.isClass && !isLambda) EtaExpand else self + /** Test whether this type has a base type of the form `B[T1, ..., Bn]` where * the type parameters of `B` match one-by-one the variances of `tparams`, * and where the lambda abstracted type diff --git a/src/dotty/tools/dotc/typer/Namer.scala b/src/dotty/tools/dotc/typer/Namer.scala index f6a0d2cd401c..a4af9edbdaab 100644 --- a/src/dotty/tools/dotc/typer/Namer.scala +++ b/src/dotty/tools/dotc/typer/Namer.scala @@ -799,9 +799,6 @@ class Namer { typer: Typer => * type parameter to type lambdas, e.g. [HK0] => C[HK0] */ def etaExpandArgs(implicit ctx: Context) = new TypeMap { - def etaExpandArg(tp: Type, tparam: Symbol): Type = - if (tparam.info.isLambda && tp.typeSymbol.isClass && tp.isLambda) tp.EtaExpand - else tp def apply(tp: Type): Type = { tp match { case tp: RefinedType => @@ -810,7 +807,7 @@ class Namer { typer: Typer => val tycon = tp.withoutArgs(args) val tparams = tycon.typeParams if (args.length == tparams.length) { // if lengths differ, problem is caught in typedTypeApply - val args1 = args.zipWithConserve(tparams)(etaExpandArg) + val args1 = args.zipWithConserve(tparams)((arg, tparam) => arg.EtaExpandIfLambda(tparam.info)) if (args1 ne args) return this(tycon).appliedTo(args1) } } From 6c992f0de4cf9841bec24590a97024f59524f4c9 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 14 Jul 2015 19:53:05 +0200 Subject: [PATCH 09/42] Strenghten condition in EtaExpandIfLambda Without the additional `typeParams.nonEmpty` condition we got a crash in t1439.scala --- src/dotty/tools/dotc/core/TypeApplications.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/core/TypeApplications.scala b/src/dotty/tools/dotc/core/TypeApplications.scala index f581f7955205..e588d3d9b361 100644 --- a/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/src/dotty/tools/dotc/core/TypeApplications.scala @@ -516,8 +516,10 @@ class TypeApplications(val self: Type) extends AnyVal { self.appliedTo(tparams map (_.typeRef)).LambdaAbstract(tparams) } + /** Eta expand if `bound` is a type lambda */ def EtaExpandIfLambda(bound: Type)(implicit ctx: Context): Type = - if (bound.isLambda && self.typeSymbol.isClass && !isLambda) EtaExpand else self + if (bound.isLambda && self.typeSymbol.isClass && typeParams.nonEmpty && !isLambda) EtaExpand + else self /** Test whether this type has a base type of the form `B[T1, ..., Bn]` where * the type parameters of `B` match one-by-one the variances of `tparams`, From 43377ad5c336c959f01af1ee49424f2810ae8417 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 14 Jul 2015 19:54:18 +0200 Subject: [PATCH 10/42] Adapt arguments in all type applications Previously, we did this only in applications in rhs of type definitions. Need to do it everywhere. --- src/dotty/tools/dotc/typer/Applications.scala | 7 +++---- src/dotty/tools/dotc/typer/Typer.scala | 9 +++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/dotty/tools/dotc/typer/Applications.scala b/src/dotty/tools/dotc/typer/Applications.scala index c45db4ccc827..f90f44ae03f5 100644 --- a/src/dotty/tools/dotc/typer/Applications.scala +++ b/src/dotty/tools/dotc/typer/Applications.scala @@ -604,10 +604,6 @@ trait Applications extends Compatibility { self: Typer => val typedFn = typedExpr(tree.fun, PolyProto(typedArgs.tpes, pt)) typedFn.tpe.widen match { case pt: PolyType => - def adaptTypeArg(tree: tpd.Tree, bound: Type): tpd.Tree = - if (bound.isLambda && !tree.tpe.isLambda && tree.tpe.typeParams.nonEmpty) - tree.withType(tree.tpe.EtaExpand) - else tree if (typedArgs.length <= pt.paramBounds.length) typedArgs = typedArgs.zipWithConserve(pt.paramBounds)(adaptTypeArg) checkBounds(typedArgs, pt) @@ -616,6 +612,9 @@ trait Applications extends Compatibility { self: Typer => assignType(cpy.TypeApply(tree)(typedFn, typedArgs), typedFn, typedArgs) } + def adaptTypeArg(tree: tpd.Tree, bound: Type)(implicit ctx: Context): tpd.Tree = + tree.withType(tree.tpe.EtaExpandIfLambda(bound)) + /** Rewrite `new Array[T](....)` trees to calls of newXYZArray methods. */ def convertNewArray(tree: tpd.Tree)(implicit ctx: Context): tpd.Tree = tree match { case Apply(TypeApply(tycon, targ :: Nil), args) if tycon.symbol == defn.ArrayConstructor => diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index 284d71ce5dd0..593250cea865 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -824,9 +824,6 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit def typedAppliedTypeTree(tree: untpd.AppliedTypeTree)(implicit ctx: Context): AppliedTypeTree = track("typedAppliedTypeTree") { val tpt1 = typed(tree.tpt) - val argPts = - if (ctx.mode is Mode.Pattern) tpt1.tpe.typeParams.map(_.info) - else tree.args.map(_ => WildcardType) val tparams = tpt1.tpe.typeParams var args = tree.args if (tparams.isEmpty) { @@ -837,7 +834,11 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit ctx.error(d"wrong number of type arguments for ${tpt1.tpe}, should be ${tparams.length}", tree.pos) args = args.take(tparams.length) } - val args1 = args.zipWithConserve(argPts)(typed(_, _)).asInstanceOf[List[Tree]] + val argPts = + if (ctx.mode is Mode.Pattern) tpt1.tpe.typeParams.map(_.info) + else tree.args.map(_ => WildcardType) + def typedArg(arg: untpd.Tree, pt: Type) = adaptTypeArg(typed(arg, pt), pt) + val args1 = args.zipWithConserve(argPts)(typedArg(_, _)).asInstanceOf[List[Tree]] // check that arguments conform to bounds is done in phase PostTyper assignType(cpy.AppliedTypeTree(tree)(tpt1, args1), tpt1, args1) } From 35adb64085a16ea3c390d4e3d3fd341d037eadde Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 15 Jul 2015 11:43:39 +0200 Subject: [PATCH 11/42] Avoid generating companion-methods for non-class types. This would lead to a crash. Example is in Predef: object Pair type Pair --- .../tools/dotc/core/unpickleScala2/Scala2Unpickler.scala | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala b/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala index 53e8478fa66f..0dca4154b92a 100644 --- a/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala +++ b/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala @@ -126,9 +126,11 @@ object Scala2Unpickler { val companionClassMethod = ctx.synthesizeCompanionMethod(nme.COMPANION_CLASS_METHOD, claz, module) if (companionClassMethod.exists) companionClassMethod.entered - val companionModuleMethod = ctx.synthesizeCompanionMethod(nme.COMPANION_MODULE_METHOD, module, claz) - if (companionModuleMethod.exists) - companionModuleMethod.entered + if (claz.isClass) { + val companionModuleMethod = ctx.synthesizeCompanionMethod(nme.COMPANION_MODULE_METHOD, module, claz) + if (companionModuleMethod.exists) + companionModuleMethod.entered + } } if (denot.flagsUNSAFE is Module) { From 38ac13a17ac4a1c077d6b6efc87e24decf3704cc Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 16 Jul 2015 12:14:33 +0200 Subject: [PATCH 12/42] Add EtaReduce method. EtaReduce will be used to keep applications on eta expanded methods small. --- .../tools/dotc/core/TypeApplications.scala | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/dotty/tools/dotc/core/TypeApplications.scala b/src/dotty/tools/dotc/core/TypeApplications.scala index e588d3d9b361..3d82628bccb4 100644 --- a/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/src/dotty/tools/dotc/core/TypeApplications.scala @@ -514,6 +514,7 @@ class TypeApplications(val self: Type) extends AnyVal { def EtaExpand(implicit ctx: Context): Type = { val tparams = typeParams self.appliedTo(tparams map (_.typeRef)).LambdaAbstract(tparams) + //.ensuring(res => res.EtaReduce =:= self, s"res = $res, core = ${res.EtaReduce}, self = $self, hc = ${res.hashCode}") } /** Eta expand if `bound` is a type lambda */ @@ -521,6 +522,38 @@ class TypeApplications(val self: Type) extends AnyVal { if (bound.isLambda && self.typeSymbol.isClass && typeParams.nonEmpty && !isLambda) EtaExpand else self + /** If `self` is an eta expansion of type T, return T, otherwise NoType */ + def EtaReduce(implicit ctx: Context): Type = { + def etaCore(tp: Type, tparams: List[Symbol]): Type = tparams match { + case Nil => tp + case tparam :: otherParams => + tp match { + case tp: RefinedType => + tp.refinedInfo match { + case TypeAlias(TypeRef(RefinedThis(rt), rname)) // TODO: Drop once hk applications have been updated + if (rname == tparam.name) && (rt eq self) => + etaCore(tp.parent, otherParams) + case TypeRef(TypeAlias(TypeRef(RefinedThis(rt), rname)), tpnme.Apply) + if (rname == tparam.name) && (rt eq self) => + etaCore(tp.parent, otherParams) + case _ => + NoType + } + case _ => + NoType + } + } + self match { + case self @ RefinedType(parent, tpnme.Apply) => + val lc = parent.LambdaClass(forcing = false) + self.refinedInfo match { + case TypeAlias(alias) if lc.exists => etaCore(alias, lc.typeParams.reverse) + case _ => NoType + } + case _ => NoType + } + } + /** Test whether this type has a base type of the form `B[T1, ..., Bn]` where * the type parameters of `B` match one-by-one the variances of `tparams`, * and where the lambda abstracted type From 8029f6540e89ace138330b0b1a124faf3cb6006e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 17 Jul 2015 12:20:54 +0200 Subject: [PATCH 13/42] Add comment. --- src/dotty/tools/dotc/typer/Namer.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/typer/Namer.scala b/src/dotty/tools/dotc/typer/Namer.scala index a4af9edbdaab..b86acf4d654f 100644 --- a/src/dotty/tools/dotc/typer/Namer.scala +++ b/src/dotty/tools/dotc/typer/Namer.scala @@ -796,7 +796,10 @@ class Namer { typer: Typer => } /** Eta expand all class types C appearing as arguments to a higher-kinded - * type parameter to type lambdas, e.g. [HK0] => C[HK0] + * type parameter to type lambdas, e.g. [HK0] => C[HK0]. This is necessary + * because in `typedAppliedTypeTree` we might ahve missed some eta expansions + * of arguments in F-bounds, because the recursive type was initialized with + * TypeBounds.empty. */ def etaExpandArgs(implicit ctx: Context) = new TypeMap { def apply(tp: Type): Type = { From c5958aad63314041f20a564056f5371097493a35 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 17 Jul 2015 12:21:18 +0200 Subject: [PATCH 14/42] Add a test that logs the classpath to pinpoint setup failures. --- test/dotc/tests.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index fbf26044cf59..9f731277d924 100644 --- a/test/dotc/tests.scala +++ b/test/dotc/tests.scala @@ -51,6 +51,8 @@ class tests extends CompilerTest { //@Test def pickle_core = compileDir(dotcDir, "core", testPickling, xerrors = 2) // two spurious comparison errors in Types and TypeOps + @Test def pos_arraycopy = + compileFile(runDir, "arraycopy", List("-Ylog-classpath")) @Test def pos_t2168_pat = compileFile(posDir, "t2168", twice) @Test def pos_erasure = compileFile(posDir, "erasure", twice) @Test def pos_Coder() = compileFile(posDir, "Coder", twice) From 24e340303db03ac0e069dec19dd9cf64071b8f1a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 14 Jul 2015 14:28:44 +0200 Subject: [PATCH 15/42] Don't eta expand in appliedTo --- src/dotty/tools/dotc/core/TypeApplications.scala | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeApplications.scala b/src/dotty/tools/dotc/core/TypeApplications.scala index 3d82628bccb4..cc438cd0111f 100644 --- a/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/src/dotty/tools/dotc/core/TypeApplications.scala @@ -145,10 +145,12 @@ class TypeApplications(val self: Type) extends AnyVal { } val tparam = tparams.head val arg1 = - if ((tparam is HigherKinded) && !arg.isLambda && arg.typeParams.nonEmpty) + if ((tparam is HigherKinded) && !arg.isLambda && arg.typeParams.nonEmpty) { + println(i"missing eta expansion of $arg") arg.EtaExpand + } else arg - val tp1 = RefinedType(tp, tparam.name, arg1.toBounds(tparam)) + val tp1 = RefinedType(tp, tparam.name, arg.toBounds(tparam)) matchParams(tp1, tparams.tail, args1) case nil => tp } From 5e45dc5310652217f10f5b6236c267f7b4b4f3cb Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 15 Jul 2015 10:03:14 +0200 Subject: [PATCH 16/42] Always lambda abstract TempPolyTypes representing types. --- .../core/unpickleScala2/Scala2Unpickler.scala | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala b/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala index 0dca4154b92a..7eba9eb58408 100644 --- a/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala +++ b/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala @@ -49,18 +49,9 @@ object Scala2Unpickler { */ def depoly(tp: Type, denot: SymDenotation)(implicit ctx: Context): Type = tp match { case TempPolyType(tparams, restpe) => - if (denot.isAbstractType) - restpe.LambdaAbstract(tparams) // bounds needed? - else if (denot.isAliasType) { - var err: Option[(String, Position)] = None - val result = restpe.parameterizeWith(tparams) - for ((msg, pos) <- err) - ctx.warning( - sm"""$msg - |originally parsed type : ${tp.show} - |will be approximated by: ${result.show}. - |Proceed at own risk.""") - result + if (denot.isType) { + assert(!denot.isClass) + restpe.LambdaAbstract(tparams) } else PolyType.fromSymbols(tparams, restpe) From 968999af0937d028e4639020d234e6c01b660e05 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 15 Jul 2015 10:04:11 +0200 Subject: [PATCH 17/42] Lambda abstract all TypeRefs when unpickling. --- .../tools/dotc/core/unpickleScala2/Scala2Unpickler.scala | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala b/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala index 7eba9eb58408..2fc7c65e37af 100644 --- a/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala +++ b/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala @@ -669,6 +669,11 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas else TypeRef(pre, sym.name.asTypeName) val args = until(end, readTypeRef) if (sym == defn.ByNameParamClass2x) ExprType(args.head) + else if (args.isEmpty && sym.typeParams.nonEmpty) { + val res = tycon.LambdaAbstract(sym.typeParams) + println(s"lambda abstract $tycon") + res + } else tycon.appliedTo(args) case TYPEBOUNDStpe => TypeBounds(readTypeRef(), readTypeRef()) From d4741003f316002ab614abb49d2e0c46785ca27f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 15 Jul 2015 10:05:20 +0200 Subject: [PATCH 18/42] A non-class typeref has type parameters only if subtype of a lambda class. --- src/dotty/tools/dotc/core/TypeApplications.scala | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeApplications.scala b/src/dotty/tools/dotc/core/TypeApplications.scala index cc438cd0111f..13d11071b6c5 100644 --- a/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/src/dotty/tools/dotc/core/TypeApplications.scala @@ -44,7 +44,7 @@ class TypeApplications(val self: Type) extends AnyVal { /** The type parameters of this type are: * For a ClassInfo type, the type parameters of its class. * For a typeref referring to a class, the type parameters of the class. - * For a typeref referring to an alias or abstract type, the type parameters of + * For a typeref referring to a Lambda class, the type parameters of * its right hand side or upper bound. * For a refinement type, the type parameters of its parent, unless the refinement * re-binds the type parameter with a type-alias. @@ -58,7 +58,10 @@ class TypeApplications(val self: Type) extends AnyVal { case tp: TypeRef => val tsym = tp.typeSymbol if (tsym.isClass) tsym.typeParams - else tp.underlying.typeParams + else { + val lam = LambdaClass(forcing = false) + if (lam.exists) lam.typeParams else Nil//tp.underlying.typeParams + } case tp: RefinedType => val tparams = tp.parent.typeParams tp.refinedInfo match { From accbed2c6a389e61b83bab14cf6b4c224b162213 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 17 Jul 2015 12:32:57 +0200 Subject: [PATCH 19/42] Drop parameterizeWith parameterizeWith picked between simple hk types and lambda abstraction. No longer needed because now we always lambda abstract. --- src/dotty/tools/dotc/core/TypeApplications.scala | 4 ++-- src/dotty/tools/dotc/typer/Namer.scala | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeApplications.scala b/src/dotty/tools/dotc/core/TypeApplications.scala index 13d11071b6c5..d3df387bfcce 100644 --- a/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/src/dotty/tools/dotc/core/TypeApplications.scala @@ -415,7 +415,7 @@ class TypeApplications(val self: Type) extends AnyVal { } recur(self) } - +/* /** Given a type alias * * type T[boundSyms] = p.C[targs] @@ -478,7 +478,7 @@ class TypeApplications(val self: Type) extends AnyVal { if (cls.isClass) matchParams(boundSyms, cls.typeParams, argInfos, Map()) else LambdaAbstract(boundSyms) } - +*/ /** The typed lambda abstraction of this type `T` relative to `boundSyms`. * This is: * diff --git a/src/dotty/tools/dotc/typer/Namer.scala b/src/dotty/tools/dotc/typer/Namer.scala index b86acf4d654f..cddbd125e428 100644 --- a/src/dotty/tools/dotc/typer/Namer.scala +++ b/src/dotty/tools/dotc/typer/Namer.scala @@ -768,11 +768,11 @@ class Namer { typer: Typer => completeParams(tdef.tparams) val tparamSyms = tdef.tparams map symbolOfTree val isDerived = tdef.rhs.isInstanceOf[untpd.DerivedTypeTree] - val toParameterize = tparamSyms.nonEmpty && !isDerived - val needsLambda = sym.allOverriddenSymbols.exists(_ is HigherKinded) && !isDerived + //val toParameterize = tparamSyms.nonEmpty && !isDerived + //val needsLambda = sym.allOverriddenSymbols.exists(_ is HigherKinded) && !isDerived def abstracted(tp: Type): Type = - if (needsLambda) tp.LambdaAbstract(tparamSyms) - else if (toParameterize) tp.parameterizeWith(tparamSyms) + if (tparamSyms.nonEmpty) tp.LambdaAbstract(tparamSyms) + //else if (toParameterize) tp.parameterizeWith(tparamSyms) else tp sym.info = abstracted(TypeBounds.empty) // Temporarily set info of defined type T to ` >: Nothing <: Any. From c2a124f1885920564522a2756e861a97280711aa Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 19 Jul 2015 12:41:32 +0200 Subject: [PATCH 20/42] Allow to use safe substitution in LambdaAbstract Needed to avoid cycles involving F-boundes hk-types when reading Scala2 collection classes with new hk-scheme. --- src/dotty/tools/dotc/core/Substituters.scala | 27 +++++++++++++++++++ .../tools/dotc/core/TypeApplications.scala | 12 +++++++-- src/dotty/tools/dotc/core/Types.scala | 2 +- .../core/unpickleScala2/Scala2Unpickler.scala | 2 +- 4 files changed, 39 insertions(+), 4 deletions(-) diff --git a/src/dotty/tools/dotc/core/Substituters.scala b/src/dotty/tools/dotc/core/Substituters.scala index e4bbf2305805..0083ac626fae 100644 --- a/src/dotty/tools/dotc/core/Substituters.scala +++ b/src/dotty/tools/dotc/core/Substituters.scala @@ -277,4 +277,31 @@ trait Substituters { this: Context => final class SubstParamsMap(from: BindingType, to: List[Type]) extends DeepTypeMap { def apply(tp: Type) = substParams(tp, from, to, this) } + + /** A map for "cycle safe substitutions" which do not force the denotation + * of a TypeRef unless the name matches up with one of the substituted symbols. + */ + final class SafeSubstMap(from: List[Symbol], to: List[Type]) extends TypeMap { + def apply(tp: Type): Type = tp match { + case tp: NamedType => + try { + var sym: Symbol = null + var fs = from + var ts = to + while (fs.nonEmpty) { + if (fs.head.name == tp.name) { + if (sym == null) sym = tp.symbol + if (fs.head eq sym) return ts.head + } + fs = fs.tail + ts = ts.tail + } + tp.newLikeThis(apply(tp.prefix)) + } + catch { + case ex: CyclicReference => tp.derivedSelect(apply(tp.prefix)) + } + case _ => mapOver(tp) + } + } } diff --git a/src/dotty/tools/dotc/core/TypeApplications.scala b/src/dotty/tools/dotc/core/TypeApplications.scala index d3df387bfcce..61ed9495bfe3 100644 --- a/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/src/dotty/tools/dotc/core/TypeApplications.scala @@ -489,14 +489,22 @@ class TypeApplications(val self: Type) extends AnyVal { * `this.Arg$i`. * * TypeBounds are lambda abstracting by lambda abstracting their upper bound. + * + * @param cycleParanoid If `true` don't force denotation of a TypeRef unless + * its name matches one of `boundSyms`. Needed to avoid cycles + * involving F-boundes hk-types when reading Scala2 collection classes + * with new hk-scheme. */ - def LambdaAbstract(boundSyms: List[Symbol])(implicit ctx: Context): Type = { + def LambdaAbstract(boundSyms: List[Symbol], cycleParanoid: Boolean = false)(implicit ctx: Context): Type = { def expand(tp: Type) = { val lambda = defn.lambdaTrait(boundSyms.map(_.variance)) val substitutedRHS = (rt: RefinedType) => { val argRefs = boundSyms.indices.toList.map(i => RefinedThis(rt).select(tpnme.lambdaArgName(i))) - tp.subst(boundSyms, argRefs).bounds.withVariance(1) + val substituted = + if (cycleParanoid) new ctx.SafeSubstMap(boundSyms, argRefs).apply(tp) + else tp.subst(boundSyms, argRefs) + substituted.bounds.withVariance(1) } val res = RefinedType(lambda.typeRef, tpnme.Apply, substitutedRHS) //println(i"lambda abstract $self wrt $boundSyms%, % --> $res") diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 9417a6581993..ddfe31393580 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -1485,7 +1485,7 @@ object Types { /** Create a NamedType of the same kind as this type, but with a new prefix. */ - protected def newLikeThis(prefix: Type)(implicit ctx: Context): NamedType = + def newLikeThis(prefix: Type)(implicit ctx: Context): NamedType = NamedType(prefix, name) /** Create a NamedType of the same kind as this type, but with a "inherited name". diff --git a/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala b/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala index 2fc7c65e37af..8d5db625946f 100644 --- a/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala +++ b/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala @@ -51,7 +51,7 @@ object Scala2Unpickler { case TempPolyType(tparams, restpe) => if (denot.isType) { assert(!denot.isClass) - restpe.LambdaAbstract(tparams) + restpe.LambdaAbstract(tparams, cycleParanoid = true) } else PolyType.fromSymbols(tparams, restpe) From 9bb3f52059ca2694bd530a4ec0eb27db2d707348 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 19 Jul 2015 12:36:26 +0200 Subject: [PATCH 21/42] Use EtaExpand instead of LambdaAbstract when unpickling typerefs When unpickling from Scala2 TypeRefs with arguments which do not refer to classes, use EtaExpand instead of LambdaAbstract. Lambda Abstrct is wrong since it drops type arguments. --- .../tools/dotc/core/unpickleScala2/Scala2Unpickler.scala | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala b/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala index 8d5db625946f..540ad77d61e3 100644 --- a/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala +++ b/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala @@ -669,11 +669,7 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas else TypeRef(pre, sym.name.asTypeName) val args = until(end, readTypeRef) if (sym == defn.ByNameParamClass2x) ExprType(args.head) - else if (args.isEmpty && sym.typeParams.nonEmpty) { - val res = tycon.LambdaAbstract(sym.typeParams) - println(s"lambda abstract $tycon") - res - } + else if (args.isEmpty && sym.typeParams.nonEmpty) tycon.EtaExpand else tycon.appliedTo(args) case TYPEBOUNDStpe => TypeBounds(readTypeRef(), readTypeRef()) From 8610b4dc6e385ebbc53eaca66b11fb2cbb6429dc Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 19 Jul 2015 12:37:34 +0200 Subject: [PATCH 22/42] Avoid cycles when eliminating existentials in unpickling New hk-scheme caused cycles in elimExistentials which are fixed by this patch. --- .../core/unpickleScala2/Scala2Unpickler.scala | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala b/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala index 540ad77d61e3..d029e3118e09 100644 --- a/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala +++ b/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala @@ -579,15 +579,35 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas * tp { name: T } */ def elimExistentials(boundSyms: List[Symbol], tp: Type)(implicit ctx: Context): Type = { + // Need to be careful not to run into cyclic references here (observed when + // comiling t247.scala). That's why we avoiud taking `symbol` of a TypeRef + // unless names match up. + val isBound = (tp: Type) => { + def refersTo(tp: Type, sym: Symbol): Boolean = tp match { + case tp @ TypeRef(_, name) => sym.name == name && sym == tp.symbol + case tp: TypeVar => refersTo(tp.underlying, sym) + case tp : LazyRef => refersTo(tp.ref, sym) + case _ => false + } + boundSyms.exists(refersTo(tp, _)) + } + // Cannot use standard `existsPart` method because it calls `lookupRefined` + // which can cause CyclicReference errors. + val isBoundAccumulator = new ExistsAccumulator(isBound) { + override def foldOver(x: Boolean, tp: Type): Boolean = tp match { + case tp: TypeRef => applyToPrefix(x, tp) + case _ => super.foldOver(x, tp) + } + } def removeSingleton(tp: Type): Type = if (tp isRef defn.SingletonClass) defn.AnyType else tp def elim(tp: Type): Type = tp match { case tp @ RefinedType(parent, name) => val parent1 = elim(tp.parent) tp.refinedInfo match { - case TypeAlias(info: TypeRef) if boundSyms contains info.symbol => + case TypeAlias(info: TypeRef) if isBound(info) => RefinedType(parent1, name, info.symbol.info) - case info: TypeRef if boundSyms contains info.symbol => + case info: TypeRef if isBound(info) => val info1 = info.symbol.info assert(info1.derivesFrom(defn.SingletonClass)) RefinedType(parent1, name, info1.mapReduceAnd(removeSingleton)(_ & _)) @@ -600,8 +620,7 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas tp } val tp1 = elim(tp) - val isBound = (tp: Type) => boundSyms contains tp.typeSymbol - if (tp1 existsPart isBound) { + if (isBoundAccumulator(false, tp1)) { val anyTypes = boundSyms map (_ => defn.AnyType) val boundBounds = boundSyms map (_.info.bounds.hi) val tp2 = tp1.subst(boundSyms, boundBounds).subst(boundSyms, anyTypes) From 7b751f89e47bf91fb0a563a87376b2bd06cc739c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 19 Jul 2015 12:38:19 +0200 Subject: [PATCH 23/42] Don't lambda abstract derived types Derived types already contain the lambda abstractoion; lambda abstracting them again would cause a double lambda. --- src/dotty/tools/dotc/typer/Namer.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/typer/Namer.scala b/src/dotty/tools/dotc/typer/Namer.scala index cddbd125e428..50373037e730 100644 --- a/src/dotty/tools/dotc/typer/Namer.scala +++ b/src/dotty/tools/dotc/typer/Namer.scala @@ -771,7 +771,7 @@ class Namer { typer: Typer => //val toParameterize = tparamSyms.nonEmpty && !isDerived //val needsLambda = sym.allOverriddenSymbols.exists(_ is HigherKinded) && !isDerived def abstracted(tp: Type): Type = - if (tparamSyms.nonEmpty) tp.LambdaAbstract(tparamSyms) + if (tparamSyms.nonEmpty && !isDerived) tp.LambdaAbstract(tparamSyms) //else if (toParameterize) tp.parameterizeWith(tparamSyms) else tp sym.info = abstracted(TypeBounds.empty) From 253adb007af24b7541187c93df901752116f38bb Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 19 Jul 2015 12:57:06 +0200 Subject: [PATCH 24/42] Temporary fix to testLifted Discrepancies between numbers of formal and actual type arguments were observed when typing partialFunctions.scala under new scheme. Should come back to this when subtyping is rewrittem/simplified to work with new hk-scheme. Maybe testLifted is no longer needed at all. --- src/dotty/tools/dotc/core/TypeApplications.scala | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeApplications.scala b/src/dotty/tools/dotc/core/TypeApplications.scala index 61ed9495bfe3..bcf4b76bf32b 100644 --- a/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/src/dotty/tools/dotc/core/TypeApplications.scala @@ -14,6 +14,7 @@ import StdNames.tpnme import util.Positions.Position import config.Printers._ import collection.mutable +import java.util.NoSuchElementException object TypeApplications { @@ -602,8 +603,13 @@ class TypeApplications(val self: Type) extends AnyVal { case nil => false } - if (tparams.isEmpty) false - else if (typeParams.nonEmpty) p(EtaExpand) || classBounds.nonEmpty && tryLift(self.baseClasses) - else classBounds.nonEmpty && tryLift(self.baseClasses) + try { // temporary, to avoid type mismatches in applications. Should come back to this + // when subtyping is rewritten to account for new hk-scheme. + if (tparams.isEmpty) false + else if (typeParams.nonEmpty) p(EtaExpand) || classBounds.nonEmpty && tryLift(self.baseClasses) + else classBounds.nonEmpty && tryLift(self.baseClasses) + } catch { + case ex: NoSuchElementException => false + } } } From f67983079baa827b00f992e17e4855973243c9b2 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 19 Jul 2015 12:59:34 +0200 Subject: [PATCH 25/42] Follow alias types when computing type params. This aligns typeParams and rawTypeParams. It's not strictly to necessary, though. --- src/dotty/tools/dotc/core/TypeApplications.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeApplications.scala b/src/dotty/tools/dotc/core/TypeApplications.scala index bcf4b76bf32b..a7fb6e928389 100644 --- a/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/src/dotty/tools/dotc/core/TypeApplications.scala @@ -59,9 +59,10 @@ class TypeApplications(val self: Type) extends AnyVal { case tp: TypeRef => val tsym = tp.typeSymbol if (tsym.isClass) tsym.typeParams + else if (tsym.isAliasType) tp.underlying.rawTypeParams else { val lam = LambdaClass(forcing = false) - if (lam.exists) lam.typeParams else Nil//tp.underlying.typeParams + if (lam.exists) lam.typeParams else Nil } case tp: RefinedType => val tparams = tp.parent.typeParams @@ -84,7 +85,7 @@ class TypeApplications(val self: Type) extends AnyVal { * do not remove corresponding type parameters. * Second, it will return Nil for BoundTypes because we might get a NullPointer exception * on PolyParam#underlying otherwise (demonstrated by showClass test). - * Third, it won't return higher-kinded type parameters, i.e. the type parameters of + * Third, it won't return abstract higher-kinded type parameters, i.e. the type parameters of * an abstract type are always empty. */ final def rawTypeParams(implicit ctx: Context): List[TypeSymbol] = { From 484afefc27c3c0d13b7ec8d5cd0000fde181d6a2 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 19 Jul 2015 13:02:36 +0200 Subject: [PATCH 26/42] Try to eta reduce type applications. Rewrite a type application like ([HK$0] => C[HK$0])(T) to C[T] Avoids application cahins to become unnecessarly large. --- src/dotty/tools/dotc/core/TypeApplications.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/core/TypeApplications.scala b/src/dotty/tools/dotc/core/TypeApplications.scala index a7fb6e928389..d14999ee533e 100644 --- a/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/src/dotty/tools/dotc/core/TypeApplications.scala @@ -180,7 +180,9 @@ class TypeApplications(val self: Type) extends AnyVal { matchParams(tp, safeTypeParams, args) } case tp: RefinedType => - tp.derivedRefinedType( + val redux = tp.EtaReduce + if (redux.exists) redux.appliedTo(args) // Rewrite ([HK$0] => C[HK$0])(T) to C[T] + else tp.derivedRefinedType( instantiate(tp.parent, original), tp.refinedName, tp.refinedInfo) From 5f779f6416516dbf196703dd51d83c3b7dd22401 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 19 Jul 2015 13:04:39 +0200 Subject: [PATCH 27/42] Project all high-kinded types with #Apply Used to be just instantiated lambdas. With the new scheme every type with a kind higher than * needs to be projected with #Apply. --- src/dotty/tools/dotc/core/TypeApplications.scala | 11 ++++++++++- src/dotty/tools/dotc/core/TypeOps.scala | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeApplications.scala b/src/dotty/tools/dotc/core/TypeApplications.scala index d14999ee533e..daf26408740a 100644 --- a/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/src/dotty/tools/dotc/core/TypeApplications.scala @@ -194,10 +194,19 @@ class TypeApplications(val self: Type) extends AnyVal { tp } + def isHK(tp: Type): Boolean = tp match { + case tp: TypeRef => + val sym = tp.symbol + if (sym.isClass) sym.isLambdaTrait + else !sym.isAliasType || isHK(tp.info) + case tp: TypeProxy => isHK(tp.underlying) + case _ => false + } + if (args.isEmpty || ctx.erasedTypes) self else { val res = instantiate(self, self) - if (res.isInstantiatedLambda) res.select(tpnme.Apply) else res + if (isHK(res)) TypeRef(res, tpnme.Apply) else res } } diff --git a/src/dotty/tools/dotc/core/TypeOps.scala b/src/dotty/tools/dotc/core/TypeOps.scala index b61d39749a4b..eb46ef25df89 100644 --- a/src/dotty/tools/dotc/core/TypeOps.scala +++ b/src/dotty/tools/dotc/core/TypeOps.scala @@ -21,7 +21,7 @@ trait TypeOps { this: Context => // TODO: Make standalone object. * class C { type T; def f(x: T): T } * * and an expression `e` of type `C`. Then computing the type of `e.f` leads - * to the query asSeenFrom(`C`, `(x: T)T`). What should it's result be? The + * to the query asSeenFrom(`C`, `(x: T)T`). What should its result be? The * naive answer `(x: C.T)C.T` is incorrect given that we treat `C.T` as the existential * `exists(c: C)c.T`. What we need to do instead is to skolemize the existential. So * the answer would be `(x: c.T)c.T` for some (unknown) value `c` of type `C`. From 7cb91c7beb6c7608fa076e9516710650d1f162f0 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 19 Jul 2015 13:05:56 +0200 Subject: [PATCH 28/42] Better error reporting in TreePickler Turn a possible NPE into an AssertionError. The latter are caught in pickleTree, so an error leaves a trace about what was pickled. --- src/dotty/tools/dotc/core/tasty/TreePickler.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 8847d2de3c40..a23d59339de4 100644 --- a/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -200,7 +200,9 @@ class TreePickler(pickler: TastyPickler) { withLength { pickleType(tpe.thistpe); pickleType(tpe.supertpe)} case tpe: RefinedThis => writeByte(REFINEDthis) - writeRef(pickledTypes.get(tpe.binder).asInstanceOf[Addr]) + val binderAddr = pickledTypes.get(tpe.binder) + assert(binderAddr != null) + writeRef(binderAddr.asInstanceOf[Addr]) case tpe: SkolemType => pickleType(tpe.info) case tpe: RefinedType => From 8b1d32093a43c441ad81005e9ca049bfb30e0df9 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 19 Jul 2015 13:09:05 +0200 Subject: [PATCH 29/42] Adapt type arguments in typedAppliedTypeTree Previously, only pattern bound arguments were adapated. This was an oversight. Also, change logix so that we survive empty type parameter lists. This was also an oversight before. --- src/dotty/tools/dotc/typer/Typer.scala | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index 593250cea865..e08a654f441a 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -822,7 +822,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit res } - def typedAppliedTypeTree(tree: untpd.AppliedTypeTree)(implicit ctx: Context): AppliedTypeTree = track("typedAppliedTypeTree") { + def typedAppliedTypeTree(tree: untpd.AppliedTypeTree)(implicit ctx: Context): Tree = track("typedAppliedTypeTree") { val tpt1 = typed(tree.tpt) val tparams = tpt1.tpe.typeParams var args = tree.args @@ -830,17 +830,19 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit ctx.error(d"${tpt1.tpe} does not take type parameters") tpt1 } - else if (args.length != tparams.length) { - ctx.error(d"wrong number of type arguments for ${tpt1.tpe}, should be ${tparams.length}", tree.pos) - args = args.take(tparams.length) + else { + if (args.length != tparams.length) { + ctx.error(d"wrong number of type arguments for ${tpt1.tpe}, should be ${tparams.length}", tree.pos) + args = args.take(tparams.length) + } + def typedArg(arg: untpd.Tree, tparam: Symbol) = { + val arg1 = typed(arg, if (ctx.mode is Mode.Pattern) tparam.info else WildcardType) + adaptTypeArg(arg1, if (tparam.isCompleted) tparam.info else WildcardType) + } + val args1 = args.zipWithConserve(tparams)(typedArg(_, _)).asInstanceOf[List[Tree]] + // check that arguments conform to bounds is done in phase PostTyper + assignType(cpy.AppliedTypeTree(tree)(tpt1, args1), tpt1, args1) } - val argPts = - if (ctx.mode is Mode.Pattern) tpt1.tpe.typeParams.map(_.info) - else tree.args.map(_ => WildcardType) - def typedArg(arg: untpd.Tree, pt: Type) = adaptTypeArg(typed(arg, pt), pt) - val args1 = args.zipWithConserve(argPts)(typedArg(_, _)).asInstanceOf[List[Tree]] - // check that arguments conform to bounds is done in phase PostTyper - assignType(cpy.AppliedTypeTree(tree)(tpt1, args1), tpt1, args1) } def typedByNameTypeTree(tree: untpd.ByNameTypeTree)(implicit ctx: Context): ByNameTypeTree = track("typedByNameTypeTree") { From 4b06dea5c6123b273f513d40e63fa73c85368982 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 19 Jul 2015 13:20:06 +0200 Subject: [PATCH 30/42] Add missing position to error --- src/dotty/tools/dotc/typer/Typer.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index e08a654f441a..6bed5bef869b 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -827,7 +827,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit val tparams = tpt1.tpe.typeParams var args = tree.args if (tparams.isEmpty) { - ctx.error(d"${tpt1.tpe} does not take type parameters") + ctx.error(d"${tpt1.tpe} does not take type parameters", tree.pos) tpt1 } else { From e24d164c98d88c449aac558473f900545a8cec5f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 20 Jul 2015 10:36:53 +0200 Subject: [PATCH 31/42] Rename Apply -> hkApply Want to have a unique name for Apply, so that tests for higher-kinded types become cheaper. --- src/dotty/tools/dotc/core/Definitions.scala | 2 +- src/dotty/tools/dotc/core/StdNames.scala | 2 +- src/dotty/tools/dotc/core/TypeApplications.scala | 12 ++++++------ src/dotty/tools/dotc/core/TypeComparer.scala | 8 ++++---- src/dotty/tools/dotc/core/Types.scala | 4 ++-- src/dotty/tools/dotc/core/tasty/TreePickler.scala | 2 +- .../dotc/core/unpickleScala2/Scala2Unpickler.scala | 4 ++-- src/dotty/tools/dotc/printing/RefinedPrinter.scala | 8 ++++---- src/dotty/tools/dotc/typer/Typer.scala | 7 ++++++- 9 files changed, 27 insertions(+), 22 deletions(-) diff --git a/src/dotty/tools/dotc/core/Definitions.scala b/src/dotty/tools/dotc/core/Definitions.scala index 7ed0a26e018c..7831f4de7a2a 100644 --- a/src/dotty/tools/dotc/core/Definitions.scala +++ b/src/dotty/tools/dotc/core/Definitions.scala @@ -527,7 +527,7 @@ class Definitions { val paramDecls = newScope for (i <- 0 until vcs.length) newTypeParam(cls, tpnme.lambdaArgName(i), varianceFlags(vcs(i)), paramDecls) - newTypeField(cls, tpnme.Apply, Covariant, paramDecls) + newTypeField(cls, tpnme.hkApply, Covariant, paramDecls) val parentTraitRefs = for (i <- 0 until vcs.length if vcs(i) != 0) yield lambdaTrait(vcs.updated(i, 0)).typeRef diff --git a/src/dotty/tools/dotc/core/StdNames.scala b/src/dotty/tools/dotc/core/StdNames.scala index eb1a73625381..3ced604454cc 100644 --- a/src/dotty/tools/dotc/core/StdNames.scala +++ b/src/dotty/tools/dotc/core/StdNames.scala @@ -311,7 +311,7 @@ object StdNames { val AnnotatedType: N = "AnnotatedType" val AppliedTypeTree: N = "AppliedTypeTree" - val Apply: N = "Apply" + val hkApply: N = "$apply" val ArrayAnnotArg: N = "ArrayAnnotArg" val Constant: N = "Constant" val ConstantType: N = "ConstantType" diff --git a/src/dotty/tools/dotc/core/TypeApplications.scala b/src/dotty/tools/dotc/core/TypeApplications.scala index daf26408740a..f5e321d2bbe8 100644 --- a/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/src/dotty/tools/dotc/core/TypeApplications.scala @@ -206,15 +206,15 @@ class TypeApplications(val self: Type) extends AnyVal { if (args.isEmpty || ctx.erasedTypes) self else { val res = instantiate(self, self) - if (isHK(res)) TypeRef(res, tpnme.Apply) else res + if (isHK(res)) TypeRef(res, tpnme.hkApply) else res } } /** Simplify a fully instantiated type of the form `LambdaX{... type Apply = T } # Apply` to `T`. */ def simplifyApply(implicit ctx: Context): Type = self match { - case self @ TypeRef(prefix, tpnme.Apply) if prefix.isInstantiatedLambda => - prefix.member(tpnme.Apply).info match { + case self @ TypeRef(prefix, tpnme.hkApply) if prefix.isInstantiatedLambda => + prefix.member(tpnme.hkApply).info match { case TypeAlias(alias) => alias case _ => self } @@ -519,7 +519,7 @@ class TypeApplications(val self: Type) extends AnyVal { else tp.subst(boundSyms, argRefs) substituted.bounds.withVariance(1) } - val res = RefinedType(lambda.typeRef, tpnme.Apply, substitutedRHS) + val res = RefinedType(lambda.typeRef, tpnme.hkApply, substitutedRHS) //println(i"lambda abstract $self wrt $boundSyms%, % --> $res") res } @@ -559,7 +559,7 @@ class TypeApplications(val self: Type) extends AnyVal { case TypeAlias(TypeRef(RefinedThis(rt), rname)) // TODO: Drop once hk applications have been updated if (rname == tparam.name) && (rt eq self) => etaCore(tp.parent, otherParams) - case TypeRef(TypeAlias(TypeRef(RefinedThis(rt), rname)), tpnme.Apply) + case TypeRef(TypeAlias(TypeRef(RefinedThis(rt), rname)), tpnme.hkApply) if (rname == tparam.name) && (rt eq self) => etaCore(tp.parent, otherParams) case _ => @@ -570,7 +570,7 @@ class TypeApplications(val self: Type) extends AnyVal { } } self match { - case self @ RefinedType(parent, tpnme.Apply) => + case self @ RefinedType(parent, tpnme.hkApply) => val lc = parent.LambdaClass(forcing = false) self.refinedInfo match { case TypeAlias(alias) if lc.exists => etaCore(alias, lc.typeParams.reverse) diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index ea815f6c0778..a8598ae443cf 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -471,7 +471,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { * continue with `T <:< U` if `inOrder` is true and `U <:< T` otherwise. */ def compareHK(projection: NamedType, other: Type, inOrder: Boolean) = - projection.name == tpnme.Apply && { + projection.name == tpnme.hkApply && { // @@@ rewrite val lambda = projection.prefix.LambdaClass(forcing = true) lambda.exists && !other.isLambda && other.testLifted(lambda.typeParams, @@ -480,7 +480,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { } /** The class symbols bounding the type of the `Apply` member of `tp` */ - private def classBounds(tp: Type) = tp.member(tpnme.Apply).info.classSymbols + private def classBounds(tp: Type) = tp.member(tpnme.hkApply).info.classSymbols /** Returns true iff either `tp11 <:< tp21` or `tp12 <:< tp22`, trying at the same time * to keep the constraint as wide as possible. Specifically, if @@ -633,9 +633,9 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { /** Does `tp` need to be eta lifted to be comparable to `target`? */ private def needsEtaLift(tp: Type, target: RefinedType): Boolean = { - //default.echo(i"needs eta $tp $target?", { + //default.echo(i"needs eta $tp $target?", { // @@@ rewrite val name = target.refinedName - (name.isLambdaArgName || (name eq tpnme.Apply)) && target.isLambda && + (name.isLambdaArgName || (name eq tpnme.hkApply)) && target.isLambda && tp.exists && !tp.isLambda //}) } diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index ddfe31393580..185ea9e23a79 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -826,7 +826,7 @@ object Types { * * P { type T = String, type R = P{...}.T } # R --> String * - * (2) The refinement is a fully instantiated type lambda, and the projected name is "Apply". + * (2) The refinement is a fully instantiated type lambda, and the projected name is "$apply". * In this case the rhs of the apply is returned with all references to lambda argument types * substituted by their definitions. * @@ -862,7 +862,7 @@ object Types { else if (!pre.refinementRefersToThis) alias else alias match { case TypeRef(RefinedThis(`pre`), aliasName) => lookupRefined(aliasName) // (1) - case _ => if (name == tpnme.Apply) betaReduce(alias) else NoType // (2) + case _ => if (name == tpnme.hkApply) betaReduce(alias) else NoType // (2) } case _ => loop(pre.parent, resolved) } diff --git a/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/src/dotty/tools/dotc/core/tasty/TreePickler.scala index a23d59339de4..d50817b60e19 100644 --- a/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -176,7 +176,7 @@ class TreePickler(pickler: TastyPickler) { pickleNameAndSig(tpe.name, tpe.signature); pickleType(tpe.prefix) } case tpe: NamedType => - if (tpe.name == tpnme.Apply && tpe.prefix.argInfos.nonEmpty && tpe.prefix.isInstantiatedLambda) + if (tpe.name == tpnme.hkApply && tpe.prefix.argInfos.nonEmpty && tpe.prefix.isInstantiatedLambda) // instantiated lambdas are pickled as APPLIEDTYPE; #Apply will // be reconstituted when unpickling. pickleType(tpe.prefix) diff --git a/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala b/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala index d029e3118e09..74e9b21eac0a 100644 --- a/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala +++ b/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala @@ -579,7 +579,7 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas * tp { name: T } */ def elimExistentials(boundSyms: List[Symbol], tp: Type)(implicit ctx: Context): Type = { - // Need to be careful not to run into cyclic references here (observed when + // Need to be careful not to run into cyclic references here (observed when // comiling t247.scala). That's why we avoiud taking `symbol` of a TypeRef // unless names match up. val isBound = (tp: Type) => { @@ -614,7 +614,7 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas case info => tp.derivedRefinedType(parent1, name, info) } - case tp @ TypeRef(pre, tpnme.Apply) if pre.isLambda => + case tp @ TypeRef(pre, tpnme.hkApply) => elim(pre) case _ => tp diff --git a/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 70fab7e0fc5a..a46665ec02c8 100644 --- a/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -122,7 +122,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { return (toTextLocal(tycon) ~ "[" ~ Text(args map argText, ", ") ~ "]").close } if (tp.isSafeLambda) { - val (prefix, body, bindings) = extractApply(tp) + val (prefix, body, bindings) = decomposeHKApply(tp) prefix match { case prefix: TypeRef if prefix.symbol.isLambdaTrait && body.exists => return typeLambdaText(prefix.symbol, body, bindings) @@ -184,9 +184,9 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { * without a prefix, because the latter print nicer. * */ - def extractApply(tp: Type): (Type, Type, List[(Name, Type)]) = tp.stripTypeVar match { + def decomposeHKApply(tp: Type): (Type, Type, List[(Name, Type)]) = tp.stripTypeVar match { case tp @ RefinedType(parent, name) => - if (name == tpnme.Apply) { + if (name == tpnme.hkApply) { // simplify arguments so that parameters just print HK$i and not // LambdaI{...}.HK$i val simplifyArgs = new TypeMap { @@ -199,7 +199,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { } (parent, simplifyArgs(tp.refinedInfo.followTypeAlias), Nil) } else if (name.isLambdaArgName) { - val (prefix, body, argBindings) = extractApply(parent) + val (prefix, body, argBindings) = decomposeHKApply(parent) (prefix, body, (name, tp.refinedInfo) :: argBindings) } else (tp, NoType, Nil) case _ => diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index 6bed5bef869b..6b05b49638e0 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -1431,7 +1431,12 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit case Select(New(tpt), nme.CONSTRUCTOR) => tpt.tpe.dealias.argTypesLo case _ => Nil } - if (typeArgs.isEmpty) typeArgs = constrained(poly, tree)._2 + if (typeArgs.isEmpty) { + //for ((pname, pbound) <- poly.paramNames.zip(poly.paramBounds)) + // if (pbound.hi.isSafeLambda) + // ctx.error(d"cannot infer argument for higher-kinded type parameter $pname", tree.pos) + typeArgs = constrained(poly, tree)._2 + } convertNewArray( adaptInterpolated(tree.appliedToTypes(typeArgs), pt, original)) } From 7582c0e8e00150cc656f50fdf5402a59b6400a0e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 20 Jul 2015 10:58:51 +0200 Subject: [PATCH 32/42] Simplify EtaReduce by removing dead case. --- src/dotty/tools/dotc/core/TypeApplications.scala | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeApplications.scala b/src/dotty/tools/dotc/core/TypeApplications.scala index f5e321d2bbe8..5455c0444476 100644 --- a/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/src/dotty/tools/dotc/core/TypeApplications.scala @@ -556,10 +556,7 @@ class TypeApplications(val self: Type) extends AnyVal { tp match { case tp: RefinedType => tp.refinedInfo match { - case TypeAlias(TypeRef(RefinedThis(rt), rname)) // TODO: Drop once hk applications have been updated - if (rname == tparam.name) && (rt eq self) => - etaCore(tp.parent, otherParams) - case TypeRef(TypeAlias(TypeRef(RefinedThis(rt), rname)), tpnme.hkApply) + case TypeAlias(TypeRef(RefinedThis(rt), rname)) if (rname == tparam.name) && (rt eq self) => etaCore(tp.parent, otherParams) case _ => From 4a5bf9850bb58c5351d0d283bb53567330cc8f00 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 20 Jul 2015 11:35:52 +0200 Subject: [PATCH 33/42] Rename of hk args HK$x -> hk$x These are not user-accessible types, so no need to follow type convention and write in upper case. Also, rename occurrences of lambda to Lambda, to make clear we mean a type lambda. --- src/dotty/tools/dotc/core/Definitions.scala | 12 +-- src/dotty/tools/dotc/core/NameOps.scala | 2 +- src/dotty/tools/dotc/core/StdNames.scala | 6 +- .../tools/dotc/core/TypeApplications.scala | 83 ++++--------------- src/dotty/tools/dotc/core/Types.scala | 6 +- 5 files changed, 27 insertions(+), 82 deletions(-) diff --git a/src/dotty/tools/dotc/core/Definitions.scala b/src/dotty/tools/dotc/core/Definitions.scala index 7831f4de7a2a..a75602187fa9 100644 --- a/src/dotty/tools/dotc/core/Definitions.scala +++ b/src/dotty/tools/dotc/core/Definitions.scala @@ -493,7 +493,7 @@ class Definitions { /** The set of HigherKindedXYZ traits encountered so far */ def lambdaTraits: Set[Symbol] = myLambdaTraits - private var lambdaTraitForVariances = mutable.Map[List[Int], ClassSymbol]() + private var LambdaTraitForVariances = mutable.Map[List[Int], ClassSymbol]() /** The HigherKinded trait corresponding to symbols `boundSyms` (which are assumed * to be the type parameters of a higher-kided type). This is a class symbol that @@ -512,7 +512,7 @@ class Definitions { * - for each positive or negative variance v_i there is a parent trait Pj which * is the same as LambdaXYZ except that it has `I` in i-th position. */ - def lambdaTrait(vcs: List[Int]): ClassSymbol = { + def LambdaTrait(vcs: List[Int]): ClassSymbol = { assert(vcs.nonEmpty) def varianceFlags(v: Int) = v match { @@ -526,17 +526,17 @@ class Definitions { val cls = denot.asClass.classSymbol val paramDecls = newScope for (i <- 0 until vcs.length) - newTypeParam(cls, tpnme.lambdaArgName(i), varianceFlags(vcs(i)), paramDecls) + newTypeParam(cls, tpnme.LambdaArgName(i), varianceFlags(vcs(i)), paramDecls) newTypeField(cls, tpnme.hkApply, Covariant, paramDecls) val parentTraitRefs = for (i <- 0 until vcs.length if vcs(i) != 0) - yield lambdaTrait(vcs.updated(i, 0)).typeRef + yield LambdaTrait(vcs.updated(i, 0)).typeRef denot.info = ClassInfo( ScalaPackageClass.thisType, cls, ObjectClass.typeRef :: parentTraitRefs.toList, paramDecls) } } - val traitName = tpnme.lambdaTraitName(vcs) + val traitName = tpnme.LambdaTraitName(vcs) def createTrait = { val cls = newClassSymbol( @@ -548,7 +548,7 @@ class Definitions { cls } - lambdaTraitForVariances.getOrElseUpdate(vcs, createTrait) + LambdaTraitForVariances.getOrElseUpdate(vcs, createTrait) } // ----- primitive value class machinery ------------------------------------------ diff --git a/src/dotty/tools/dotc/core/NameOps.scala b/src/dotty/tools/dotc/core/NameOps.scala index 4d6cca61dc08..77a14767ec3b 100644 --- a/src/dotty/tools/dotc/core/NameOps.scala +++ b/src/dotty/tools/dotc/core/NameOps.scala @@ -109,7 +109,7 @@ object NameOps { /** The index of the higher-kinded type parameter with this name. * Pre: isLambdaArgName. */ - def lambdaArgIndex: Int = + def LambdaArgIndex: Int = name.drop(tpnme.LAMBDA_ARG_PREFIX.length).toString.toInt /** If the name ends with $nn where nn are diff --git a/src/dotty/tools/dotc/core/StdNames.scala b/src/dotty/tools/dotc/core/StdNames.scala index 3ced604454cc..25457f676c29 100644 --- a/src/dotty/tools/dotc/core/StdNames.scala +++ b/src/dotty/tools/dotc/core/StdNames.scala @@ -173,7 +173,7 @@ object StdNames { final val WILDCARD_STAR: N = "_*" final val REIFY_TREECREATOR_PREFIX: N = "$treecreator" final val REIFY_TYPECREATOR_PREFIX: N = "$typecreator" - final val LAMBDA_ARG_PREFIX: N = "HK$" + final val LAMBDA_ARG_PREFIX: N = "hk$" final val LAMBDA_ARG_PREFIXhead: Char = LAMBDA_ARG_PREFIX.head final val Any: N = "Any" @@ -737,8 +737,8 @@ object StdNames { def syntheticTypeParamNames(num: Int): List[TypeName] = (0 until num).map(syntheticTypeParamName)(breakOut) - def lambdaTraitName(vcs: List[Int]): TypeName = LambdaPrefix ++ vcs.map(varianceSuffix).mkString - def lambdaArgName(n: Int) = LAMBDA_ARG_PREFIX ++ n.toString + def LambdaTraitName(vcs: List[Int]): TypeName = LambdaPrefix ++ vcs.map(varianceSuffix).mkString + def LambdaArgName(n: Int) = LAMBDA_ARG_PREFIX ++ n.toString final val Conforms = encode("<:<") diff --git a/src/dotty/tools/dotc/core/TypeApplications.scala b/src/dotty/tools/dotc/core/TypeApplications.scala index 5455c0444476..57f095e8e724 100644 --- a/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/src/dotty/tools/dotc/core/TypeApplications.scala @@ -140,6 +140,16 @@ class TypeApplications(val self: Type) extends AnyVal { def isInstantiatedLambda(implicit ctx: Context): Boolean = isSafeLambda && typeParams.isEmpty + def isHK(implicit ctx: Context): Boolean = self.dealias match { + case self: TypeRef => + self.info match { + case TypeBounds(_, hi) => hi.isHK + case _ => false + } + case RefinedType(_, name) => name == tpnme.hkApply || name.isLambdaArgName + case _ => false + } + /** Encode the type resulting from applying this type to given arguments */ final def appliedTo(args: List[Type])(implicit ctx: Context): Type = /*>|>*/ track("appliedTo") /*<|<*/ { def matchParams(tp: Type, tparams: List[TypeSymbol], args: List[Type]): Type = args match { @@ -175,7 +185,7 @@ class TypeApplications(val self: Type) extends AnyVal { if (tsym.isClass || !tp.typeSymbol.isCompleting) original.typeParams else { ctx.warning(i"encountered F-bounded higher-kinded type parameters for $tsym; assuming they are invariant") - defn.lambdaTrait(args map alwaysZero).typeParams + defn.LambdaTrait(args map alwaysZero).typeParams // @@@ can we force? } matchParams(tp, safeTypeParams, args) } @@ -428,70 +438,7 @@ class TypeApplications(val self: Type) extends AnyVal { } recur(self) } -/* - /** Given a type alias - * - * type T[boundSyms] = p.C[targs] - * - * produce its equivalent right hand side RHS that makes no reference to the bound - * symbols on the left hand side. I.e. the type alias can be replaced by - * - * type T = RHS - * - * There are two strategies how this is achieved. - * 1st strategy: Applies if `C` is a class such that every bound symbol in `boundSyms` - * appears as an argument in `targs`, and in the same order. Then the rewriting replaces - * bound symbols by references to the parameters of class C. Example: - * - * Say we have: - * - * class Triple[type T1, type T2, type T3] - * type A[X] = Triple[(X, X), X, String] - * - * Then this is rewritable, as `X` appears as second type argument to `Triple`. - * Occurrences of `X` are rewritten to `this.T2` and the whole definition becomes: - * - * type A = Triple { type T1 = (this.T2, this.T2); type T3 = String } - * - * 2nd strategy: Used as a fallback if 1st strategy does not apply. It rewrites - * the RHS to a typed lambda abstraction. - */ - def parameterizeWith(boundSyms: List[Symbol])(implicit ctx: Context): Type = { - def matchParams(bsyms: List[Symbol], tparams: List[Symbol], targs: List[Type], - correspondingParamName: Map[Symbol, TypeName]): Type = { - if (bsyms.isEmpty) { - val correspondingNames = correspondingParamName.values.toSet - - def replacements(rt: RefinedType): List[Type] = - for (sym <- boundSyms) - yield TypeRef(RefinedThis(rt), correspondingParamName(sym)) - - def rewrite(tp: Type): Type = tp match { - case tp @ RefinedType(parent, name: TypeName) => - if (correspondingNames contains name) rewrite(parent) - else RefinedType( - rewrite(parent), name, - rt => tp.refinedInfo.subst(boundSyms, replacements(rt))) - case tp => - tp - } - - rewrite(self) - } - else if (tparams.isEmpty || targs.isEmpty) - LambdaAbstract(boundSyms) - else if (bsyms.head == targs.head.typeSymbol) - matchParams(bsyms.tail, tparams.tail, targs.tail, - correspondingParamName + (bsyms.head -> tparams.head.name.asTypeName)) - else - matchParams(bsyms, tparams.tail, targs.tail, correspondingParamName) - } - val cls = self.typeSymbol - if (cls.isClass) matchParams(boundSyms, cls.typeParams, argInfos, Map()) - else LambdaAbstract(boundSyms) - } -*/ /** The typed lambda abstraction of this type `T` relative to `boundSyms`. * This is: * @@ -510,18 +457,16 @@ class TypeApplications(val self: Type) extends AnyVal { */ def LambdaAbstract(boundSyms: List[Symbol], cycleParanoid: Boolean = false)(implicit ctx: Context): Type = { def expand(tp: Type) = { - val lambda = defn.lambdaTrait(boundSyms.map(_.variance)) + val lambda = defn.LambdaTrait(boundSyms.map(_.variance)) val substitutedRHS = (rt: RefinedType) => { val argRefs = boundSyms.indices.toList.map(i => - RefinedThis(rt).select(tpnme.lambdaArgName(i))) + RefinedThis(rt).select(tpnme.LambdaArgName(i))) val substituted = if (cycleParanoid) new ctx.SafeSubstMap(boundSyms, argRefs).apply(tp) else tp.subst(boundSyms, argRefs) substituted.bounds.withVariance(1) } - val res = RefinedType(lambda.typeRef, tpnme.hkApply, substitutedRHS) - //println(i"lambda abstract $self wrt $boundSyms%, % --> $res") - res + RefinedType(lambda.typeRef, tpnme.hkApply, substitutedRHS) } self match { case self @ TypeBounds(lo, hi) => diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 185ea9e23a79..cf0d60711cc1 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -1858,8 +1858,8 @@ object Types { case refinedInfo: TypeBounds if refinedInfo.variance != 0 && refinedName.isLambdaArgName => val cls = parent.LambdaClass(forcing = false) if (cls.exists) - assert(refinedInfo.variance == cls.typeParams.apply(refinedName.lambdaArgIndex).variance, - s"variance mismatch for $this, $cls, ${cls.typeParams}, ${cls.typeParams.apply(refinedName.lambdaArgIndex).variance}, ${refinedInfo.variance}") + assert(refinedInfo.variance == cls.typeParams.apply(refinedName.LambdaArgIndex).variance, + s"variance mismatch for $this, $cls, ${cls.typeParams}, ${cls.typeParams.apply(refinedName.LambdaArgIndex).variance}, ${refinedInfo.variance}") case _ => } this @@ -1875,7 +1875,7 @@ object Types { this else if ( refinedName.isLambdaArgName //&& { println(s"deriving $refinedName $parent $underlyingTypeParams"); true } - && refinedName.lambdaArgIndex < underlyingTypeParams.length + && refinedName.LambdaArgIndex < underlyingTypeParams.length && !parent.isLambda) derivedRefinedType(parent.EtaExpand, refinedName, refinedInfo) else From 974c503a27ead47dfe75eaed919b0169c9f21c8d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 20 Jul 2015 12:15:27 +0200 Subject: [PATCH 34/42] Remove magic from derivedRefinedType. It's no longer needed with new hk scheme. --- src/dotty/tools/dotc/core/Types.scala | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index cf0d60711cc1..ab6eb03ad776 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -1865,23 +1865,9 @@ object Types { this } - /** Derived refined type, with a twist: A refinement with a higher-kinded type param placeholder - * is transformed to a refinement of the original type parameter if that one exists. - */ - def derivedRefinedType(parent: Type, refinedName: Name, refinedInfo: Type)(implicit ctx: Context): RefinedType = { - lazy val underlyingTypeParams = parent.rawTypeParams - - if ((parent eq this.parent) && (refinedName eq this.refinedName) && (refinedInfo eq this.refinedInfo)) - this - else if ( refinedName.isLambdaArgName - //&& { println(s"deriving $refinedName $parent $underlyingTypeParams"); true } - && refinedName.LambdaArgIndex < underlyingTypeParams.length - && !parent.isLambda) - derivedRefinedType(parent.EtaExpand, refinedName, refinedInfo) - else - if (false) RefinedType(parent, refinedName, refinedInfo) - else RefinedType(parent, refinedName, rt => refinedInfo.substRefinedThis(this, RefinedThis(rt))) - } + def derivedRefinedType(parent: Type, refinedName: Name, refinedInfo: Type)(implicit ctx: Context): RefinedType = + if ((parent eq this.parent) && (refinedName eq this.refinedName) && (refinedInfo eq this.refinedInfo)) this + else RefinedType(parent, refinedName, rt => refinedInfo.substRefinedThis(this, RefinedThis(rt))) /** Add this refinement to `parent`, provided If `refinedName` is a member of `parent`. */ def wrapIfMember(parent: Type)(implicit ctx: Context): Type = From 7eaa515907968ddd2d561d633bbb4f296b764e29 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 20 Jul 2015 13:59:34 +0200 Subject: [PATCH 35/42] Replace isLambda with ifHK Replace occurrences of isLambda with isHK, because isHK is a bit faster and simplier. --- .../tools/dotc/core/TypeApplications.scala | 32 ++++++++----------- src/dotty/tools/dotc/core/TypeComparer.scala | 7 ++-- src/dotty/tools/dotc/typer/Applications.scala | 2 +- src/dotty/tools/dotc/typer/Namer.scala | 2 +- src/dotty/tools/dotc/typer/Typer.scala | 7 +--- 5 files changed, 20 insertions(+), 30 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeApplications.scala b/src/dotty/tools/dotc/core/TypeApplications.scala index 57f095e8e724..4a6b238d962c 100644 --- a/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/src/dotty/tools/dotc/core/TypeApplications.scala @@ -140,13 +140,11 @@ class TypeApplications(val self: Type) extends AnyVal { def isInstantiatedLambda(implicit ctx: Context): Boolean = isSafeLambda && typeParams.isEmpty + /** Is receiver type higher-kinded (i.e. of kind != "*")? */ def isHK(implicit ctx: Context): Boolean = self.dealias match { - case self: TypeRef => - self.info match { - case TypeBounds(_, hi) => hi.isHK - case _ => false - } + case self: TypeRef => self.info.isHK case RefinedType(_, name) => name == tpnme.hkApply || name.isLambdaArgName + case TypeBounds(_, hi) => hi.isHK case _ => false } @@ -159,12 +157,6 @@ class TypeApplications(val self: Type) extends AnyVal { println(s"precomplete decls = ${self.typeSymbol.unforcedDecls.toList.map(_.denot).mkString("\n ")}") } val tparam = tparams.head - val arg1 = - if ((tparam is HigherKinded) && !arg.isLambda && arg.typeParams.nonEmpty) { - println(i"missing eta expansion of $arg") - arg.EtaExpand - } - else arg val tp1 = RefinedType(tp, tparam.name, arg.toBounds(tparam)) matchParams(tp1, tparams.tail, args1) case nil => tp @@ -204,19 +196,23 @@ class TypeApplications(val self: Type) extends AnyVal { tp } - def isHK(tp: Type): Boolean = tp match { + /** Same as isHK, except we classify all abstract types as HK, + * (they must be, because the are applied). This avoids some forcing and + * CyclicReference errors of the standard isHK. + */ + def isKnownHK(tp: Type): Boolean = tp match { case tp: TypeRef => val sym = tp.symbol if (sym.isClass) sym.isLambdaTrait - else !sym.isAliasType || isHK(tp.info) - case tp: TypeProxy => isHK(tp.underlying) + else !sym.isAliasType || isKnownHK(tp.info) + case tp: TypeProxy => isKnownHK(tp.underlying) case _ => false } if (args.isEmpty || ctx.erasedTypes) self else { val res = instantiate(self, self) - if (isHK(res)) TypeRef(res, tpnme.hkApply) else res + if (isKnownHK(res)) TypeRef(res, tpnme.hkApply) else res } } @@ -488,9 +484,9 @@ class TypeApplications(val self: Type) extends AnyVal { //.ensuring(res => res.EtaReduce =:= self, s"res = $res, core = ${res.EtaReduce}, self = $self, hc = ${res.hashCode}") } - /** Eta expand if `bound` is a type lambda */ - def EtaExpandIfLambda(bound: Type)(implicit ctx: Context): Type = - if (bound.isLambda && self.typeSymbol.isClass && typeParams.nonEmpty && !isLambda) EtaExpand + /** Eta expand if `bound` is a higher-kinded type */ + def EtaExpandIfHK(bound: Type)(implicit ctx: Context): Type = + if (bound.isHK && !isHK && self.typeSymbol.isClass && typeParams.nonEmpty) EtaExpand else self /** If `self` is an eta expansion of type T, return T, otherwise NoType */ diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index a8598ae443cf..35e4d804bb3a 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -633,11 +633,10 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { /** Does `tp` need to be eta lifted to be comparable to `target`? */ private def needsEtaLift(tp: Type, target: RefinedType): Boolean = { - //default.echo(i"needs eta $tp $target?", { // @@@ rewrite + // if (tp.isLambda != tp.isHK) println(i"discrepancy for $tp, isLambda = ${tp.isLambda}, isHK = ${tp.isHK}") val name = target.refinedName - (name.isLambdaArgName || (name eq tpnme.hkApply)) && target.isLambda && - tp.exists && !tp.isLambda - //}) + (name.isLambdaArgName || (name eq tpnme.hkApply)) && + tp.exists && !tp.isLambda // we do encounter Lambda classes without any arguments here @@@ check whether this makes sense. } /** Narrow gadt.bounds for the type parameter referenced by `tr` to include diff --git a/src/dotty/tools/dotc/typer/Applications.scala b/src/dotty/tools/dotc/typer/Applications.scala index f90f44ae03f5..b2d2b9bd31ee 100644 --- a/src/dotty/tools/dotc/typer/Applications.scala +++ b/src/dotty/tools/dotc/typer/Applications.scala @@ -613,7 +613,7 @@ trait Applications extends Compatibility { self: Typer => } def adaptTypeArg(tree: tpd.Tree, bound: Type)(implicit ctx: Context): tpd.Tree = - tree.withType(tree.tpe.EtaExpandIfLambda(bound)) + tree.withType(tree.tpe.EtaExpandIfHK(bound)) /** Rewrite `new Array[T](....)` trees to calls of newXYZArray methods. */ def convertNewArray(tree: tpd.Tree)(implicit ctx: Context): tpd.Tree = tree match { diff --git a/src/dotty/tools/dotc/typer/Namer.scala b/src/dotty/tools/dotc/typer/Namer.scala index 50373037e730..506b40651a30 100644 --- a/src/dotty/tools/dotc/typer/Namer.scala +++ b/src/dotty/tools/dotc/typer/Namer.scala @@ -810,7 +810,7 @@ class Namer { typer: Typer => val tycon = tp.withoutArgs(args) val tparams = tycon.typeParams if (args.length == tparams.length) { // if lengths differ, problem is caught in typedTypeApply - val args1 = args.zipWithConserve(tparams)((arg, tparam) => arg.EtaExpandIfLambda(tparam.info)) + val args1 = args.zipWithConserve(tparams)((arg, tparam) => arg.EtaExpandIfHK(tparam.info)) if (args1 ne args) return this(tycon).appliedTo(args1) } } diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index 6b05b49638e0..6bed5bef869b 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -1431,12 +1431,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit case Select(New(tpt), nme.CONSTRUCTOR) => tpt.tpe.dealias.argTypesLo case _ => Nil } - if (typeArgs.isEmpty) { - //for ((pname, pbound) <- poly.paramNames.zip(poly.paramBounds)) - // if (pbound.hi.isSafeLambda) - // ctx.error(d"cannot infer argument for higher-kinded type parameter $pname", tree.pos) - typeArgs = constrained(poly, tree)._2 - } + if (typeArgs.isEmpty) typeArgs = constrained(poly, tree)._2 convertNewArray( adaptInterpolated(tree.appliedToTypes(typeArgs), pt, original)) } From ab5522131a4dcec2177bc6b87e4f36ea834f05b6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 20 Jul 2015 15:13:07 +0200 Subject: [PATCH 36/42] Rewrite compareHK to be kind-correct --- src/dotty/tools/dotc/core/TypeApplications.scala | 6 ++++++ src/dotty/tools/dotc/core/TypeComparer.scala | 16 +++++++--------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeApplications.scala b/src/dotty/tools/dotc/core/TypeApplications.scala index 4a6b238d962c..7d69f6b5096f 100644 --- a/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/src/dotty/tools/dotc/core/TypeApplications.scala @@ -148,6 +148,12 @@ class TypeApplications(val self: Type) extends AnyVal { case _ => false } + /** is receiver of the form T#$apply? */ + def isHKApply: Boolean = self match { + case TypeRef(_, name) => name == tpnme.hkApply + case _ => false + } + /** Encode the type resulting from applying this type to given arguments */ final def appliedTo(args: List[Type])(implicit ctx: Context): Type = /*>|>*/ track("appliedTo") /*<|<*/ { def matchParams(tp: Type, tparams: List[TypeSymbol], args: List[Type]): Type = args match { diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index 35e4d804bb3a..9e86a22a7876 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -466,18 +466,16 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { false } - /** If `projection` is of the form T # Apply where `T` is an instance of a Lambda class, - * and `other` is not a type lambda projection, then convert `other` to a type lambda `U`, and + /** If `projection` is a hk projection T#$apply + * and `other` is not a hk projection, then convert `other` to a hk projection `U`, and * continue with `T <:< U` if `inOrder` is true and `U <:< T` otherwise. */ def compareHK(projection: NamedType, other: Type, inOrder: Boolean) = - projection.name == tpnme.hkApply && { // @@@ rewrite - val lambda = projection.prefix.LambdaClass(forcing = true) - lambda.exists && !other.isLambda && - other.testLifted(lambda.typeParams, - if (inOrder) isSubType(projection.prefix, _) else isSubType(_, projection.prefix), - if (inOrder) Nil else classBounds(projection.prefix)) - } + projection.name == tpnme.hkApply && + !other.isHKApply && + other.testLifted(projection.prefix.LambdaClass(forcing = true).typeParams, + if (inOrder) isSubType(projection.prefix, _) else isSubType(_, projection.prefix), + if (inOrder) Nil else classBounds(projection.prefix)) /** The class symbols bounding the type of the `Apply` member of `tp` */ private def classBounds(tp: Type) = tp.member(tpnme.hkApply).info.classSymbols From 03be249f6aa60376b080375ce96888736aa3fa43 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 20 Jul 2015 18:12:04 +0200 Subject: [PATCH 37/42] Fix problem in typeParams Accidentally forwarded to rawTypeParams. This solved the problem with mismatching type params in appliedTo that was caught in testLifted. --- .../tools/dotc/core/TypeApplications.scala | 19 +++++++------------ src/dotty/tools/dotc/core/TypeComparer.scala | 2 +- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeApplications.scala b/src/dotty/tools/dotc/core/TypeApplications.scala index 7d69f6b5096f..d2fba2895f8b 100644 --- a/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/src/dotty/tools/dotc/core/TypeApplications.scala @@ -59,7 +59,7 @@ class TypeApplications(val self: Type) extends AnyVal { case tp: TypeRef => val tsym = tp.typeSymbol if (tsym.isClass) tsym.typeParams - else if (tsym.isAliasType) tp.underlying.rawTypeParams + else if (tsym.isAliasType) tp.underlying.typeParams else { val lam = LambdaClass(forcing = false) if (lam.exists) lam.typeParams else Nil @@ -524,12 +524,12 @@ class TypeApplications(val self: Type) extends AnyVal { } } - /** Test whether this type has a base type of the form `B[T1, ..., Bn]` where + /** Test whether this type has a base type of the form `B[T1, ..., Tn]` where * the type parameters of `B` match one-by-one the variances of `tparams`, * and where the lambda abstracted type * - * LambdaXYZ { type Apply = B[$hkArg$0, ..., $hkArg$n] } - * { type $hkArg$0 = T1; ...; type $hkArg$n = Tn } + * LambdaXYZ { type Apply = B[$hkArg$0, ..., $hkArg${n-1}] } + * { type $hkArg$0 = T1; ...; type $hkArg${n-1} = Tn } * * satisfies predicate `p`. Try base types in the order of their occurrence in `baseClasses`. * A type parameter matches a variance V if it has V as its variance or if V == 0. @@ -559,13 +559,8 @@ class TypeApplications(val self: Type) extends AnyVal { case nil => false } - try { // temporary, to avoid type mismatches in applications. Should come back to this - // when subtyping is rewritten to account for new hk-scheme. - if (tparams.isEmpty) false - else if (typeParams.nonEmpty) p(EtaExpand) || classBounds.nonEmpty && tryLift(self.baseClasses) - else classBounds.nonEmpty && tryLift(self.baseClasses) - } catch { - case ex: NoSuchElementException => false - } + tparams.nonEmpty && + (typeParams.nonEmpty && p(EtaExpand) || + classBounds.nonEmpty && tryLift(self.baseClasses)) } } diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index 9e86a22a7876..eb1dcacdd7e5 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -634,7 +634,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { // if (tp.isLambda != tp.isHK) println(i"discrepancy for $tp, isLambda = ${tp.isLambda}, isHK = ${tp.isHK}") val name = target.refinedName (name.isLambdaArgName || (name eq tpnme.hkApply)) && - tp.exists && !tp.isLambda // we do encounter Lambda classes without any arguments here @@@ check whether this makes sense. + tp.exists && !tp.isLambda // we do encounter Lambda classes without any arguments here } /** Narrow gadt.bounds for the type parameter referenced by `tr` to include From 9c75b53a9420df82657b5c2293043cd996bc1b07 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 21 Jul 2015 18:31:32 +0200 Subject: [PATCH 38/42] Generalize eta reduction to partially applied types. Now also allows to reduce something like ([T] => Map[T, String]) to Map[_, String] --- src/dotty/tools/dotc/core/TypeApplications.scala | 8 ++++++-- src/dotty/tools/dotc/core/TypeComparer.scala | 7 +++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeApplications.scala b/src/dotty/tools/dotc/core/TypeApplications.scala index d2fba2895f8b..b5a218e0d515 100644 --- a/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/src/dotty/tools/dotc/core/TypeApplications.scala @@ -505,9 +505,12 @@ class TypeApplications(val self: Type) extends AnyVal { tp.refinedInfo match { case TypeAlias(TypeRef(RefinedThis(rt), rname)) if (rname == tparam.name) && (rt eq self) => + // todo: add bounds etaCore(tp.parent, otherParams) case _ => - NoType + val pcore = etaCore(tp.parent, tparams) + if (pcore.exists) tp.derivedRefinedType(pcore, tp.refinedName, tp.refinedInfo) + else NoType } case _ => NoType @@ -517,7 +520,8 @@ class TypeApplications(val self: Type) extends AnyVal { case self @ RefinedType(parent, tpnme.hkApply) => val lc = parent.LambdaClass(forcing = false) self.refinedInfo match { - case TypeAlias(alias) if lc.exists => etaCore(alias, lc.typeParams.reverse) + case TypeAlias(alias) if lc.exists => + etaCore(alias, lc.typeParams.reverse) case _ => NoType } case _ => NoType diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index eb1dcacdd7e5..c9b1e87947ae 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -1232,5 +1232,12 @@ class ExplainingTypeComparer(initctx: Context) extends TypeComparer(initctx) { override def copyIn(ctx: Context) = new ExplainingTypeComparer(ctx) + override def compareHK(projection: NamedType, other: Type, inOrder: Boolean) = + if (projection.name == tpnme.hkApply) + traceIndented(i"compareHK $projection, $other, $inOrder") { + super.compareHK(projection, other, inOrder) + } + else super.compareHK(projection, other, inOrder) + override def toString = "Subtype trace:" + { try b.toString finally b.clear() } } From fecd591063b1949a8c04cf9899a256e3893f0a62 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 21 Jul 2015 18:42:17 +0200 Subject: [PATCH 39/42] Disallow wildcard arguments to higher-kinded types... ...unless the HK type can be eta-reduced to a class type. --- src/dotty/tools/dotc/typer/Typer.scala | 29 ++++++++++++++++++++- test/dotc/tests.scala | 1 + tests/neg/partialApplications.scala | 11 ++++++++ tests/pos/partialApplications.scala | 35 +++++++++++++++++++++++++- 4 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 tests/neg/partialApplications.scala diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index 6bed5bef869b..868ef4e16303 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -841,7 +841,34 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit } val args1 = args.zipWithConserve(tparams)(typedArg(_, _)).asInstanceOf[List[Tree]] // check that arguments conform to bounds is done in phase PostTyper - assignType(cpy.AppliedTypeTree(tree)(tpt1, args1), tpt1, args1) + val tree1 = assignType(cpy.AppliedTypeTree(tree)(tpt1, args1), tpt1, args1) + if (tree1.tpe.isHKApply) + for (arg @ TypeBoundsTree(_, _) <- args1) + ctx.error("illegal wildcard type argument; does not correspond to type parameter of a class", arg.pos) + // The reason for outlawing such arguments is illustrated by the following example. + // Say we have + // + // type RMap[A, B] = Map[B, A] + // + // Then + // + // Rmap[_, Int] + // + // translates to + // + // Lambda$I { type hk$0; type hk$1 = Int; type $apply = Map[$hk1, $hk0] } # $apply + // + // Let's call the last type T. You would expect that + // + // Map[Int, String] <: RMap[_, Int] + // + // But that's not the case given the standard subtyping rules. In fact, the rhs reduces to + // + // Map[Int, T # $hk0] + // + // That means the second argument to `Map` is unknown and String is certainly not a subtype of it. + // To avoid the surprise we outlaw problematic wildcard arguments from the start. + tree1 } } diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index 9f731277d924..8150be6b1a99 100644 --- a/test/dotc/tests.scala +++ b/test/dotc/tests.scala @@ -147,6 +147,7 @@ class tests extends CompilerTest { @Test def neg_traitParamsMixin = compileFile(negDir, "traitParamsMixin", xerrors = 2) @Test def neg_firstError = compileFile(negDir, "firstError", xerrors = 3) @Test def neg_implicitLowerBound = compileFile(negDir, "implicit-lower-bound", xerrors = 1) + @Test def neg_partialApplications = compileFile(negDir, "partialApplications", xerrors = 8) @Test def run_all = runFiles(runDir) diff --git a/tests/neg/partialApplications.scala b/tests/neg/partialApplications.scala new file mode 100644 index 000000000000..67f6cf059ad0 --- /dev/null +++ b/tests/neg/partialApplications.scala @@ -0,0 +1,11 @@ +object Test { + + type RMap[X, Y] = Map[Y, X] + val m = Map[Int, String]() + val ts: RMap[_, Int] = m // erorr // error + val us: RMap[String, _] = m // error // error + val vs: RMap[_, _] = m // error // error // error + val zz: RMap = m // error + +} + diff --git a/tests/pos/partialApplications.scala b/tests/pos/partialApplications.scala index c1df1dee2035..285dc86613cc 100644 --- a/tests/pos/partialApplications.scala +++ b/tests/pos/partialApplications.scala @@ -8,6 +8,39 @@ object Test { val ys: StringlyHistogram[String] = xs - val zs: StringlyHistogram = xs + def e = xs + + val zs: StringlyHistogram[_] = e + + type IntMap[Y] = Map[Int, Y] + + val is = Map[Int, Boolean]() + + val js: IntMap[Boolean] = is + + val ks: IntMap[_] = is + + type RMap[X, Y] = Map[Y, X] + + val rs = Map[Int, Float]() + + val ss: RMap[Float, Int] = rs + +} + +object Test2 { + type Histogram = Map[_, Int] + + type StringlyHistogram = Histogram[_ >: String] // error + + val xs: Histogram[String] = Map[String, Int]() // error + + val ys: StringlyHistogram[String] = xs // error + + val zs: StringlyHistogram = xs // error + + val xs1 = xs + val ys1 = ys + val zs1 = zs } From 140eb104095f1aa28921ea9db7fbaffbcf05e4f0 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 21 Jul 2015 19:53:28 +0200 Subject: [PATCH 40/42] Keep track of parameter bounds in LambdaAbstract and EtaReduce 1) Lambda abstract now records bounds of abstracted type parameters in TypeLambda 2) Eta-reduce likewise keeps the bounds it finds in the TypeLambda 3) Eta-reduce now also translates hk$i references to type parameters of the reduced type. --- .../tools/dotc/core/TypeApplications.scala | 77 ++++++++++++++++--- 1 file changed, 66 insertions(+), 11 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeApplications.scala b/src/dotty/tools/dotc/core/TypeApplications.scala index b5a218e0d515..42da617d624f 100644 --- a/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/src/dotty/tools/dotc/core/TypeApplications.scala @@ -444,11 +444,14 @@ class TypeApplications(val self: Type) extends AnyVal { /** The typed lambda abstraction of this type `T` relative to `boundSyms`. * This is: * - * LambdaXYZ{ type Apply = subst(T) } + * LambdaXYZ{ bounds }{ type Apply = toHK(T) } * - * where XYZ reflets that variances of the bound symbols and - * `subst` is a substitution that replaces every bound symbol sym_i by - * `this.Arg$i`. + * where + * - XYZ reflects the variances of the bound symbols, + * - `bounds` consists of type declarations `type hk$i >: toHK(L) <: toHK(U), + * one for each type parameter in `T` with non-trivial bounds L,U. + * - `toHK` is a substitution that replaces every bound symbol sym_i by + * `this.Arg$i`. * * TypeBounds are lambda abstracting by lambda abstracting their upper bound. * @@ -458,9 +461,9 @@ class TypeApplications(val self: Type) extends AnyVal { * with new hk-scheme. */ def LambdaAbstract(boundSyms: List[Symbol], cycleParanoid: Boolean = false)(implicit ctx: Context): Type = { - def expand(tp: Type) = { + def expand(tp: Type): Type = { val lambda = defn.LambdaTrait(boundSyms.map(_.variance)) - val substitutedRHS = (rt: RefinedType) => { + def toHK(tp: Type) = (rt: RefinedType) => { val argRefs = boundSyms.indices.toList.map(i => RefinedThis(rt).select(tpnme.LambdaArgName(i))) val substituted = @@ -468,7 +471,18 @@ class TypeApplications(val self: Type) extends AnyVal { else tp.subst(boundSyms, argRefs) substituted.bounds.withVariance(1) } - RefinedType(lambda.typeRef, tpnme.hkApply, substitutedRHS) + val boundNames = new mutable.ListBuffer[Name] + val boundss = new mutable.ListBuffer[TypeBounds] + for (sym <- boundSyms) { + val bounds = sym.info.bounds + if (!(TypeBounds.empty <:< bounds)) { + boundNames += sym.name + boundss += bounds + } + } + val lambdaWithBounds = + RefinedType.make(lambda.typeRef, boundNames.toList, boundss.toList.map(toHK)) + RefinedType(lambdaWithBounds, tpnme.hkApply, toHK(tp)) } self match { case self @ TypeBounds(lo, hi) => @@ -495,7 +509,29 @@ class TypeApplications(val self: Type) extends AnyVal { if (bound.isHK && !isHK && self.typeSymbol.isClass && typeParams.nonEmpty) EtaExpand else self - /** If `self` is an eta expansion of type T, return T, otherwise NoType */ + /** If `self` is a (potentially partially instantiated) eta expansion of type T, return T, + * otherwise NoType. More precisely if `self` is of the form + * + * T { type $apply = U[T1, ..., Tn] } + * + * where + * + * - hk$0, ..., hk${m-1} are the type parameters of T + * - a sublist of the arguments Ti_k (k = 0,...,m_1) are of the form T{...}.this.hk$i_k + * + * rewrite `self` to + * + * U[T'1,...T'j] + * + * where + * + * T'j = _ >: Lj <: Uj if j is in the i_k list defined above + * where Lj and Uj are the bounds of hk$j mapped using `fromHK`. + * = fromHK(Tj) otherwise. + * + * `fromHK` is the function that replaces every occurrence of `.this.hk$i` by the + * corresponding parameter reference in `U[T'1,...T'j]` + */ def EtaReduce(implicit ctx: Context): Type = { def etaCore(tp: Type, tparams: List[Symbol]): Type = tparams match { case Nil => tp @@ -505,8 +541,11 @@ class TypeApplications(val self: Type) extends AnyVal { tp.refinedInfo match { case TypeAlias(TypeRef(RefinedThis(rt), rname)) if (rname == tparam.name) && (rt eq self) => - // todo: add bounds - etaCore(tp.parent, otherParams) + // we have a binding T = Lambda$XYZ{...}.this.hk$i where hk$i names the current `tparam`. + val pcore = etaCore(tp.parent, otherParams) + val hkBounds = self.member(rname).info.bounds + if (TypeBounds.empty <:< hkBounds) pcore + else tp.derivedRefinedType(pcore, tp.refinedName, hkBounds) case _ => val pcore = etaCore(tp.parent, tparams) if (pcore.exists) tp.derivedRefinedType(pcore, tp.refinedName, tp.refinedInfo) @@ -516,12 +555,28 @@ class TypeApplications(val self: Type) extends AnyVal { NoType } } + // Map references `Lambda$XYZ{...}.this.hk$i to corresponding parameter references of the reduced core. + def fromHK(reduced: Type) = reduced match { + case reduced: RefinedType => + new TypeMap { + def apply(tp: Type): Type = tp match { + case TypeRef(RefinedThis(binder), name) if binder eq self => + assert(name.isLambdaArgName) + RefinedThis(reduced).select(reduced.typeParams.apply(name.LambdaArgIndex)) + case _ => + mapOver(tp) + } + }.apply(reduced) + case _ => + reduced + } + self match { case self @ RefinedType(parent, tpnme.hkApply) => val lc = parent.LambdaClass(forcing = false) self.refinedInfo match { case TypeAlias(alias) if lc.exists => - etaCore(alias, lc.typeParams.reverse) + fromHK(etaCore(alias, lc.typeParams.reverse)) case _ => NoType } case _ => NoType From fd0fa8e23cbee96f7df24a1e586b4af2e1c5ce3c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 22 Jul 2015 16:03:14 +0200 Subject: [PATCH 41/42] Avoid orphan RefinedThis types in glb/lub. --- src/dotty/tools/dotc/core/TypeComparer.scala | 4 ++-- src/dotty/tools/dotc/core/tasty/TreePickler.scala | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index c9b1e87947ae..fce803c56866 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -929,7 +929,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { tp1.derivedRefinedType( tp1.parent & tp2.parent, tp1.refinedName, - tp1.refinedInfo & tp2.refinedInfo) + tp1.refinedInfo & tp2.refinedInfo.substRefinedThis(tp2, RefinedThis(tp1))) case _ => NoType } @@ -995,7 +995,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { tp1.derivedRefinedType( tp1.parent | tp2.parent, tp1.refinedName, - tp1.refinedInfo | tp2.refinedInfo) + tp1.refinedInfo | tp2.refinedInfo.substRefinedThis(tp2, RefinedThis(tp1))) case _ => NoType } diff --git a/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/src/dotty/tools/dotc/core/tasty/TreePickler.scala index d50817b60e19..58697c196b36 100644 --- a/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -201,7 +201,7 @@ class TreePickler(pickler: TastyPickler) { case tpe: RefinedThis => writeByte(REFINEDthis) val binderAddr = pickledTypes.get(tpe.binder) - assert(binderAddr != null) + assert(binderAddr != null, tpe.binder) writeRef(binderAddr.asInstanceOf[Addr]) case tpe: SkolemType => pickleType(tpe.info) From 887f815e6254436c79b96e2668911355c342e739 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 22 Jul 2015 16:05:11 +0200 Subject: [PATCH 42/42] Move failing test to pending. The original IterableSelfRec is not syntactically legal after the hk changes. I attempted to fix, but there's still a type error. Need to investigate whether this is a true error or a bug. --- tests/{ => pending}/pos/IterableSelfRec.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename tests/{ => pending}/pos/IterableSelfRec.scala (84%) diff --git a/tests/pos/IterableSelfRec.scala b/tests/pending/pos/IterableSelfRec.scala similarity index 84% rename from tests/pos/IterableSelfRec.scala rename to tests/pending/pos/IterableSelfRec.scala index bba7a82d252a..a97833991785 100644 --- a/tests/pos/IterableSelfRec.scala +++ b/tests/pending/pos/IterableSelfRec.scala @@ -22,9 +22,9 @@ trait Seq[T] extends Iterable[T] { self => def apply(x: Int): T } -abstract class CollectionCompanion[+CC <: Collection { type This <: CC }] +abstract class CollectionCompanion[+CC[X] <: Collection[X] { type This <: CC }] -abstract class IterableCompanion[+CC <: Iterable { type This <: CC }] extends CollectionCompanion[CC] { +abstract class IterableCompanion[+CC[X] <: Iterable[X] { type This <: CC }] extends CollectionCompanion[CC] { def fromIterator[T](it: Iterator[T]): CC[T] def map[T, U](xs: Iterable[T], f: T => U): CC[U] = fromIterator(xs.iterator.map(f)) @@ -36,7 +36,7 @@ abstract class IterableCompanion[+CC <: Iterable { type This <: CC }] extends Co implicit def transformOps[T](xs: CC[T] @uncheckedVariance): TransformOps[CC, T] = ??? // new TransformOps[CC, T](xs) } -class TransformOps[+CC <: Iterable { type This <: CC }, T] (val xs: CC[T]) extends AnyVal { +class TransformOps[+CC[X] <: Iterable[X] { type This <: CC }, T] (val xs: CC[T]) extends AnyVal { def companion[T](xs: CC[T] @uncheckedVariance): IterableCompanion[CC] = xs.companion def map[U](f: T => U): CC[U] = companion(xs).map(xs, f) def filter(p: T => Boolean): CC[T] = companion(xs).filter(xs, p)