From 5968e9f6154cc8642fc3f6cef636a12104dd7a26 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 22 Jul 2016 00:12:03 +0200 Subject: [PATCH 1/3] Fix parameterized typedefs with lambdas as rhs Previously the compiler crashed when faced with a parameterized typedef that has a lambda as rhs. We fix this by refining the condition when not to abstract in typeDefsig. --- src/dotty/tools/dotc/typer/Namer.scala | 14 ++++++-------- tests/pos/nestedLambdas.scala | 9 +++++++++ 2 files changed, 15 insertions(+), 8 deletions(-) create mode 100644 tests/pos/nestedLambdas.scala diff --git a/src/dotty/tools/dotc/typer/Namer.scala b/src/dotty/tools/dotc/typer/Namer.scala index 3b193d2dba4e..666047db2873 100644 --- a/src/dotty/tools/dotc/typer/Namer.scala +++ b/src/dotty/tools/dotc/typer/Namer.scala @@ -934,15 +934,11 @@ class Namer { typer: Typer => } def typeDefSig(tdef: TypeDef, sym: Symbol, tparamSyms: List[TypeSymbol])(implicit ctx: Context): Type = { - val isDerived = tdef.rhs.isInstanceOf[untpd.DerivedTypeTree] - //val toParameterize = tparamSyms.nonEmpty && !isDerived - //val needsLambda = sym.allOverriddenSymbols.exists(_ is HigherKinded) && !isDerived - def abstracted(tp: Type): Type = - if (tparamSyms.nonEmpty && !tp.isHK) tp.LambdaAbstract(tparamSyms) - //else if (toParameterize) tp.parameterizeWith(tparamSyms) + def abstracted(tp: Type, canAbstract: Boolean): Type = + if (tparamSyms.nonEmpty && canAbstract) tp.LambdaAbstract(tparamSyms) else tp - val dummyInfo = abstracted(TypeBounds.empty) + val dummyInfo = abstracted(TypeBounds.empty, canAbstract = true) sym.info = dummyInfo // Temporarily set info of defined type T to ` >: Nothing <: Any. // This is done to avoid cyclic reference errors for F-bounds. @@ -954,7 +950,9 @@ 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 = abstracted(typedAheadType(tdef.rhs).tpe) + + val isDerived = tdef.rhs.isInstanceOf[untpd.DerivedTypeTree] + val rhsType = abstracted(typedAheadType(tdef.rhs).tpe, canAbstract = !isDerived) val unsafeInfo = rhsType match { case bounds: TypeBounds => bounds case alias => TypeAlias(alias, if (sym is Local) sym.variance else 0) diff --git a/tests/pos/nestedLambdas.scala b/tests/pos/nestedLambdas.scala new file mode 100644 index 000000000000..0e186d193ab8 --- /dev/null +++ b/tests/pos/nestedLambdas.scala @@ -0,0 +1,9 @@ +class Test { + + type T = [X] -> [Y] -> (X, Y) + + type A[X] = [Y] -> (X, Y) + + type B[X] = (X, X) + +} From 013101d9f77d8283007911c61aaae7ac9cc2d486 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 22 Jul 2016 15:22:13 +0200 Subject: [PATCH 2/3] Take curried type lambdas into account Adapt operations TypeApplications and TypeComparer to account for the possibilities of curried type lambdas. --- .../tools/dotc/core/TypeApplications.scala | 7 +++---- src/dotty/tools/dotc/core/TypeComparer.scala | 19 ++++++++++++++----- src/dotty/tools/dotc/core/Types.scala | 1 - tests/pos/nestedLambdas.scala | 12 ++++++++++++ 4 files changed, 29 insertions(+), 10 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeApplications.scala b/src/dotty/tools/dotc/core/TypeApplications.scala index cb11d3fdc398..af362f4daef9 100644 --- a/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/src/dotty/tools/dotc/core/TypeApplications.scala @@ -223,12 +223,12 @@ class TypeApplications(val self: Type) extends AnyVal { self.parent.typeParams.filterNot(_.paramName == self.refinedName) case self: RecType => self.parent.typeParams - case _: HKApply | _: SingletonType => + case _: SingletonType => Nil case self: WildcardType => self.optBounds.typeParams case self: TypeProxy => - self.underlying.typeParams + self.superType.typeParams case _ => Nil } @@ -312,14 +312,13 @@ class TypeApplications(val self: Type) extends AnyVal { case self: TypeRef => self.info.isHK case self: RefinedType => false case self: TypeLambda => true - case self: HKApply => false case self: SingletonType => false case self: TypeVar => // Using `origin` instead of `underlying`, as is done for typeParams, // avoids having to set ephemeral in some cases. self.origin.isHK case self: WildcardType => self.optBounds.isHK - case self: TypeProxy => self.underlying.isHK + case self: TypeProxy => self.superType.isHK case _ => false } diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index e984970b4a38..18eb424bc8c8 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -665,6 +665,13 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { } } + /** Fall back to comparing either with `fourthTry` or against the lower + * approximation of the rhs. + * @param tyconLo The type constructor's lower approximation. + */ + def fallback(tyconLo: Type) = + either(fourthTry(tp1, tp2), isSubType(tp1, tyconLo.applyIfParameterized(args2))) + /** Let `tycon2bounds` be the bounds of the RHS type constructor `tycon2`. * Let `app2 = tp2` where the type constructor of `tp2` is replaced by * `tycon2bounds.lo`. @@ -674,13 +681,13 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { * tp1 <:< tp2 using fourthTry (this might instantiate params in tp1) * tp1 <:< app2 using isSubType (this might instantiate params in tp2) */ - def compareLower(tycon2bounds: TypeBounds, tyconIsTypeRef: Boolean): Boolean = { - def app2 = tycon2bounds.lo.applyIfParameterized(args2) + def compareLower(tycon2bounds: TypeBounds, tyconIsTypeRef: Boolean): Boolean = if (tycon2bounds.lo eq tycon2bounds.hi) - isSubType(tp1, if (tyconIsTypeRef) tp2.superType else app2) + isSubType(tp1, + if (tyconIsTypeRef) tp2.superType + else tycon2bounds.lo.applyIfParameterized(args2)) else - either(fourthTry(tp1, tp2), isSubType(tp1, app2)) - } + fallback(tycon2bounds.lo) tycon2 match { case param2: PolyParam => @@ -693,6 +700,8 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { compareLower(tycon2.info.bounds, tyconIsTypeRef = true) case _: TypeVar | _: AnnotatedType => isSubType(tp1, tp2.superType) + case tycon2: HKApply => + fallback(tycon2.lowerBound) case _ => false } diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 11da27265289..bbb23d32fc0a 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -2653,7 +2653,6 @@ object Types { cachedSuper } - /* (Not needed yet) */ def lowerBound(implicit ctx: Context) = tycon.stripTypeVar match { case tycon: TypeRef => tycon.info match { diff --git a/tests/pos/nestedLambdas.scala b/tests/pos/nestedLambdas.scala index 0e186d193ab8..58be1ae2f6e0 100644 --- a/tests/pos/nestedLambdas.scala +++ b/tests/pos/nestedLambdas.scala @@ -6,4 +6,16 @@ class Test { type B[X] = (X, X) + val x: T[Int][Boolean] = ??? + + val y: A[Int][Boolean] = x + + def f[X <: T[Int]] = ??? + + f[A[Int]] + + def g[X <: T] = ??? + + g[A] + } From 006748d895ade64b0b63916996eb7df8258def18 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 22 Jul 2016 16:22:00 +0200 Subject: [PATCH 3/3] Simplify abstracted As @smarter noted, makes no sense to use a boolean parameter here. We got there by refactoring blindness. --- src/dotty/tools/dotc/typer/Namer.scala | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/dotty/tools/dotc/typer/Namer.scala b/src/dotty/tools/dotc/typer/Namer.scala index 666047db2873..2c08f238daa9 100644 --- a/src/dotty/tools/dotc/typer/Namer.scala +++ b/src/dotty/tools/dotc/typer/Namer.scala @@ -934,11 +934,10 @@ class Namer { typer: Typer => } def typeDefSig(tdef: TypeDef, sym: Symbol, tparamSyms: List[TypeSymbol])(implicit ctx: Context): Type = { - def abstracted(tp: Type, canAbstract: Boolean): Type = - if (tparamSyms.nonEmpty && canAbstract) tp.LambdaAbstract(tparamSyms) - else tp + def abstracted(tp: Type): Type = + if (tparamSyms.nonEmpty) tp.LambdaAbstract(tparamSyms) else tp - val dummyInfo = abstracted(TypeBounds.empty, canAbstract = true) + val dummyInfo = abstracted(TypeBounds.empty) sym.info = dummyInfo // Temporarily set info of defined type T to ` >: Nothing <: Any. // This is done to avoid cyclic reference errors for F-bounds. @@ -952,7 +951,8 @@ class Namer { typer: Typer => // inspects a TypeRef's info, instead of simply dealiasing alias types. val isDerived = tdef.rhs.isInstanceOf[untpd.DerivedTypeTree] - val rhsType = abstracted(typedAheadType(tdef.rhs).tpe, canAbstract = !isDerived) + val rhsBodyType = typedAheadType(tdef.rhs).tpe + val rhsType = if (isDerived) rhsBodyType else abstracted(rhsBodyType) val unsafeInfo = rhsType match { case bounds: TypeBounds => bounds case alias => TypeAlias(alias, if (sym is Local) sym.variance else 0)