From 51563aee5478f90a0f86c29385f74d020b8995dd Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 12 Jun 2014 12:46:57 +0200 Subject: [PATCH 01/23] Change definition of isLegalPrefix so that it allows projecting on an abstract type. This is needed to make the encoding og higher-kinded types work. E.g. Rep[Int] would be represented as Rep { type Arg$0 = Int } # Apply where Apply is an abstract member of the base class Lambfa$I of Rep. --- src/dotty/tools/dotc/core/Types.scala | 7 ++++--- src/dotty/tools/dotc/typer/Checking.scala | 6 +++--- src/dotty/tools/dotc/typer/Typer.scala | 2 +- test/dotc/tests.scala | 1 + tests/neg/projections.scala | 7 +++++++ tests/pos/projections.scala | 14 ++++++++++++++ 6 files changed, 30 insertions(+), 7 deletions(-) create mode 100644 tests/neg/projections.scala create mode 100644 tests/pos/projections.scala diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 89facfee52c0..827e851a9dd4 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -117,14 +117,15 @@ object Types { } /** A type T is a legal prefix in a type selection T#A if - * T is stable or T contains no abstract types + * T is stable or T contains no abstract types except possibly A. * !!! Todo: What about non-final vals that contain abstract types? */ - final def isLegalPrefix(implicit ctx: Context): Boolean = + final def isLegalPrefixFor(selector: Name)(implicit ctx: Context): Boolean = isStable || { val absTypeNames = memberNames(abstractTypeNameFilter) if (absTypeNames.nonEmpty) typr.println(s"abstract type members of ${this.showWithUnderlying()}: $absTypeNames") - absTypeNames.isEmpty + absTypeNames.isEmpty || + absTypeNames.head == selector && absTypeNames.tail.isEmpty } /** Is this type guaranteed not to have `null` as a value? diff --git a/src/dotty/tools/dotc/typer/Checking.scala b/src/dotty/tools/dotc/typer/Checking.scala index 5e52c5d7e0cd..7da00e051367 100644 --- a/src/dotty/tools/dotc/typer/Checking.scala +++ b/src/dotty/tools/dotc/typer/Checking.scala @@ -51,8 +51,8 @@ trait Checking { /** Check that type `tp` is a legal prefix for '#'. * @return The type itself */ - def checkLegalPrefix(tp: Type, pos: Position)(implicit ctx: Context): Unit = - if (!tp.isLegalPrefix) ctx.error(d"$tp is not a valid prefix for '#'", pos) + def checkLegalPrefix(tp: Type, selector: Name, pos: Position)(implicit ctx: Context): Unit = + if (!tp.isLegalPrefixFor(selector)) ctx.error(d"$tp is not a valid prefix for '# $selector'", pos) /** Check that `tp` is a class type with a stable prefix. Also, if `isFirst` is * false check that `tp` is a trait. @@ -139,7 +139,7 @@ trait NoChecking extends Checking { override def checkValue(tree: Tree, proto: Type)(implicit ctx: Context): tree.type = tree override def checkBounds(args: List[tpd.Tree], poly: PolyType, pos: Position)(implicit ctx: Context): Unit = () override def checkStable(tp: Type, pos: Position)(implicit ctx: Context): Unit = () - override def checkLegalPrefix(tp: Type, pos: Position)(implicit ctx: Context): Unit = () + override def checkLegalPrefix(tp: Type, selector: Name, pos: Position)(implicit ctx: Context): Unit = () override def checkClassTypeWithStablePrefix(tp: Type, pos: Position, traitReq: Boolean)(implicit ctx: Context): Type = tp override def checkImplicitParamsNotSingletons(vparamss: List[List[ValDef]])(implicit ctx: Context): Unit = () override def checkFeasible(tp: Type, pos: Position, where: => String = "")(implicit ctx: Context): Type = tp diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index d6b724270e80..c01cf714f8c1 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -277,7 +277,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit def typedSelectFromTypeTree(tree: untpd.SelectFromTypeTree, pt: Type)(implicit ctx: Context): SelectFromTypeTree = track("typedSelectFromTypeTree") { val qual1 = typedType(tree.qualifier, selectionProto(tree.name, pt, this)) - checkLegalPrefix(qual1.tpe, qual1.pos) + checkLegalPrefix(qual1.tpe, tree.name, qual1.pos) assignType(cpy.SelectFromTypeTree(tree, qual1, tree.name), qual1) } diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index c7b0dc044169..64d520500e7e 100644 --- a/test/dotc/tests.scala +++ b/test/dotc/tests.scala @@ -67,6 +67,7 @@ class tests extends CompilerTest { @Test def neg_autoTupling = compileFile(posDir, "autoTuplingTest", "-language:noAutoTupling" :: Nil, xerrors = 4) @Test def neg_autoTupling2 = compileFile(negDir, "autoTuplingTest", xerrors = 4) @Test def neg_companions = compileFile(negDir, "companions", xerrors = 1) + @Test def neg_projections = compileFile(negDir, "projections", xerrors = 1) @Test def neg_i39 = compileFile(negDir, "i39", xerrors = 1) @Test def neg_i50_volatile = compileFile(negDir, "i50-volatile", xerrors = 4) @Test def neg_t0273_doubledefs = compileFile(negDir, "t0273", xerrors = 1) diff --git a/tests/neg/projections.scala b/tests/neg/projections.scala new file mode 100644 index 000000000000..5d80e115146b --- /dev/null +++ b/tests/neg/projections.scala @@ -0,0 +1,7 @@ +class projections { + + class Lambda { type Arg; type Apply } + + var x: (Lambda { type Apply = Int }) # Apply = _ // error: illegal prefix + +} diff --git a/tests/pos/projections.scala b/tests/pos/projections.scala new file mode 100644 index 000000000000..894a00bcf7f4 --- /dev/null +++ b/tests/pos/projections.scala @@ -0,0 +1,14 @@ +class projections { + + class Lambda { type Arg; type Apply } + + var x: (Lambda { type Apply = Int; type Arg = String }) # Apply = _ + var y: Int = _ + x = y + y = x + + var xx: (Lambda { type Apply = Arg } { type Arg = Int }) # Apply = _ + xx = y + y = xx + +} From 96196c9ffa9939acd7437103d1621dac96e9abc6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 12 Jun 2014 14:50:05 +0200 Subject: [PATCH 02/23] Names and definitions for Lambdas Adding names and definitions for the Lambda scheme to hk types. Also add HigherKinded flag for HK type parameters and abstract types. --- src/dotty/tools/dotc/core/Definitions.scala | 62 ++++++++++++++++++++- src/dotty/tools/dotc/core/Flags.scala | 7 ++- src/dotty/tools/dotc/core/NameOps.scala | 12 ++-- src/dotty/tools/dotc/core/StdNames.scala | 13 +++-- 4 files changed, 81 insertions(+), 13 deletions(-) diff --git a/src/dotty/tools/dotc/core/Definitions.scala b/src/dotty/tools/dotc/core/Definitions.scala index 6f34efc8bcc8..3f567d87a032 100644 --- a/src/dotty/tools/dotc/core/Definitions.scala +++ b/src/dotty/tools/dotc/core/Definitions.scala @@ -422,7 +422,7 @@ class Definitions { def functionArity(tp: Type) = tp.dealias.argInfos.length - 1 // ----- Higher kinds machinery ------------------------------------------ - + // tbr private var _hkTraits: Set[Symbol] = Set() /** The set of HigherKindedXYZ traits encountered so far */ @@ -476,6 +476,66 @@ class Definitions { hkTraitOfArity.getOrElseUpdate(vcs, createTrait) } + // ----- LambdaXYZ traits ------------------------------------------ + + private var myLambdaTraits: Set[Symbol] = Set() + + /** The set of HigherKindedXYZ traits encountered so far */ + def lambdaTraits: Set[Symbol] = myLambdaTraits + + 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 + * would be generated by the following schema. + * + * class LambdaXYZ { type v_1 Arg1; ...; type v_N ArgN; type Apply } + * + * Here: + * + * - XYZ is a string of length N with one letter for each variant of a bound symbols, + * using `P` (positive variance), `N` (negative variance), `I` (invariant). + * - v_i are the variances of the bound symbols (i.e. +, -, or empty). + */ + def lambdaTrait(vcs: List[Int]): ClassSymbol = { + assert(vcs.nonEmpty) + + def varianceFlags(v: Int) = v match { + case -1 => Contravariant + case 0 => EmptyFlags + case 1 => Covariant + } + + val completer = new LazyType { + def complete(denot: SymDenotation)(implicit ctx: Context): Unit = { + 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.Apply, EmptyFlags, paramDecls) + val parentTraitRefs = + for (i <- 0 until vcs.length if vcs(i) != 0) + yield lambdaTrait(vcs.updated(i, 0)).typeRef + denot.info = ClassInfo( + ScalaPackageClass.thisType, cls, ObjectClass.typeRef :: parentTraitRefs.toList, paramDecls) + } + } + + val traitName = tpnme.lambdaTraitName(vcs) + + def createTrait = { + val cls = newClassSymbol( + ScalaPackageClass, + traitName, + Trait | Interface | Synthetic, + completer) + myLambdaTraits += cls + cls + } + + lambdaTraitForVariances.getOrElseUpdate(vcs, createTrait) + } + // ----- primitive value class machinery ------------------------------------------ lazy val ScalaNumericValueClasses: collection.Set[Symbol] = Set( diff --git a/src/dotty/tools/dotc/core/Flags.scala b/src/dotty/tools/dotc/core/Flags.scala index 9f87120f815d..40da7525dba4 100644 --- a/src/dotty/tools/dotc/core/Flags.scala +++ b/src/dotty/tools/dotc/core/Flags.scala @@ -198,8 +198,9 @@ object Flags { final val Final = commonFlag(6, "final") /** A method symbol. */ - final val MethodCommon = commonFlag(7, "") - final val Method = MethodCommon.toTermFlags + final val MethodOrHKCommon = commonFlag(7, "") + final val Method = MethodOrHKCommon.toTermFlags + final val HigherKinded = MethodOrHKCommon.toTypeFlags /** A (term or type) parameter to a class or method */ final val Param = commonFlag(8, "") @@ -411,7 +412,7 @@ object Flags { /** Flags guaranteed to be set upon symbol creation */ final val FromStartFlags = - AccessFlags | Module | Package | Deferred | MethodCommon | Param | Scala2ExistentialCommon | Touched | + AccessFlags | Module | Package | Deferred | MethodOrHKCommon | Param | Scala2ExistentialCommon | Touched | Static | CovariantCommon | ContravariantCommon | ExpandedName | AccessorOrSealed | CaseAccessorOrTypeArgument | Frozen | Erroneous | ImplicitCommon | Permanent | SelfNameOrImplClass diff --git a/src/dotty/tools/dotc/core/NameOps.scala b/src/dotty/tools/dotc/core/NameOps.scala index 22a7e5734048..187946590ebb 100644 --- a/src/dotty/tools/dotc/core/NameOps.scala +++ b/src/dotty/tools/dotc/core/NameOps.scala @@ -87,13 +87,15 @@ object NameOps { name.last == '=' && name.head != '=' && isOperatorPart(name.head) } - /** Is this the name of a higher-kinded type parameter? */ - def isHkParamName: Boolean = name(0) == '_' && name.startsWith(HK_PARAM_PREFIX) + /** Is this the name of a higher-kinded type parameter of a Lambda? */ + def isLambdaArgName = name.startsWith(tpnme.LAMBDA_ARG_PREFIX) + def isHkParamName: Boolean = name(0) == '_' && name.startsWith(HK_PARAM_PREFIX) // tbr /** The index of the higher-kinded type parameter with this name. - * Pre: isHkParamName. + * Pre: isLambdaArgName. */ - def hkParamIndex: Int = name.drop(name.lastIndexOf('$') + 1).toString.toInt + def lambdaArgIndex: Int = name.drop(name.lastIndexOf('$') + 1).toString.toInt + def hkParamIndex: Int = name.drop(name.lastIndexOf('$') + 1).toString.toInt // tbr /** If the name ends with $nn where nn are * all digits, strip the $ and the digits. @@ -181,7 +183,7 @@ object NameOps { * by this name. * @pre The name is a higher-kinded trait name, i.e. it starts with HK_TRAIT_PREFIX */ - def hkVariances: List[Int] = { + def hkVariances: List[Int] = { // tbr def varianceOfSuffix(suffix: Char): Int = { val idx = tpnme.varianceSuffixes.indexOf(suffix) assert(idx >= 0) diff --git a/src/dotty/tools/dotc/core/StdNames.scala b/src/dotty/tools/dotc/core/StdNames.scala index 593feb9092e5..3b1cb63f1e67 100644 --- a/src/dotty/tools/dotc/core/StdNames.scala +++ b/src/dotty/tools/dotc/core/StdNames.scala @@ -166,6 +166,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 = "$hkArg$" final val Any: N = "Any" final val AnyVal: N = "AnyVal" @@ -249,8 +250,8 @@ object StdNames { val SKOLEM: N = "" val SPECIALIZED_INSTANCE: N = "specInstance$" val THIS: N = "_$this" - val HK_PARAM_PREFIX: N = "_$hk$" - val HK_TRAIT_PREFIX: N = "$HigherKinded$" + val HK_PARAM_PREFIX: N = "_$hk$" // tbr + val HK_TRAIT_PREFIX: N = "$HigherKinded$" // tbr final val Nil: N = "Nil" final val Predef: N = "Predef" @@ -286,6 +287,7 @@ object StdNames { val Flag : N = "Flag" val Ident: N = "Ident" val Import: N = "Import" + val LambdaPrefix: N = "Lambda$" val Literal: N = "Literal" val LiteralAnnotArg: N = "LiteralAnnotArg" val Modifiers: N = "Modifiers" @@ -645,8 +647,11 @@ object StdNames { def syntheticTypeParamNames(num: Int): List[TypeName] = (0 until num).map(syntheticTypeParamName)(breakOut) - def higherKindedTraitName(vcs: List[Int]): TypeName = HK_TRAIT_PREFIX ++ vcs.map(varianceSuffix).mkString - def higherKindedParamName(n: Int) = HK_PARAM_PREFIX ++ n.toString + def higherKindedTraitName(vcs: List[Int]): TypeName = HK_TRAIT_PREFIX ++ vcs.map(varianceSuffix).mkString // tbr + def higherKindedParamName(n: Int) = HK_PARAM_PREFIX ++ n.toString //tbr + + def lambdaTraitName(vcs: List[Int]): TypeName = LambdaPrefix ++ vcs.map(varianceSuffix).mkString + def lambdaArgName(n: Int) = LAMBDA_ARG_PREFIX ++ n.toString final val Conforms = encode("<:<") From 4e287d506eeb1d0463c3efd275327b55fce452f2 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 12 Jun 2014 15:05:24 +0200 Subject: [PATCH 03/23] Add isLambda... tests Testing whether a type is (a subtype of) a Lambda class. --- .../tools/dotc/core/SymDenotations.scala | 3 ++ .../tools/dotc/core/TypeApplications.scala | 37 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index 606966be9f76..46484e6cc8b8 100644 --- a/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/src/dotty/tools/dotc/core/SymDenotations.scala @@ -324,6 +324,9 @@ object SymDenotations { final def isRefinementClass(implicit ctx: Context): Boolean = name.decode == tpnme.REFINE_CLASS + final def isLambdaTrait(implicit ctx: Context): Boolean = + isClass && name.startsWith(tpnme.LambdaPrefix) + /** Is this symbol a package object or its module class? */ def isPackageObject(implicit ctx: Context): Boolean = { val poName = if (isType) nme.PACKAGE_CLS else nme.PACKAGE diff --git a/src/dotty/tools/dotc/core/TypeApplications.scala b/src/dotty/tools/dotc/core/TypeApplications.scala index c4845a249de4..428178b81ede 100644 --- a/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/src/dotty/tools/dotc/core/TypeApplications.scala @@ -8,7 +8,9 @@ import Decorators._ import util.Stats._ import util.common._ import Names._ +import NameOps._ import Flags._ +import StdNames.tpnme import util.Positions.Position import config.Printers._ import collection.mutable @@ -108,6 +110,41 @@ class TypeApplications(val self: Type) extends AnyVal { def uninstantiatedTypeParams(implicit ctx: Context): List[TypeSymbol] = typeParams filter (tparam => self.member(tparam.name).symbol == tparam) + /** If type `tp` is equal, aliased-to, or upperbounded-by a type of the form + * `LambdaXYZ { ... }`, the class symbol of that type, otherwise NoSymbol. + * @param forcing if set, might force completion. If not, never forces + * but returns NoSymbol when it would have to otherwise. + */ + def LambdaClass(forcing: Boolean)(implicit ctx: Context): Symbol = ctx.traceIndented(i"LambdaClass($self)", hk) { self.stripTypeVar match { + case self: TypeRef => + val sym = self.symbol + if (sym.isLambdaTrait) sym + else if (sym.isClass || sym.isCompleting && !forcing) NoSymbol + else self.info.LambdaClass(forcing) + case self: TermRef => + NoSymbol + case self: TypeProxy => + self.underlying.LambdaClass(forcing) + case _ => + NoSymbol + }} + + /** Is type `tp` equal, aliased-to, or upperbounded-by a type of the form + * `LambdaXYZ { ... }`? + */ + def isLambda(implicit ctx: Context): Boolean = + LambdaClass(forcing = true).exists + + /** Same is `isLambda`, except that symbol denotations are not forced + * Symbols in completion count as not lambdas. + */ + def isSafeLambda(implicit ctx: Context): Boolean = + LambdaClass(forcing = false).exists + + /** Is type `tp` a Lambda with all Arg$ fields fully instantiated? */ + def isInstantiatedLambda(tp: Type)(implicit ctx: Context): Boolean = + tp.isSafeLambda && tp.typeParams.forall(_.name == tpnme.Apply) + /** Encode the type resulting from applying this type to given arguments */ final def appliedTo(args: List[Type])(implicit ctx: Context): Type = /*>|>*/ track("appliedTo") /*<|<*/ { From c4f96837538e953df3473107bd5d622092f74d80 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 12 Jun 2014 15:46:44 +0200 Subject: [PATCH 04/23] Fixing hashing for RefinedThis types. ... to bring it in sync with equals. --- src/dotty/tools/dotc/core/Hashable.scala | 2 +- src/dotty/tools/dotc/core/Types.scala | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/dotty/tools/dotc/core/Hashable.scala b/src/dotty/tools/dotc/core/Hashable.scala index fc3b7d0c738a..3eb08d7747ec 100644 --- a/src/dotty/tools/dotc/core/Hashable.scala +++ b/src/dotty/tools/dotc/core/Hashable.scala @@ -33,7 +33,7 @@ trait Hashable { private def finishHash(hashCode: Int, arity: Int): Int = avoidNotCached(hashing.finalizeHash(hashCode, arity)) - protected final def identityHash = avoidNotCached(System.identityHashCode(this)) + final def identityHash = avoidNotCached(System.identityHashCode(this)) private def finishHash(seed: Int, arity: Int, tp: Type): Int = { val elemHash = tp.hash diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 827e851a9dd4..8677ec6d22a8 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -1757,7 +1757,7 @@ object Types { } } - // ----- Bound types: MethodParam, PolyParam, RefiendThis -------------------------- + // ----- Bound types: MethodParam, PolyParam, RefinedThis -------------------------- abstract class BoundType extends CachedProxyType with ValueType { type BT <: BindingType @@ -1821,7 +1821,7 @@ object Types { // need to customize hashCode and equals to prevent infinite recursion for // refinements that refer to the refinement type via this - override def computeHash = identityHash + override def computeHash = addDelta(binder.identityHash, 41) override def equals(that: Any) = that match { case that: RefinedThis => this.binder eq that.binder case _ => false From a77a4f6d8bd8f9990eac23859c399abb3b93df7c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 12 Jun 2014 15:50:37 +0200 Subject: [PATCH 05/23] Fix RefinementType#member Type members used to always pick the last refined info for a type name. This is incorrect, as a type might have several refinements for the same type name, which are not necessarily subsumed by the last one. We now only pick the last one if it is an alias type (assuming we check for conflicts elsewhere). --- src/dotty/tools/dotc/core/Types.scala | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 8677ec6d22a8..dda5134c194b 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -375,9 +375,10 @@ object Types { def goRefined(tp: RefinedType) = { val pdenot = go(tp.parent) val rinfo = tp.refinedInfo.substThis(tp, pre) - if (name.isTypeName) // simplified case that runs more efficiently - pdenot.asSingleDenotation.derivedSingleDenotation(pdenot.symbol, rinfo) - else + if (name.isTypeName) {// simplified case that runs more efficiently + val jointInfo = if (rinfo.isAlias) rinfo else pdenot.info & rinfo + pdenot.asSingleDenotation.derivedSingleDenotation(pdenot.symbol, jointInfo) + } else pdenot & (new JointRefDenotation(NoSymbol, rinfo, Period.allInRun(ctx.runId)), pre) } def goThis(tp: ThisType) = { From c2bcf2ec054b3c20b9addd9d1dd6de286de7f6c3 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 12 Jun 2014 15:53:47 +0200 Subject: [PATCH 06/23] Optionally check variance of Lambda classes. Make sure that Lambda Arg refinements have the same variance as the Lambda classes they instantiate. Controlled by a Config parameter. --- src/dotty/tools/dotc/config/Config.scala | 7 ++++++- src/dotty/tools/dotc/core/Types.scala | 17 +++++++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/dotty/tools/dotc/config/Config.scala b/src/dotty/tools/dotc/config/Config.scala index c247699da351..906d173804e7 100644 --- a/src/dotty/tools/dotc/config/Config.scala +++ b/src/dotty/tools/dotc/config/Config.scala @@ -27,7 +27,7 @@ object Config { /** Show subtype traces for all deep subtype recursions */ final val traceDeepSubTypeRecursions = false - final val verboseExplainSubtype = true + final val verboseExplainSubtype = false /** When set, use new signature-based matching. * Advantantage of doing so: It's supposed to be faster @@ -43,4 +43,9 @@ object Config { * for large constraints. */ final val trackConstrDeps = true + + /** Check that variances of lambda arguments match the + * variance of the underlying lambda class. + */ + final val checkLambdaVariance = false } \ No newline at end of file diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index dda5134c194b..24586f412102 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -1420,6 +1420,19 @@ object Types { override def underlying(implicit ctx: Context) = parent + private def checkInst(implicit ctx: Context): this.type = { + if (Config.checkLambdaVariance) + refinedInfo match { + 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}") + case _ => + } + 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. */ @@ -1476,10 +1489,10 @@ object Types { else make(RefinedType(parent, names.head, infoFns.head), names.tail, infoFns.tail) def apply(parent: Type, name: Name, infoFn: RefinedType => Type)(implicit ctx: Context): RefinedType = - ctx.base.uniqueRefinedTypes.enterIfNew(new CachedRefinedType(parent, name, infoFn)) + ctx.base.uniqueRefinedTypes.enterIfNew(new CachedRefinedType(parent, name, infoFn)).checkInst def apply(parent: Type, name: Name, info: Type)(implicit ctx: Context): RefinedType = { - ctx.base.uniqueRefinedTypes.enterIfNew(parent, name, info) + ctx.base.uniqueRefinedTypes.enterIfNew(parent, name, info).checkInst } } From c1b884bbf80bc4c906ada08904d83510f2322ae7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 12 Jun 2014 16:57:24 +0200 Subject: [PATCH 07/23] Make Lambda#Apply a type field instead of a type parameter. Apply cannot be treated as a type parameter because it does not count towards the number of legal arguments in an instantiation of a higher-kinded type. --- src/dotty/tools/dotc/core/Definitions.scala | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/core/Definitions.scala b/src/dotty/tools/dotc/core/Definitions.scala index 3f567d87a032..08588818f839 100644 --- a/src/dotty/tools/dotc/core/Definitions.scala +++ b/src/dotty/tools/dotc/core/Definitions.scala @@ -33,8 +33,11 @@ class Definitions { private def newTopClassSymbol(name: TypeName, flags: FlagSet, parents: List[TypeRef]) = completeClass(newCompleteClassSymbol(ScalaPackageClass, name, flags, parents)) + private def newTypeField(cls: ClassSymbol, name: TypeName, flags: FlagSet, scope: MutableScope) = + scope.enter(newSymbol(cls, name, flags, TypeBounds.empty)) + private def newTypeParam(cls: ClassSymbol, name: TypeName, flags: FlagSet, scope: MutableScope) = - scope.enter(newSymbol(cls, name, flags | TypeParamCreationFlags, TypeBounds.empty)) + newTypeField(cls, name, flags | TypeParamCreationFlags, scope) private def newSyntheticTypeParam(cls: ClassSymbol, scope: MutableScope, paramFlags: FlagSet, suffix: String = "T0") = newTypeParam(cls, suffix.toTypeName.expandedName(cls), ExpandedName | paramFlags, scope) @@ -512,7 +515,7 @@ class Definitions { val paramDecls = newScope for (i <- 0 until vcs.length) newTypeParam(cls, tpnme.lambdaArgName(i), varianceFlags(vcs(i)), paramDecls) - newTypeParam(cls, tpnme.Apply, EmptyFlags, paramDecls) + newTypeField(cls, tpnme.Apply, EmptyFlags, paramDecls) val parentTraitRefs = for (i <- 0 until vcs.length if vcs(i) != 0) yield lambdaTrait(vcs.updated(i, 0)).typeRef From 8db6b3e3c28f671942d4a05ec7cd8848c2dd7fa9 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 12 Jun 2014 16:58:22 +0200 Subject: [PATCH 08/23] Harden allOverriddenSymbols Now also works for symbols that are not class members (and returns Iterator.empty for them). --- src/dotty/tools/dotc/core/Denotations.scala | 2 +- src/dotty/tools/dotc/core/SymDenotations.scala | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/core/Denotations.scala b/src/dotty/tools/dotc/core/Denotations.scala index 9eab2bd040f7..264f9aa467da 100644 --- a/src/dotty/tools/dotc/core/Denotations.scala +++ b/src/dotty/tools/dotc/core/Denotations.scala @@ -378,7 +378,7 @@ object Denotations { case info: SignedType => try info.signature catch { // !!! DEBUG - case ex: MatchError => + case ex: Throwable => println(s"cannot take signature of ${info.show}") throw ex } diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index 46484e6cc8b8..8027620458dc 100644 --- a/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/src/dotty/tools/dotc/core/SymDenotations.scala @@ -321,9 +321,14 @@ object SymDenotations { final def isAnonymousClass(implicit ctx: Context): Boolean = initial.asSymDenotation.name startsWith tpnme.ANON_CLASS + /** Is this symbol a class representing a refinement? These classes + * are used only temporarily in Typer and Unpickler as an intermediate + * step for creating Refinement types. + */ final def isRefinementClass(implicit ctx: Context): Boolean = name.decode == tpnme.REFINE_CLASS + /** is this symbol a trait representing a type lambda? */ final def isLambdaTrait(implicit ctx: Context): Boolean = isClass && name.startsWith(tpnme.LambdaPrefix) @@ -702,7 +707,7 @@ object SymDenotations { /** All symbols overriden by this denotation. */ final def allOverriddenSymbols(implicit ctx: Context): Iterator[Symbol] = - if (exists) + if (exists && owner.isClass) owner.info.baseClasses.tail.iterator map overriddenSymbol filter (_.exists) else Iterator.empty From 9a6a4e8ca400835643e839cd98bb5581cbf97ab9 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 13 Jun 2014 09:34:12 +0200 Subject: [PATCH 09/23] More careful with lookupRefined Avoid to reduce projections `A{ ... }#T` to aliases if the alias would refer to abstract members of the refinement type. --- src/dotty/tools/dotc/core/Types.scala | 33 +++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 24586f412102..a9bbde4070c4 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -164,6 +164,21 @@ object Types { case _ => false } + /** Is this type a transitive refinement of the given type or class symbol? + * This is true if the type consists of 0 or more refinements or other + * non-singleton proxies that lead to the `prefix` type, or, if + * `prefix` is a class symbol, lead to an instance type of this class. + */ + def refines(prefix: AnyRef /* RefinedType | ClassSymbol */)(implicit ctx: Context): Boolean = + (this eq prefix) || { + this match { + case base: ClassInfo => base.cls eq prefix + case base: SingletonType => false + case base: TypeProxy => base.underlying refines prefix + case _ => false + } + } + // ----- Higher-order combinators ----------------------------------- /** Returns true if there is a part of this type that satisfies predicate `p`. @@ -635,11 +650,25 @@ object Types { */ def lookupRefined(name: Name)(implicit ctx: Context): Type = stripTypeVar match { case pre: RefinedType => - if (pre.refinedName ne name) pre.parent.lookupRefined(name) + def dependsOnThis(tp: Type): Boolean = tp match { + case tp @ TypeRef(RefinedThis(rt), _) if rt refines pre => + tp.info match { + case TypeBounds(lo, hi) if lo eq hi => dependsOnThis(hi) + case _ => true + } + case RefinedThis(rt) => + rt refines pre + case _ => false + } + if (pre.refinedName ne name) + pre.parent.lookupRefined(name) else pre.refinedInfo match { - case TypeBounds(lo, hi) /*if lo eq hi*/ => hi + case TypeBounds(lo, hi) if lo eq hi => + if (hi.existsPart(dependsOnThis)) NoType else hi case _ => NoType } + case RefinedThis(rt) => + rt.lookupRefined(name) case pre: WildcardType => WildcardType case _ => From cfd13f731c6757005379c5ad5ad51abfe3282215 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 13 Jun 2014 09:36:47 +0200 Subject: [PATCH 10/23] Fix to printing type parameters. Printing a type parameter reference `A#T` now only reduces to `T` if `A` is some `B.this.type`. --- src/dotty/tools/dotc/core/transform/Erasure.scala | 2 +- src/dotty/tools/dotc/printing/RefinedPrinter.scala | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/dotty/tools/dotc/core/transform/Erasure.scala b/src/dotty/tools/dotc/core/transform/Erasure.scala index f2e3355fb304..da14f72d1aa6 100644 --- a/src/dotty/tools/dotc/core/transform/Erasure.scala +++ b/src/dotty/tools/dotc/core/transform/Erasure.scala @@ -247,7 +247,7 @@ class Erasure(isJava: Boolean, isSemi: Boolean, isConstructor: Boolean, wildcard sigName(tp.optBounds) case _ => val erased = this(tp) - assert(erased ne tp) + assert(erased ne tp, tp) sigName(erased) } } diff --git a/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/src/dotty/tools/dotc/printing/RefinedPrinter.scala index eb6b151b438d..1fc6fbee021f 100644 --- a/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -110,9 +110,9 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { } case tp: TypeRef => if ((tp.symbol is TypeParam | TypeArgument) && !ctx.phase.erasedTypes) { - return tp.info match { - case TypeAlias(hi) => toText(hi) - case _ => nameString(tp.symbol) + tp.info match { + case TypeAlias(hi) => return toText(hi) + case _ => if (tp.prefix.isInstanceOf[ThisType]) return nameString(tp.symbol) } } else if (tp.symbol.isAnonymousClass) From 099e5a64a731c0221a19089bffb0dec9ccde8f95 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 13 Jun 2014 09:38:49 +0200 Subject: [PATCH 11/23] More explanations for -Ycheck failures. In case of "types differ" failure, explain why original and retyped type are not subtypes of each other. --- src/dotty/tools/dotc/transform/TreeChecker.scala | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/transform/TreeChecker.scala b/src/dotty/tools/dotc/transform/TreeChecker.scala index e9970e1f88f3..87e84e169a10 100644 --- a/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -48,13 +48,19 @@ class TreeChecker { def sameType(tp1: Type, tp2: Type) = (tp1 eq tp2) || // accept NoType / NoType (tp1 =:= tp2) - def divergenceMsg = + def divergenceMsg = { + def explanation(tp1: Type, tp2: Type) = + if (tp1 <:< tp2) "" + else "\n why different:\n" + core.TypeComparer.explained((tp1 <:< tp2)(_)) s"""Types differ |Original type : ${tree.typeOpt.show} |After checking: ${tree1.tpe.show} |Original tree : ${tree.show} |After checking: ${tree1.show} - """.stripMargin + """.stripMargin + + explanation(tree1.tpe, tree.typeOpt) + + explanation(tree.typeOpt, tree1.tpe) + } assert(sameType(tree1.tpe, tree.typeOpt), divergenceMsg) tree1 } From 388d9a889c6929699e879a307dc80145b906390a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 15 Jun 2014 12:00:13 +0200 Subject: [PATCH 12/23] Fixing subtyping of refined types Refined type subtyping needs to take into account all information that was seen about members of both refined types. This is handled by storing context info in `pendingRefinedBases` and making use of this to selective rebase qualifiers when comparing refined types. Note: This commit fails in pos/collections and dotc/ast, presumably because of bad interaction between the refined subtyping and the "matchingNames" logic (which will go away). --- src/dotty/tools/dotc/core/TypeComparer.scala | 186 +++++++++++++++---- 1 file changed, 146 insertions(+), 40 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index 4153857195f6..30e243aca4d3 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -8,7 +8,7 @@ import Decorators._ import StdNames.{nme, tpnme} import collection.mutable import printing.Disambiguation.disambiguated -import util.{Stats, DotClass} +import util.{Stats, DotClass, SimpleMap} import config.Config import config.Printers._ @@ -84,6 +84,8 @@ class TypeComparer(initctx: Context) extends DotClass { myAnyType } + // Constraint handling + /** Map that approximates each param in constraint by its lower bound. * Currently only used for diagnostics. */ @@ -264,6 +266,82 @@ class TypeComparer(initctx: Context) extends DotClass { inst } + // Keeping track of seen refinements + + /** A map from refined names to the refined types in which they occur. + * During the subtype check involving the parent of a refined type, + * the refined name is stored in the map, so that the outermost + * refinements can be retrieved when interpreting a reference to the name. + * The name is associated with a pair of refinements. If the refinedInfo is + * skipped in sub- and super-type at the same time (first clause of + * `compareRefined`, both refinements are stored. If the name only appears + * as a refinement in the sub- or -super-type, the refinement type is stored + * twice as both elements of the pair. + */ + protected var pendingRefinedBases: SimpleMap[Name, Set[(RefinedType, RefinedType)]] + = SimpleMap.Empty + + /** Add pending name to `pendingRefinedBases`. */ + private def addPendingName(name: Name, rt1: RefinedType, rt2: RefinedType) = { + var s = pendingRefinedBases(name) + if (s == null) s = Set() + pendingRefinedBases = pendingRefinedBases.updated(name, s + ((rt1, rt2))) + } + + /** Given a selection of qualifier `qual` with given `name`, return a refined type + * that refines `qual`, or if that fails return `qual` itself. + * @param considerBoth If true consider both lower and upper base of `name` when + * checking for refinement (but always return the lower one) + * @see Type#refines + */ + private def rebaseQual(qual: Type, name: Name, considerBoth: Boolean = false): Type = { + val bases = pendingRefinedBases(name) + if (bases == null) qual + else bases.find { + case (tp1, tp2) => + (tp1 refines qual) || considerBoth && (tp1 ne tp2) && (tp2 refines qual) + } match { + case Some((base1, _)) => base1 + case _ => qual + } + } + + /** If the prefix of a named type is `this` (i.e. an instance of type + * `ThisType` or `RefinedThis`), and there is a refinement type R that + * "refines" (transitively contains as its parent) a class reference + * or refinement corresponding to the prefix, return the named type where + * the prefix is replaced by `RefinedThis(R)`. Otherwise return the named type itself. + */ + private def rebase(tp: NamedType): Type = { + def rebaseFrom(prefix: Type): Type = { + rebaseQual(prefix, tp.name, considerBoth = true) match { + case rt: RefinedType if rt ne prefix => tp.derivedSelect(RefinedThis(rt)) + case _ => tp + } + } + tp.prefix match { + case RefinedThis(rt) => rebaseFrom(rt) + case ThisType(cls) => rebaseFrom(cls.info) + case _ => tp + } + } + + /** If the given refined type is refined further, return the member + * of the refiend name relative to the refining base, otherwise return + * `refinedInfo`. + * TODO: Figure out why cannot simply write + * + * rebaseQual(rt, rt.refinedName).member(rt.refinedName).info + * + * (too much forcing, probably). + */ + def normalizedInfo(rt: RefinedType) = { + val base = rebaseQual(rt, rt.refinedName) + if (base eq rt) rt.refinedInfo else base.member(rt.refinedName).info + } + + // Subtype testing `<:<` + def topLevelSubType(tp1: Type, tp2: Type): Boolean = { if (tp2 eq NoType) return false if ((tp2 eq tp1) || @@ -448,6 +526,11 @@ class TypeComparer(initctx: Context) extends DotClass { } } comparePolyParam + case tp1: RefinedThis => + tp2 match { + case tp2: RefinedThis if tp1.binder.parent =:= tp2.binder.parent => true + case _ => thirdTry(tp1, tp2) + } case tp1: BoundType => tp1 == tp2 || thirdTry(tp1, tp2) case tp1: TypeVar => @@ -466,30 +549,42 @@ class TypeComparer(initctx: Context) extends DotClass { thirdTry(tp1, tp2) } - def secondTryNamed(tp1: NamedType, tp2: Type): Boolean = tp1.info match { - case OrType(tp11, tp12) => - val sd = tp1.denot.asSingleDenotation - def derivedRef(tp: Type) = - NamedType(tp1.prefix, tp1.name, sd.derivedSingleDenotation(sd.symbol, tp)) - secondTry(OrType.make(derivedRef(tp11), derivedRef(tp12)), tp2) - case TypeBounds(lo1, hi1) => - if ((tp1.symbol is GADTFlexType) && !isSubTypeWhenFrozen(hi1, tp2)) - trySetType(tp1, TypeBounds(lo1, hi1 & tp2)) - else if (lo1 eq hi1) isSubType(hi1, tp2) + def secondTryNamed(tp1: NamedType, tp2: Type): Boolean = { + def tryRebase2nd = { + val tp1rebased = rebase(tp1) + if (tp1rebased ne tp1) isSubType(tp1rebased, tp2) else thirdTry(tp1, tp2) - case _ => - thirdTry(tp1, tp2) + } + tp1.info match { + case OrType(tp11, tp12) => + val sd = tp1.denot.asSingleDenotation + def derivedRef(tp: Type) = + NamedType(tp1.prefix, tp1.name, sd.derivedSingleDenotation(sd.symbol, tp)) + secondTry(OrType.make(derivedRef(tp11), derivedRef(tp12)), tp2) + case TypeBounds(lo1, hi1) => + if ((tp1.symbol is GADTFlexType) && !isSubTypeWhenFrozen(hi1, tp2)) + trySetType(tp1, TypeBounds(lo1, hi1 & tp2)) + else if (lo1 eq hi1) isSubType(hi1, tp2) + else tryRebase2nd + case _ => + tryRebase2nd + } } def thirdTry(tp1: Type, tp2: Type): Boolean = tp2 match { case tp2: NamedType => + def tryRebase3rd = { + val tp2rebased = rebase(tp2) + if (tp2rebased ne tp2) isSubType(tp1, tp2rebased) + else fourthTry(tp1, tp2) + } def compareNamed: Boolean = tp2.info match { case TypeBounds(lo2, hi2) => if ((tp2.symbol is GADTFlexType) && !isSubTypeWhenFrozen(tp1, lo2)) trySetType(tp2, TypeBounds(lo2 | tp1, hi2)) else ((frozenConstraint || !isCappable(tp1)) && isSubType(tp1, lo2) - || fourthTry(tp1, tp2)) + || tryRebase3rd) case _ => val cls2 = tp2.symbol @@ -499,29 +594,21 @@ class TypeComparer(initctx: Context) extends DotClass { if ( cls2 == defn.SingletonClass && tp1.isStable || cls2 == defn.NotNullClass && tp1.isNotNull) return true } - fourthTry(tp1, tp2) + tryRebase3rd } compareNamed case tp2 @ RefinedType(parent2, name2) => - def matchRefinements(tp1: Type, tp2: Type, seen: Set[Name]): Type = tp1 match { - case tp1 @ RefinedType(parent1, name1) if !(seen contains name1) => - tp2 match { - case tp2 @ RefinedType(parent2, name2) if nameMatches(name1, name2, tp1, tp2) => - if (isSubType(tp1.refinedInfo, tp2.refinedInfo)) - matchRefinements(parent1, parent2, seen + name1) - else NoType - case _ => tp2 - } - case _ => tp2 - } - def compareRefined: Boolean = - if (defn.hkTraits contains parent2.typeSymbol) isSubTypeHK(tp1, tp2) - else tp1.widen match { - case tp1 @ RefinedType(parent1, name1) if nameMatches(name1, name2, tp1, tp2) => - // optimized case; all info on tp1.name2 is in refinement tp1.refinedInfo. - isSubType(tp1.refinedInfo, tp2.refinedInfo) && { - val ancestor2 = matchRefinements(parent1, parent2, Set.empty + name1) - ancestor2.exists && isSubType(tp1, ancestor2) + def compareRefined: Boolean = tp1.widen match { + case tp1 @ RefinedType(parent1, name1) + if nameMatches(name1, name2, tp1, tp2) => + // optimized case; all info on tp1.name1 is in refinement tp1.refinedInfo. + isSubType(normalizedInfo(tp1), tp2.refinedInfo) && { + val saved = pendingRefinedBases + try { + addPendingName(name1, tp1, tp2) + isSubType(parent1, parent2) + } + finally pendingRefinedBases = saved } case _ => def qualifies(m: SingleDenotation) = isSubType(m.info, tp2.refinedInfo) @@ -529,13 +616,14 @@ class TypeComparer(initctx: Context) extends DotClass { case mbr: SingleDenotation => qualifies(mbr) case _ => mbr hasAltWith qualifies } - def hasMatchingMember(name: Name): Boolean = /*>|>*/ ctx.traceIndented(s"hasMatchingMember($name) ${tp1.member(name).info.show}", subtyping) /*<|<*/ ( - memberMatches(tp1 member name) + def hasMatchingMember(name: Name): Boolean = /*>|>*/ ctx.traceIndented(s"hasMatchingMember($name) ${tp1.member(name).info.show}", subtyping) /*<|<*/ { + val tp1r = rebaseQual(tp1, name) + ( memberMatches(tp1r member name) || { // special case for situations like: // foo <: C { type T = foo.T } tp2.refinedInfo match { - case TypeBounds(lo, hi) if lo eq hi => (tp1 select name) =:= lo + case TypeBounds(lo, hi) if lo eq hi => (tp1r select name) =:= lo case _ => false } } @@ -545,8 +633,17 @@ class TypeComparer(initctx: Context) extends DotClass { val tparams = tp1.typeParams idx < tparams.length && hasMatchingMember(tparams(idx).name) } - ) - isSubType(tp1, parent2) && ( + ) + } + val matchesParent = { + val saved = pendingRefinedBases + try { + addPendingName(name2, tp2, tp2) + isSubType(tp1, parent2) + } + finally pendingRefinedBases = saved + } + matchesParent && ( name2 == nme.WILDCARD || hasMatchingMember(name2) || fourthTry(tp1, tp2)) @@ -637,7 +734,12 @@ class TypeComparer(initctx: Context) extends DotClass { } } case tp1: RefinedType => - isNewSubType(tp1.parent, tp2) + val saved = pendingRefinedBases + try { + addPendingName(tp1.refinedName, tp1, tp1) + isNewSubType(tp1.parent, tp2) + } + finally pendingRefinedBases = saved case AndType(tp11, tp12) => isNewSubType(tp11, tp2) || isNewSubType(tp12, tp2) case _ => @@ -754,6 +856,8 @@ class TypeComparer(initctx: Context) extends DotClass { isSubType(bounds.lo, bounds.hi) && { tr.symbol.changeGADTInfo(bounds); true } + // Tests around `matches` + /** A function implementing `tp1` matches `tp2`. */ final def matchesType(tp1: Type, tp2: Type, alwaysMatchSimple: Boolean): Boolean = tp1 match { case tp1: MethodType => @@ -835,6 +939,8 @@ class TypeComparer(initctx: Context) extends DotClass { (poly1.paramBounds corresponds poly2.paramBounds)((b1, b2) => isSameType(b1, b2.subst(poly2, poly1))) + // Type equality =:= + /** Two types are the same if are mutual subtypes of each other */ def isSameType(tp1: Type, tp2: Type): Boolean = if (tp1 eq NoType) false From 7f721438b5bccc8ca9dd68cef273c8cac8199e1a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 18 Jun 2014 18:20:14 +0200 Subject: [PATCH 13/23] Handling higher-kinded types with lambdas Switch to the new scheme where higher-kinded types (and also some polymorphic type aliases) are represented as instances of Lambda traits. --- src/dotty/tools/dotc/core/Definitions.scala | 55 ----- src/dotty/tools/dotc/core/NameOps.scala | 18 +- src/dotty/tools/dotc/core/StdNames.scala | 6 +- .../tools/dotc/core/TypeApplications.scala | 222 +++++++++++------- src/dotty/tools/dotc/core/TypeComparer.scala | 156 +++++------- src/dotty/tools/dotc/core/Types.scala | 80 ++----- .../tools/dotc/core/pickling/UnPickler.scala | 10 +- src/dotty/tools/dotc/typer/Applications.scala | 11 +- src/dotty/tools/dotc/typer/Namer.scala | 29 ++- test/dotc/tests.scala | 3 +- tests/neg/t0654.scala | 2 +- tests/neg/t2994.scala | 3 +- tests/pending/pos/t1236a.scala | 15 ++ tests/pos/apply-equiv.scala | 14 ++ tests/pos/collections.scala | 2 +- tests/pos/hk.scala | 41 +++- tests/pos/lookuprefined.scala | 8 + tests/pos/partialApplications.scala | 13 + tests/pos/polyalias.scala | 26 ++ tests/pos/refinedSubtyping.scala | 19 ++ tests/pos/t0654.scala | 5 + tests/pos/t1236.scala | 2 +- tests/pos/t2994.scala | 35 +++ 23 files changed, 430 insertions(+), 345 deletions(-) create mode 100644 tests/pending/pos/t1236a.scala create mode 100644 tests/pos/apply-equiv.scala create mode 100644 tests/pos/lookuprefined.scala create mode 100644 tests/pos/partialApplications.scala create mode 100644 tests/pos/polyalias.scala create mode 100644 tests/pos/refinedSubtyping.scala create mode 100644 tests/pos/t0654.scala create mode 100644 tests/pos/t2994.scala diff --git a/src/dotty/tools/dotc/core/Definitions.scala b/src/dotty/tools/dotc/core/Definitions.scala index 08588818f839..bd323bab5cd2 100644 --- a/src/dotty/tools/dotc/core/Definitions.scala +++ b/src/dotty/tools/dotc/core/Definitions.scala @@ -424,61 +424,6 @@ class Definitions { def functionArity(tp: Type) = tp.dealias.argInfos.length - 1 - // ----- Higher kinds machinery ------------------------------------------ - // tbr - private var _hkTraits: Set[Symbol] = Set() - - /** The set of HigherKindedXYZ traits encountered so far */ - def hkTraits: Set[Symbol] = _hkTraits - - private var hkTraitOfArity = 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 - * would be generated by the following schema. - * - * class HigherKindedXYZ { type v_n _$hk$0; ...; type v_n _$Hk$n } - * - * Here: - * - * - XYZ is a string with one letter for each variant of a bound symbols, - * using `P` (positive variance), `N` (negative variance), `I` (invariant). - * - v_i are the variances of the bound symbols (i.e. +, -, or empty). - * - _$hk$i are higher-kinded parameter names, which are specially treated in type application. - */ - def hkTrait(vcs: List[Int]) = { - - def varianceFlags(v: Int) = v match { - case -1 => Contravariant - case 0 => Covariant - case 1 => EmptyFlags - } - - val completer = new LazyType { - def complete(denot: SymDenotation)(implicit ctx: Context): Unit = { - val cls = denot.asClass.classSymbol - val paramDecls = newScope - for (i <- 0 until vcs.length) - newTypeParam(cls, tpnme.higherKindedParamName(i), EmptyFlags, paramDecls) - denot.info = ClassInfo(ScalaPackageClass.thisType, cls, List(ObjectClass.typeRef), paramDecls) - } - } - - val traitName = tpnme.higherKindedTraitName(vcs) - - def createTrait = { - val cls = newClassSymbol( - ScalaPackageClass, - traitName, - Trait | Interface | Synthetic, - completer) - _hkTraits += cls - cls - } - - hkTraitOfArity.getOrElseUpdate(vcs, createTrait) - } - // ----- LambdaXYZ traits ------------------------------------------ private var myLambdaTraits: Set[Symbol] = Set() diff --git a/src/dotty/tools/dotc/core/NameOps.scala b/src/dotty/tools/dotc/core/NameOps.scala index 187946590ebb..55354cc1796b 100644 --- a/src/dotty/tools/dotc/core/NameOps.scala +++ b/src/dotty/tools/dotc/core/NameOps.scala @@ -88,14 +88,13 @@ object NameOps { } /** Is this the name of a higher-kinded type parameter of a Lambda? */ - def isLambdaArgName = name.startsWith(tpnme.LAMBDA_ARG_PREFIX) - def isHkParamName: Boolean = name(0) == '_' && name.startsWith(HK_PARAM_PREFIX) // tbr + def isLambdaArgName = + name(0) == tpnme.LAMBDA_ARG_PREFIXhead && name.startsWith(tpnme.LAMBDA_ARG_PREFIX) /** The index of the higher-kinded type parameter with this name. * Pre: isLambdaArgName. */ def lambdaArgIndex: Int = name.drop(name.lastIndexOf('$') + 1).toString.toInt - def hkParamIndex: Int = name.drop(name.lastIndexOf('$') + 1).toString.toInt // tbr /** If the name ends with $nn where nn are * all digits, strip the $ and the digits. @@ -179,19 +178,6 @@ object NameOps { } } - /** The variances of the higherKinded parameters of the trait named - * by this name. - * @pre The name is a higher-kinded trait name, i.e. it starts with HK_TRAIT_PREFIX - */ - def hkVariances: List[Int] = { // tbr - def varianceOfSuffix(suffix: Char): Int = { - val idx = tpnme.varianceSuffixes.indexOf(suffix) - assert(idx >= 0) - idx - 1 - } - name.drop(tpnme.HK_TRAIT_PREFIX.length).toList.map(varianceOfSuffix) - } - /** The name of the generic runtime operation corresponding to an array operation */ def genericArrayOp: TermName = name match { case nme.apply => nme.array_apply diff --git a/src/dotty/tools/dotc/core/StdNames.scala b/src/dotty/tools/dotc/core/StdNames.scala index 3b1cb63f1e67..3ab0ec36edcd 100644 --- a/src/dotty/tools/dotc/core/StdNames.scala +++ b/src/dotty/tools/dotc/core/StdNames.scala @@ -167,6 +167,7 @@ object StdNames { final val REIFY_TREECREATOR_PREFIX: N = "$treecreator" final val REIFY_TYPECREATOR_PREFIX: N = "$typecreator" final val LAMBDA_ARG_PREFIX: N = "$hkArg$" + final val LAMBDA_ARG_PREFIXhead: Char = LAMBDA_ARG_PREFIX.head final val Any: N = "Any" final val AnyVal: N = "AnyVal" @@ -250,8 +251,6 @@ object StdNames { val SKOLEM: N = "" val SPECIALIZED_INSTANCE: N = "specInstance$" val THIS: N = "_$this" - val HK_PARAM_PREFIX: N = "_$hk$" // tbr - val HK_TRAIT_PREFIX: N = "$HigherKinded$" // tbr final val Nil: N = "Nil" final val Predef: N = "Predef" @@ -647,9 +646,6 @@ object StdNames { def syntheticTypeParamNames(num: Int): List[TypeName] = (0 until num).map(syntheticTypeParamName)(breakOut) - def higherKindedTraitName(vcs: List[Int]): TypeName = HK_TRAIT_PREFIX ++ vcs.map(varianceSuffix).mkString // tbr - def higherKindedParamName(n: Int) = HK_PARAM_PREFIX ++ n.toString //tbr - def lambdaTraitName(vcs: List[Int]): TypeName = LambdaPrefix ++ vcs.map(varianceSuffix).mkString def lambdaArgName(n: Int) = LAMBDA_ARG_PREFIX ++ n.toString diff --git a/src/dotty/tools/dotc/core/TypeApplications.scala b/src/dotty/tools/dotc/core/TypeApplications.scala index 428178b81ede..9411ed004e24 100644 --- a/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/src/dotty/tools/dotc/core/TypeApplications.scala @@ -46,13 +46,12 @@ 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 type, the type parameters of the aliased type. - * For a typeref referring to an abstract type with a HigherKindedXYZ bound, the - * type parameters of the HigherKinded class. + * For a typeref referring to an alias or abstract type, the type parameters of + * its right hand side or upper bound. * For a refinement type, the type parameters of its parent, unless there's a - * refinement with the same name. Inherited by all other type proxies. - * For an intersection type A & B, the type parameters of its left operand, A. - * Empty list for all other types. + * refinement with the same name. + * For any other non-singleton type proxy, the type parameters of its underlying type. + * For any other type, the empty list. */ final def typeParams(implicit ctx: Context): List[TypeSymbol] = /*>|>*/ track("typeParams") /*<|<*/ { self match { @@ -61,56 +60,50 @@ class TypeApplications(val self: Type) extends AnyVal { case tp: TypeRef => val tsym = tp.typeSymbol if (tsym.isClass) tsym.typeParams - else if (tsym.info.isAlias) tp.underlying.typeParams - else tp.info.bounds.hi match { - case AndType(hkBound, other) if defn.hkTraits contains hkBound.typeSymbol => - hkBound.typeSymbol.typeParams - case _ => - Nil - } + else tp.underlying.typeParams case tp: RefinedType => - tp.parent.typeParams filterNot (_.name == tp.refinedName) + val tparams = tp.parent.typeParams + tp.refinedInfo match { + case TypeBounds(lo, hi) if lo eq hi => tparams.filterNot(_.name == tp.refinedName) + case _ => tparams + } + case tp: SingletonType => + Nil case tp: TypeProxy => tp.underlying.typeParams - case tp: AndType => - tp.tp1.typeParams case _ => Nil } } + /** The type parameters of the underlying class. * This is like `typeParams`, except for 3 differences. * First, it does not adjust type parameters in refined types. I.e. type arguments * 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. + * Third, it won't return higher-kinded type parameters, i.e. the type parameters of + * an abstract type are always empty. */ - final def safeUnderlyingTypeParams(implicit ctx: Context): List[TypeSymbol] = { - def ifCompleted(sym: Symbol): Symbol = if (sym.isCompleted) sym else NoSymbol + final def rawTypeParams(implicit ctx: Context): List[TypeSymbol] = { self match { case tp: ClassInfo => tp.cls.typeParams case tp: TypeRef => val tsym = tp.typeSymbol if (tsym.isClass) tsym.typeParams - else if (tsym.isAliasType) tp.underlying.safeUnderlyingTypeParams + else if (tsym.isAliasType) tp.underlying.rawTypeParams else Nil - case tp: BoundType => + case _: BoundType | _: SingletonType => Nil case tp: TypeProxy => - tp.underlying.safeUnderlyingTypeParams - case tp: AndType => - tp.tp1.safeUnderlyingTypeParams + tp.underlying.rawTypeParams case _ => Nil } } - def uninstantiatedTypeParams(implicit ctx: Context): List[TypeSymbol] = - typeParams filter (tparam => self.member(tparam.name).symbol == tparam) - - /** If type `tp` is equal, aliased-to, or upperbounded-by a type of the form + /** If type `tp` is equal, aliased-to, or upperbounded-by a type of the form * `LambdaXYZ { ... }`, the class symbol of that type, otherwise NoSymbol. * @param forcing if set, might force completion. If not, never forces * but returns NoSymbol when it would have to otherwise. @@ -143,11 +136,10 @@ class TypeApplications(val self: Type) extends AnyVal { /** Is type `tp` a Lambda with all Arg$ fields fully instantiated? */ def isInstantiatedLambda(tp: Type)(implicit ctx: Context): Boolean = - tp.isSafeLambda && tp.typeParams.forall(_.name == tpnme.Apply) + tp.isSafeLambda && tp.typeParams.isEmpty /** 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 { case arg :: args1 => if (tparams.isEmpty) { @@ -155,7 +147,11 @@ class TypeApplications(val self: Type) extends AnyVal { println(s"precomplete decls = ${self.typeSymbol.decls.toList.map(_.denot).mkString("\n ")}") } val tparam = tparams.head - val tp1 = RefinedType(tp, tparam.name, arg.toBounds(tparam)) + val arg1 = + if ((tparam is HigherKinded) && !arg.isLambda && arg.typeParams.nonEmpty) + arg.EtaExpand + else arg + val tp1 = RefinedType(tp, tparam.name, arg1.toBounds(tparam)) matchParams(tp1, tparams.tail, args1) case nil => tp } @@ -174,8 +170,8 @@ class TypeApplications(val self: Type) extends AnyVal { val safeTypeParams = if (tsym.isClass || !tp.typeSymbol.isCompleting) original.typeParams else { - ctx.warning("encountered F-bounded higher-kinded type parameters; assuming they are invariant") - defn.hkTrait(args map alwaysZero).typeParams + ctx.warning(i"encountered F-bounded higher-kinded type parameters for $tsym; assuming they are invariant") + defn.lambdaTrait(args map alwaysZero).typeParams } matchParams(tp, safeTypeParams, args) } @@ -186,8 +182,6 @@ class TypeApplications(val self: Type) extends AnyVal { tp.refinedInfo) case tp: TypeProxy => instantiate(tp.underlying, original) - case AndType(l, r) => - l.appliedTo(args) & r case tp: PolyType => tp.instantiate(args) case ErrorType => @@ -195,7 +189,10 @@ class TypeApplications(val self: Type) extends AnyVal { } if (args.isEmpty || !canHaveTypeParams) self - else instantiate(self, self) + else { + val res = instantiate(self, self) + if (isInstantiatedLambda(res)) res.select(tpnme.Apply) else res + } } final def appliedTo(arg: Type)(implicit ctx: Context): Type = appliedTo(arg :: Nil) @@ -363,12 +360,11 @@ class TypeApplications(val self: Type) extends AnyVal { * * type T = RHS * - * It is required that `C` is a class and that every bound symbol in `boundSyms` appears - * as an argument in `targs`. If these requirements are not met an error is - * signalled by calling the parameter `error`. - * - * The rewriting replaces bound symbols by references to the - * parameters of class C. Example: + * 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: * @@ -380,48 +376,114 @@ class TypeApplications(val self: Type) extends AnyVal { * * type A = Triple { type T1 = (this.T2, this.T2); type T3 = String } * - * If the RHS is an intersection type A & B, we Lambda abstract on A instead and - * then recombine with & B. + * 2nd strategy: Used as a fallback if 1st strategy does not apply. It rewrites + * the RHS to a typed lambda abstraction. */ - def LambdaAbstract(boundSyms: List[Symbol])(error: (String, Position) => Unit)(implicit ctx: Context): Type = self match { - case AndType(l, r) => - AndType(l.LambdaAbstract(boundSyms)(error), r) - case _ => - val cls = self.typeSymbol - if (!cls.isClass) - error("right-hand side of parameterized alias type must refer to a class", cls.pos) - - val correspondingParamName: Map[Symbol, TypeName] = { - for { - (tparam, targ: TypeRef) <- cls.typeParams zip argInfos - if boundSyms contains targ.symbol - } yield targ.symbol -> tparam.name - }.toMap - - val correspondingNames = correspondingParamName.values.toSet - - def replacements(rt: RefinedType): List[Type] = - for (sym <- boundSyms) yield { - correspondingParamName get sym match { - case Some(name) => - TypeRef(RefinedThis(rt), name) - case None => - error(s"parameter $sym of type alias does not appear as type argument of the aliased $cls", sym.pos) - defn.AnyType - } + 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 } - 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: + * + * LambdaXYZ{ type Apply = subst(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`. + * + * TypeBounds are lambda abstracting by lambda abstracting their upper bound. + */ + def LambdaAbstract(boundSyms: List[Symbol])(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.bounds.subst(boundSyms, argRefs) } + val res = RefinedType(lambda.typeRef, tpnme.Apply, substitutedRHS) + //println(i"lambda abstract $self wrt $boundSyms%, % --> $res") + res + } + self match { + case self @ TypeBounds(lo, hi) => + self.derivedTypeBounds(lo, expand(TypeBounds.upper(hi))) + case _ => + expand(self) + } + } - rewrite(self) + /** Convert a type constructor `TC` with type parameters `T1, ..., Tn` to + * + * LambdaXYZ { Apply = TC[$hkArg$0, ..., $hkArg$n] } + * + * where XYZ is a corresponds to the variances of the type parameters. + */ + def EtaExpand(implicit ctx: Context): Type = { + val tparams = typeParams + self.appliedTo(tparams map (_.typeRef)).LambdaAbstract(tparams) + } + + /** If this type has a base type `B[T1, ..., Tn]` where the type parameters + * of `B` match one-by-one the variances of `tparams`, convert it to + * + * LambdaXYZ { type Apply = B[$hkArg$0, ..., $hkArg$n] } + * { type $hkArg$0 = T1; ...; type $hkArg$n = Tn } + * + * A type parameter matches a varianve V if it has V as its variance or if V == 0. + */ + def EtaLiftTo(tparams: List[Symbol])(implicit ctx: Context): Type = { + def tryLift(bcs: List[ClassSymbol]): Type = bcs match { + case bc :: bcs1 => + val tp = self.baseTypeWithArgs(bc) + val targs = tp.argInfos + val tycon = tp.withoutArgs(targs) + def variancesMatch(param1: Symbol, param2: Symbol) = + param2.variance == param2.variance || param2.variance == 0 + if ((tycon.typeParams corresponds tparams)(variancesMatch)) { + val expanded = tycon.EtaExpand + val res = (expanded /: targs)((partialInst, arg) => + RefinedType(partialInst, partialInst.typeParams.head.name, arg.bounds)) + hk.println(i"eta lifting $self --> $res") + res + } + else tryLift(bcs1) + case nil => + NoType + } + if (tparams.isEmpty) NoType else tryLift(self.baseClasses) } } \ No newline at end of file diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index 30e243aca4d3..de03ad6e4da6 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -438,26 +438,52 @@ class TypeComparer(initctx: Context) extends DotClass { def firstTry(tp1: Type, tp2: Type): Boolean = { tp2 match { case tp2: NamedType => + // We treat two prefixes A.this, B.this as equivalent if + // A's selftype derives from B and B's selftype derives from A. + def equivalentThisTypes(tp1: Type, tp2: Type) = tp1 match { + case ThisType(cls1) => + tp2 match { + case ThisType(cls2) => + cls1.classInfo.selfType.derivesFrom(cls2) && + cls2.classInfo.selfType.derivesFrom(cls1) + case _ => false + } + case _ => false + } + def isHKSubType = { + val lambda2 = tp2.prefix.LambdaClass(forcing = true) + lambda2.exists && isSubType(tp1.EtaLiftTo(lambda2.typeParams), tp2.prefix) + } def compareNamed = { implicit val ctx: Context = this.ctx // Dotty deviation: implicits need explicit type tp1 match { case tp1: NamedType => val sym1 = tp1.symbol (if (sym1 eq tp2.symbol) ( - ctx.erasedTypes + ctx.erasedTypes || sym1.isStaticOwner - || { - val pre1 = tp1.prefix - val pre2 = tp2.prefix - isSubType(pre1, pre2) || - pre1.isInstanceOf[ThisType] && pre2.isInstanceOf[ThisType] - }) - else - (tp1.name eq tp2.name) && isSubType(tp1.prefix, tp2.prefix)) || secondTryNamed(tp1, tp2) + || { // Implements: A # X <: B # X + // if either A =:= B (i.e. A <: B and B <: A), or the following three conditions hold: + // 1. X is a class type, + // 2. B is a class type without abstract type members. + // 3. A <: B. + // Dealiasing is taken care of elsewhere. + val pre1 = tp1.prefix + val pre2 = tp2.prefix + ( isSameType(pre1, pre2) + || equivalentThisTypes(pre1, pre2) + || sym1.isClass + && pre2.classSymbol.exists + && pre2.abstractTypeMembers.isEmpty + ) + } + ) + else (tp1.name eq tp2.name) && isSameType(tp1.prefix, tp2.prefix) + ) || secondTryNamed(tp1, tp2) case ThisType(cls) if cls eq tp2.symbol.moduleClass => isSubType(cls.owner.thisType, tp2.prefix) case _ => - secondTry(tp1, tp2) + tp2.name == tpnme.Apply && isHKSubType || secondTry(tp1, tp2) } } compareNamed @@ -599,8 +625,7 @@ class TypeComparer(initctx: Context) extends DotClass { compareNamed case tp2 @ RefinedType(parent2, name2) => def compareRefined: Boolean = tp1.widen match { - case tp1 @ RefinedType(parent1, name1) - if nameMatches(name1, name2, tp1, tp2) => + case tp1 @ RefinedType(parent1, name1) if name1 == name2 && name1.isTypeName => // optimized case; all info on tp1.name1 is in refinement tp1.refinedInfo. isSubType(normalizedInfo(tp1), tp2.refinedInfo) && { val saved = pendingRefinedBases @@ -627,12 +652,6 @@ class TypeComparer(initctx: Context) extends DotClass { case _ => false } } - || - name.isHkParamName && { - val idx = name.hkParamIndex - val tparams = tp1.typeParams - idx < tparams.length && hasMatchingMember(tparams(idx).name) - } ) } val matchesParent = { @@ -643,10 +662,13 @@ class TypeComparer(initctx: Context) extends DotClass { } finally pendingRefinedBases = saved } - matchesParent && ( - name2 == nme.WILDCARD - || hasMatchingMember(name2) - || fourthTry(tp1, tp2)) + ( matchesParent && ( + name2 == nme.WILDCARD + || hasMatchingMember(name2) + || fourthTry(tp1, tp2) + ) + || needsEtaLift(tp1, tp2) && isSubType(tp1.EtaLiftTo(tp2.typeParams), tp2) + ) } compareRefined case OrType(tp21, tp22) => @@ -734,12 +756,13 @@ class TypeComparer(initctx: Context) extends DotClass { } } case tp1: RefinedType => - val saved = pendingRefinedBases - try { - addPendingName(tp1.refinedName, tp1, tp1) - isNewSubType(tp1.parent, tp2) - } - finally pendingRefinedBases = saved + { val saved = pendingRefinedBases + try { + addPendingName(tp1.refinedName, tp1, tp1) + isNewSubType(tp1.parent, tp2) + } + finally pendingRefinedBases = saved + } || needsEtaLift(tp2, tp1) && isSubType(tp1, tp2.EtaLiftTo(tp1.typeParams)) case AndType(tp11, tp12) => isNewSubType(tp11, tp2) || isNewSubType(tp12, tp2) case _ => @@ -747,7 +770,7 @@ class TypeComparer(initctx: Context) extends DotClass { } /** Like tp1 <:< tp2, but returns false immediately if we know that - * the case was covered previouslky during subtyping. + * the case was covered previously during subtyping. */ private def isNewSubType(tp1: Type, tp2: Type): Boolean = if (isCovered(tp1) && isCovered(tp2)) { @@ -781,30 +804,6 @@ class TypeComparer(initctx: Context) extends DotClass { case _ => proto.isMatchedBy(tp) } - /** Tow refinement names match if they are the same or one is the - * name of a type parameter of its parent type, and the other is - * the corresponding higher-kinded parameter name - */ - private def nameMatches(name1: Name, name2: Name, tp1: Type, tp2: Type) = - name1.isTypeName && - (name1 == name2 || isHKAlias(name1, name2, tp2) || isHKAlias(name2, name1, tp1)) - - /** Is name1 a higher-kinded parameter name and name2 a corresponding - * type parameter name? - */ - private def isHKAlias(name1: Name, name2: Name, tp2: Type) = - name1.isHkParamName && { - val i = name1.hkParamIndex - val tparams = tp2.safeUnderlyingTypeParams - i < tparams.length && tparams(i).name == name2 - } - - /** Is type `tp` a TypeRef referring to a higher-kinded parameter? */ - private def isHKRef(tp: Type) = tp match { - case TypeRef(_, name) => name.isHkParamName - case _ => false - } - /** Can type `tp` be constrained from above by adding a constraint to * a typevar that it refers to? In that case we have to be careful not * to approximate with the lower bound of a type in `thirdTry`. Instead, @@ -818,38 +817,11 @@ class TypeComparer(initctx: Context) extends DotClass { case _ => false } - /** Is `tp1` a subtype of a type `tp2` of the form - * `scala.HigerKindedXYZ { ... }? - * This is the case if `tp1` and `tp2` have the same number - * of type parameters, the bounds of tp1's paremeters - * are contained in the corresponding bounds of tp2's parameters - * and the variances of the parameters agree. - * The variances agree if the supertype parameter is invariant, - * or both parameters have the same variance. - * - * Note: When we get to isSubTypeHK, it might be that tp1 is - * instantiated, or not. If it is instantiated, we compare - * actual argument infos against higher-kinded bounds, - * if it is not instantiated we compare type parameter bounds - * and also compare variances. - */ - def isSubTypeHK(tp1: Type, tp2: Type): Boolean = ctx.traceIndented(s"isSubTypeHK(${tp1.show}, ${tp2.show}", subtyping) { - val tparams = tp1.typeParams - val argInfos1 = tp1.argInfos - val args1 = - if (argInfos1.nonEmpty) argInfos1 // tp1 is instantiated, use the argument infos - else { // tp1 is uninstantiated, use the parameter bounds - val base = tp1.narrow - tparams.map(base.memberInfo) - } - val hkBounds = tp2.argInfos.map(_.asInstanceOf[TypeBounds]) - val boundsOK = (hkBounds corresponds args1)(_ contains _) - val variancesOK = - argInfos1.nonEmpty || (tparams corresponds tp2.typeSymbol.name.hkVariances) { (tparam, v) => - v == 0 || tparam.variance == v - } - hk.println(s"isSubTypeHK: args1 = $args1, hk-bounds = $hkBounds $boundsOK $variancesOK") - boundsOK && variancesOK || fourthTry(tp1, tp2) + /** Does `tp` need to be eta lifted to be comparable to `target`? */ + def needsEtaLift(tp: Type, target: RefinedType) = { + val name = target.refinedName + (name.isLambdaArgName || (name eq tpnme.Apply)) && target.isLambda && + tp.exists && !tp.isLambda } def trySetType(tr: NamedType, bounds: TypeBounds): Boolean = @@ -1093,9 +1065,10 @@ class TypeComparer(initctx: Context) extends DotClass { val t2 = distributeAnd(tp2, tp1) if (t2.exists) t2 else { - if (isHKRef(tp1)) tp2 - else if (isHKRef(tp2)) tp1 - else AndType(tp1, tp2) + //if (isHKRef(tp1)) tp2 + //else if (isHKRef(tp2)) tp1 + //else + AndType(tp1, tp2) } } } @@ -1117,9 +1090,10 @@ class TypeComparer(initctx: Context) extends DotClass { val t2 = distributeOr(tp2, tp1) if (t2.exists) t2 else { - if (isHKRef(tp1)) tp1 - else if (isHKRef(tp2)) tp2 - else OrType(tp1, tp2) + //if (isHKRef(tp1)) tp1 + //else if (isHKRef(tp2)) tp2 + //else + OrType(tp1, tp2) } } } diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index a9bbde4070c4..794f05319c2b 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -164,15 +164,19 @@ object Types { case _ => false } - /** Is this type a transitive refinement of the given type or class symbol? + /** Is this type a transitive refinement of the given type? * This is true if the type consists of 0 or more refinements or other - * non-singleton proxies that lead to the `prefix` type, or, if - * `prefix` is a class symbol, lead to an instance type of this class. + * non-singleton proxies that lead to the `prefix` type. ClassInfos with + * the same class are counted as equal for this purpose. */ - def refines(prefix: AnyRef /* RefinedType | ClassSymbol */)(implicit ctx: Context): Boolean = + def refines(prefix: Type)(implicit ctx: Context): Boolean = (this eq prefix) || { this match { - case base: ClassInfo => base.cls eq prefix + case base: ClassInfo => + prefix match { + case prefix: ClassInfo => base.cls eq prefix.cls + case _ => false + } case base: SingletonType => false case base: TypeProxy => base.underlying refines prefix case _ => false @@ -502,7 +506,7 @@ object Types { } /** Is this type close enough to that type so that members - * with the two type would override each other? + * with the two type would override each other?d * This means: * - Either both types are polytypes with the same number of * type parameters and their result types match after renaming @@ -1466,26 +1470,15 @@ object Types { * 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.safeUnderlyingTypeParams - lazy val originalTypeParam = underlyingTypeParams(refinedName.hkParamIndex) - - /** Use variance of newly instantiated type parameter rather than the old hk argument - */ - def adjustedHKRefinedInfo(hkBounds: TypeBounds, underlyingTypeParam: TypeSymbol) = hkBounds match { - case tp @ TypeBounds(lo, hi) if lo eq hi => - tp.derivedTypeBounds(lo, hi, underlyingTypeParam.variance) - case _ => - hkBounds - } + lazy val underlyingTypeParams = parent.rawTypeParams if ((parent eq this.parent) && (refinedName eq this.refinedName) && (refinedInfo eq this.refinedInfo)) this - else if ( refinedName.isHkParamName - // && { println(s"deriving $refinedName $parent $underlyingTypeParams"); true } - && refinedName.hkParamIndex < underlyingTypeParams.length - && originalTypeParam.name != refinedName) - derivedRefinedType(parent, originalTypeParam.name, - adjustedHKRefinedInfo(refinedInfo.bounds, underlyingTypeParams(refinedName.hkParamIndex))) + else if ( refinedName.isLambdaArgName + //&& { println(s"deriving $refinedName $parent $underlyingTypeParams"); true } + && refinedName.lambdaArgIndex < underlyingTypeParams.length + && !parent.isLambda) + derivedRefinedType(parent.EtaExpand, refinedName, refinedInfo) else RefinedType(parent, refinedName, rt => refinedInfo.substThis(this, RefinedThis(rt))) } @@ -2137,47 +2130,6 @@ object Types { /** If this type and that type have the same variance, this variance, otherwise 0 */ final def commonVariance(that: TypeBounds): Int = (this.variance + that.variance) / 2 - /** Given a the typebounds L..H of higher-kinded abstract type - * - * type T[boundSyms] >: L <: H - * - * produce its equivalent bounds L'..R that make no reference to the bound - * symbols on the left hand side. The idea is to rewrite the declaration to - * - * type T >: L' <: HigherKindedXYZ { type _$hk$i >: bL_i <: bH_i } & H' - * - * where - * - * - XYZ encodes the variants of the bound symbols using `P` (positive variance) - * `N` (negative variance), `I` (invariant). - * - bL_i is the lower bound of bound symbol #i under substitution `substBoundSyms` - * - bH_i is the upper bound of bound symbol #i under substitution `substBoundSyms` - * - `substBoundSyms` is the substitution that maps every bound symbol #i to the - * reference `._$hk$i`, where `` is the RefinedThis referring to the - * previous HigherKindedXYZ refined type. - * - L' = substBoundSyms(L), H' = substBoundSyms(H) - * - * Example: - * - * type T[X <: F[X]] <: Traversable[X, T] - * - * is rewritten to: - * - * type T <: HigherKindedP { type _$hk$0 <: F[$_hk$0] } & Traversable[._$hk$0, T] - * - * @see Definitions.hkTrait - */ - def higherKinded(boundSyms: List[Symbol])(implicit ctx: Context): TypeBounds = { - val parent = defn.hkTrait(boundSyms map (_.variance)).typeRef - val hkParamNames = boundSyms.indices.toList map tpnme.higherKindedParamName - def substBoundSyms(tp: Type)(rt: RefinedType): Type = - tp.subst(boundSyms, hkParamNames map (TypeRef(RefinedThis(rt), _))) - val hkParamInfoFns: List[RefinedType => Type] = - for (bsym <- boundSyms) yield substBoundSyms(bsym.info) _ - val hkBound = RefinedType.make(parent, hkParamNames, hkParamInfoFns).asInstanceOf[RefinedType] - TypeBounds(substBoundSyms(lo)(hkBound), AndType(hkBound, substBoundSyms(hi)(hkBound))) - } - override def toString = if (lo eq hi) s"TypeAlias($lo)" else s"TypeBounds($lo, $hi)" diff --git a/src/dotty/tools/dotc/core/pickling/UnPickler.scala b/src/dotty/tools/dotc/core/pickling/UnPickler.scala index 6227525703f1..dd26b20df99f 100644 --- a/src/dotty/tools/dotc/core/pickling/UnPickler.scala +++ b/src/dotty/tools/dotc/core/pickling/UnPickler.scala @@ -42,15 +42,15 @@ object UnPickler { * * TempPolyType(List(v_1 T_1, ..., v_n T_n), lo .. hi) * - * to a higher-kinded type appoximation (@see TypeBounds.higherKinded) - */ + * to a type lambda using `parameterizeWith/LambdaAbstract`. + */ def depoly(tp: Type, denot: SymDenotation)(implicit ctx: Context): Type = tp match { case TempPolyType(tparams, restpe) => if (denot.isAbstractType) - restpe.bounds.higherKinded(tparams) + restpe.LambdaAbstract(tparams) // bounds needed? else if (denot.isAliasType) { var err: Option[(String, Position)] = None - val result = restpe.LambdaAbstract(tparams) { (msg, pos) => err = Some((msg, pos)) } + val result = restpe.parameterizeWith(tparams) for ((msg, pos) <- err) ctx.warning( s"""$msg @@ -571,6 +571,8 @@ class UnPickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClassRoot: case info => tp.derivedRefinedType(parent1, name, info) } + case tp @ TypeRef(pre, tpnme.Apply) if pre.isLambda => + elim(pre) case _ => tp } diff --git a/src/dotty/tools/dotc/typer/Applications.scala b/src/dotty/tools/dotc/typer/Applications.scala index aaceac0e0889..91f4ce9a5b12 100644 --- a/src/dotty/tools/dotc/typer/Applications.scala +++ b/src/dotty/tools/dotc/typer/Applications.scala @@ -510,10 +510,17 @@ trait Applications extends Compatibility { self: Typer => } def typedTypeApply(tree: untpd.TypeApply, pt: Type)(implicit ctx: Context): Tree = track("typedTypeApply") { - val typedArgs = tree.args mapconserve (typedType(_)) + var typedArgs = tree.args mapconserve (typedType(_)) val typedFn = typedExpr(tree.fun, PolyProto(typedArgs.tpes, pt)) typedFn.tpe.widen match { - case pt: PolyType => checkBounds(typedArgs, pt, tree.pos) + 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, tree.pos) case _ => } assignType(cpy.TypeApply(tree, typedFn, typedArgs), typedFn, typedArgs) diff --git a/src/dotty/tools/dotc/typer/Namer.scala b/src/dotty/tools/dotc/typer/Namer.scala index 681523bd24f9..14404e220038 100644 --- a/src/dotty/tools/dotc/typer/Namer.scala +++ b/src/dotty/tools/dotc/typer/Namer.scala @@ -226,8 +226,13 @@ class Namer { typer: Typer => case tree: MemberDef => val name = tree.name.encode checkNoConflict(name) - val deferred = if (lacksDefinition(tree)) Deferred else EmptyFlags + val isDeferred = lacksDefinition(tree) + val deferred = if (isDeferred) Deferred else EmptyFlags val method = if (tree.isInstanceOf[DefDef]) Method else EmptyFlags + val higherKinded = tree match { + case tree: TypeDef if tree.tparams.nonEmpty && isDeferred => HigherKinded + case _ => EmptyFlags + } // to complete a constructor, move one context further out -- this // is the context enclosing the class. Note that the context in which a @@ -238,7 +243,7 @@ class Namer { typer: Typer => val cctx = if (tree.name == nme.CONSTRUCTOR) ctx.outer else ctx record(ctx.newSymbol( - ctx.owner, name, tree.mods.flags | deferred | method, + ctx.owner, name, tree.mods.flags | deferred | method | higherKinded, adjustIfModule(new Completer(tree)(cctx), tree), privateWithinClass(tree.mods), tree.pos)) case tree: Import => @@ -453,7 +458,7 @@ class Namer { typer: Typer => val Select(New(tpt), nme.CONSTRUCTOR) = core val targs1 = targs map (typedAheadType(_)) val ptype = typedAheadType(tpt).tpe appliedTo targs1.tpes - if (ptype.uninstantiatedTypeParams.isEmpty) ptype + if (ptype.typeParams.isEmpty) ptype else typedAheadExpr(parent).tpe } @@ -661,17 +666,17 @@ class Namer { typer: Typer => completeParams(tdef.tparams) sym.info = TypeBounds.empty // avoid cyclic reference errors for F-bounds 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 rhsType = typedAheadType(tdef.rhs).tpe - + def abstractedRhsType = + if (needsLambda) rhsType.LambdaAbstract(tparamSyms) + else if (toParameterize) rhsType.parameterizeWith(tparamSyms) + else rhsType rhsType match { - case bounds: TypeBounds => - if (tparamSyms.nonEmpty) bounds.higherKinded(tparamSyms) - else rhsType - case _ => - val abstractedRhsType = - if (tparamSyms.nonEmpty) rhsType.LambdaAbstract(tparamSyms)(ctx.error(_, _)) - else rhsType - TypeAlias(abstractedRhsType, if (sym is Local) sym.variance else 0) + case _: TypeBounds => abstractedRhsType + case _ => TypeAlias(abstractedRhsType, if (sym is Local) sym.variance else 0) } } } \ No newline at end of file diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index 64d520500e7e..d25288548e2d 100644 --- a/test/dotc/tests.scala +++ b/test/dotc/tests.scala @@ -76,7 +76,6 @@ class tests extends CompilerTest { defaultOptions = noCheckOptions) // -Ycheck fails because there are structural types involving higher-kinded types. // these are illegal, but are tested only later. - @Test def neg_t0654_polyalias = compileFile(negDir, "t0654", xerrors = 2) @Test def neg_t1192_legalPrefix = compileFile(negDir, "t1192", xerrors = 1) @Test def neg_tailcall_t1672b = compileFile(negDir, "tailcall/t1672b", xerrors = 6) @Test def neg_tailcall_t3275 = compileFile(negDir, "tailcall/t3275", xerrors = 1) @@ -85,7 +84,7 @@ class tests extends CompilerTest { @Test def neg_tailcall2 = compileFile(negDir, "tailcall/tailrec-2", xerrors = 2) @Test def neg_tailcall3 = compileFile(negDir, "tailcall/tailrec-3", xerrors = 2) @Test def neg_t1843 = compileFile(negDir, "t1843", xerrors = 1) - @Test def neg_t2994 = compileFile(negDir, "t2994", xerrors = 13) + @Test def neg_t2994 = compileFile(negDir, "t2994", xerrors = 2) @Test def dotc = compileDir(dotcDir + "tools/dotc", twice) @Test def dotc_ast = compileDir(dotcDir + "tools/dotc/ast", twice) diff --git a/tests/neg/t0654.scala b/tests/neg/t0654.scala index 52dbbb014c03..0d0f2f7deae4 100644 --- a/tests/neg/t0654.scala +++ b/tests/neg/t0654.scala @@ -1,5 +1,5 @@ object Test { class Foo[T] type C[T] = Foo[_ <: T] // error: parameter type T of type alias does not appear as type argument of the aliased class Foo - val a: C[AnyRef] = new Foo[AnyRef] // follow-on error: wrong number of type arguments for Test.C, should be 0 + val a: C[AnyRef] = new Foo[AnyRef] } diff --git a/tests/neg/t2994.scala b/tests/neg/t2994.scala index f3009b12f128..9e9c4ec087b0 100644 --- a/tests/neg/t2994.scala +++ b/tests/neg/t2994.scala @@ -20,7 +20,8 @@ object Naturals { // crashes scala-2.8.0 beta1 trait MUL[n <: NAT, m <: NAT] extends NAT { - trait curry[n[_[_], _], s[_]] { type f[z <: NAT] = n[s, z] } + trait curry[n[_[_], _], s[_]] { type f[z <: NAT] = n[s, z] } // can't do double param lists: + // error: `]' expected but `[` found. type a[s[_ <: NAT] <: NAT, z <: NAT] = n#a[curry[m#a, s]#f, z] } diff --git a/tests/pending/pos/t1236a.scala b/tests/pending/pos/t1236a.scala new file mode 100644 index 000000000000..a1a5a81f460b --- /dev/null +++ b/tests/pending/pos/t1236a.scala @@ -0,0 +1,15 @@ +trait Empty[E[_]] { + def e[A]: E[A] +} + +object T { + val ListEmpty = new Empty[List] { + def e[B] = Nil + } + + // needs better type inference for hk types + def foo[F[_]](q:(String,String)) = "hello" + def foo[F[_]](e: Empty[F]) = "world" + + val x = foo[List](ListEmpty) +} diff --git a/tests/pos/apply-equiv.scala b/tests/pos/apply-equiv.scala new file mode 100644 index 000000000000..f53b8b5abd2d --- /dev/null +++ b/tests/pos/apply-equiv.scala @@ -0,0 +1,14 @@ +class Test { + + class Lambda { type Arg; type Apply } + + type T1 = (Lambda { type Arg = Int } { type Apply = List[Arg] }) # Apply + type T2 = List[Int] + + var x: T1 = _ + var y: T2 = _ + + x = y + y = x + +} diff --git a/tests/pos/collections.scala b/tests/pos/collections.scala index 535e4b542143..08c3010c8605 100644 --- a/tests/pos/collections.scala +++ b/tests/pos/collections.scala @@ -31,6 +31,6 @@ object collections { (ints2, chrs).zipped foreach do2 val xs = List(List(1), List(2), List(3)).iterator - println(xs.flatten) + println(/*scala.collection.TraversableOnce.flattenTraversableOnce*/(xs).flatten) } diff --git a/tests/pos/hk.scala b/tests/pos/hk.scala index f2f10bbfb72f..461c6e386ae1 100644 --- a/tests/pos/hk.scala +++ b/tests/pos/hk.scala @@ -1,34 +1,55 @@ import language.higherKinds +object hk0 { + + class Base { + type Rep[T] + val strRep: Rep[String] + } + + class Sub extends Base { + type Rep[T] = T + val strRep = "abc" + val sr: Rep[String] = "" + } + + class Functor[F[_]] { + def map[A, B](f: A => B): F[A] => F[B] + } + val ml: Functor[List] = ??? + val mx = ml + val mm: (Int => Boolean) => List[Int] => List[Boolean] = mx.map +} + object higherKinded { - + type Untyped = Null class Tree[-T >: Untyped] { type ThisType[-U >: Untyped] <: Tree[U] def withString(s: String): ThisType[String] = withString(s) } - + class Ident[-T >: Untyped] extends Tree[T] { type ThisType[-U] = Ident[U] } - + val id = new Ident[Integer] - + val y = id.withString("abc") - + val z: Ident[String] = y - + val zz: tpd.Tree = y - + abstract class Instance[T >: Untyped] { type Tree = higherKinded.Tree[T] } - + object tpd extends Instance[String] - + def transform(tree: Tree[String]) = { - val tree1 = tree.withString("") + val tree1 = tree.withString("") tree1: Tree[String] } diff --git a/tests/pos/lookuprefined.scala b/tests/pos/lookuprefined.scala new file mode 100644 index 000000000000..f7e7f7337f45 --- /dev/null +++ b/tests/pos/lookuprefined.scala @@ -0,0 +1,8 @@ +class C { type T; type U } + +trait Test { + + val x: (C { type U = T } { type T = String }) # U + val y: String = x + +} diff --git a/tests/pos/partialApplications.scala b/tests/pos/partialApplications.scala new file mode 100644 index 000000000000..b68c4b945152 --- /dev/null +++ b/tests/pos/partialApplications.scala @@ -0,0 +1,13 @@ +object Test { + + type Histogram = Map[_, Int] + + type StringlyHistogram = Histogram[_ >: String] + + val xs: Histogram[String] = Map[String, Int]() + + val ys: StringlyHistogram[String] = xs + + val zs: StringlyHistogram = xs + +} diff --git a/tests/pos/polyalias.scala b/tests/pos/polyalias.scala new file mode 100644 index 000000000000..07bb241f006a --- /dev/null +++ b/tests/pos/polyalias.scala @@ -0,0 +1,26 @@ + +object Test { + + type S = scala.Predef.Set + + val z: S = ??? + + + type Pair[T] = (T, T) + val x = (1, 2) + val xx: Pair[Int] = x + val xxx = xx + + type Config[T] = (T => T, String) + + val y = ((x: String) => x, "a") + val yy: Config[String] = y + val yyy = yy + + type RMap[K, V] = Map[V, K] + type RRMap[KK, VV] = RMap[VV, KK] + + val rm: RMap[Int, String] = Map[String, Int]() + val rrm: RRMap[Int, String] = Map[Int, String]() + +} diff --git a/tests/pos/refinedSubtyping.scala b/tests/pos/refinedSubtyping.scala new file mode 100644 index 000000000000..e97d2a2645fc --- /dev/null +++ b/tests/pos/refinedSubtyping.scala @@ -0,0 +1,19 @@ +class Test { + + class C { type T; type Coll } + + type T1 = C { type T = Int } + + type T11 = T1 { type Coll = Set[Int] } + + type T2 = C { type Coll = Set[T] } + + type T22 = T2 { type T = Int } + + var x: T11 = _ + var y: T22 = _ + + x = y + y = x + +} diff --git a/tests/pos/t0654.scala b/tests/pos/t0654.scala new file mode 100644 index 000000000000..0d0f2f7deae4 --- /dev/null +++ b/tests/pos/t0654.scala @@ -0,0 +1,5 @@ +object Test { + class Foo[T] + type C[T] = Foo[_ <: T] // error: parameter type T of type alias does not appear as type argument of the aliased class Foo + val a: C[AnyRef] = new Foo[AnyRef] +} diff --git a/tests/pos/t1236.scala b/tests/pos/t1236.scala index a84cad0fb558..fa1ab8c0f676 100644 --- a/tests/pos/t1236.scala +++ b/tests/pos/t1236.scala @@ -10,5 +10,5 @@ object T { def foo[F[_]](q:(String,String)) = "hello" def foo[F[_]](e: Empty[F]) = "world" - val x = foo[List](ListEmpty) + val x = foo(ListEmpty) } diff --git a/tests/pos/t2994.scala b/tests/pos/t2994.scala new file mode 100644 index 000000000000..c7421c42a98c --- /dev/null +++ b/tests/pos/t2994.scala @@ -0,0 +1,35 @@ +object Naturals { + trait NAT { + type a[s[_ <: NAT] <: NAT, z <: NAT] <: NAT + type v = a[SUCC, ZERO] + } + final class ZERO extends NAT { + type a[s[_ <: NAT] <: NAT, z <: NAT] = z + } + final class SUCC[n <: NAT] extends NAT { + type a[s[_ <: NAT] <: NAT, z <: NAT] = s[n#a[s, z]] + } + type _0 = ZERO + type _1 = SUCC[_0] + type _2 = SUCC[_1] + type _3 = SUCC[_2] + type _4 = SUCC[_3] + type _5 = SUCC[_4] + type _6 = SUCC[_5] + + + // crashes scala-2.8.0 beta1 + trait MUL[n <: NAT, m <: NAT] extends NAT { + trait curry[n[_, _], s[_]] { type f[z <: NAT] = n[s, z] } + type a[s[_ <: NAT] <: NAT, z <: NAT] = n#a[curry[m#a, s]#f, z] + } + +} + +object Test { + trait Bar[X[_]] + trait Baz[S[_] <: Bar[S]] { + type Apply[T] + } + trait Foo[V[_] <: Bar[V]] extends Bar[Baz[V]#Apply] +} From f9625f9c64872dd16fa233273aa0f55c9d0b03ff Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 18 Jun 2014 18:34:37 +0200 Subject: [PATCH 14/23] Delete old, disabled code in TypeComparer --- src/dotty/tools/dotc/core/TypeComparer.scala | 285 +------------------ 1 file changed, 1 insertion(+), 284 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index de03ad6e4da6..c3a4fa98167e 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -666,7 +666,7 @@ class TypeComparer(initctx: Context) extends DotClass { name2 == nme.WILDCARD || hasMatchingMember(name2) || fourthTry(tp1, tp2) - ) + ) || needsEtaLift(tp1, tp2) && isSubType(tp1.EtaLiftTo(tp2.typeParams), tp2) ) } @@ -1366,286 +1366,3 @@ class ExplainingTypeComparer(initctx: Context) extends TypeComparer(initctx) { override def toString = "Subtype trace:" + { try b.toString finally b.clear() } } - -/* Alternative implementation of isSubType, currently put on hold. Did not work - * out. Keep around for a while longer in case we want to mine it for ideas. - - - def compare(tp1: Type, tp2: Type): Boolean = ctx.debugTraceIndented(s"$tp1 <:< $tp2") { - tp2 match { - case tp2: ProtoType => - isMatchedByProto(tp2, tp1) - case tp2: TypeVar => - isSubType(tp1, tp2.underlying) - case tp2: WildcardType => - def compareWildcard = tp2.optBounds match { - case TypeBounds(_, hi2) => isSubType(tp1, hi2) - case NoType => true - } - compareWildcard - case tp2: AnnotatedType => - isSubType(tp1, tp2.tpe) // todo: refine? - case ErrorType => - true - case AndType(left2, right2) => - isSubType(tp1, left2) && isSubType(tp1, right2) - case _ => - compare1(tp1, tp2) - } - } - - def compare1(tp1: Type, tp2: Type): Boolean = { - tp1 match { - case tref1: TypeRef => - val sym1 = tref1.symbol - tref1.info match { - case TypeBounds(lo1, hi1) => - if (lo1 eq hi1) return compare(hi1, tp2) - else if (sym1 is GADTFlexType) - return isSubType(hi1, tp2) || - trySetType(tref1, TypeBounds(lo1, hi1 & tp2)) - case _ => - if ((sym1 eq NothingClass) && tp2.isInstanceOf[ValueType]) return true - if ((sym1 eq NullClass) && tp2.dealias.typeSymbol.isNullableClass) return true - } - case param1: PolyParam if isConstrained(param1) => - def comparePoly = ( - param1 == tp2 - || isSubTypeWhenFrozen(bounds(param1).hi, tp2) - || { if ((tp2 isRef defn.NothingClass) && state.isGlobalCommittable) - ctx.log(s"!!! instantiating to Nothing: $tp1") - addConstraint(param1, tp2, fromBelow = false) - } - ) - return comparePoly - case tp1 @ ThisType(cls) if cls is ModuleClass => - tp2 match { - case tp2: TermRef => - return tp2.symbol.moduleClass == cls && cls.owner.thisType <:< tp2.prefix - case _ => - } - case tp1: TypeVar => - return compare(tp1.underlying, tp2) - case tp1: WildcardType => - def compareWildcard = tp1.optBounds match { - case TypeBounds(lo1, _) => isSubType(lo1, tp2) // todo: use short-circuiting to current method more often? - case NoType => true - } - return compareWildcard - case tp1: AnnotatedType => - return isSubType(tp1.tpe, tp2) - case ErrorType => - return true - case OrType(left1, right1) => - return isSubType(left1, tp2) && isSubType(right1, tp2) - case _ => - } - rightIsSuper(tp1, tp2) - } - - def rightIsSuper(tp1: Type, tp2: Type): Boolean = tp2 match { - case tp2: NamedType => - def compareNamed: Boolean = { - val sym2 = tp2.symbol - val pre2 = tp2.prefix - tp1 match { - case tp1: NamedType => - val sym1 = tp1.symbol - val pre1 = tp1.prefix - if (sym1 == sym2) { - if ( ctx.erasedTypes - || sym1.isStaticOwner - || isSubType(pre1, pre2) - || pre1.isInstanceOf[ThisType] && pre2.isInstanceOf[ThisType]) return true - } else if (tp1.name == tp2.name && isSubType(pre1, pre2)) return true - case _ => - } - if (sym2.isClass) { - val base = tp1.baseType(sym2) - if (base.exists && (base ne tp1)) isSubType(base, tp2) - else - (sym2 == defn.SingletonClass) && tp1.isStable || - (defn.hkTraits contains sym2) && isSubTypeHK(tp1.widen, tp2) || - leftIsSub2(tp1, tp2) - } - else tp2.info match { - case TypeBounds(lo2, hi2) => - if (lo2 eq hi2) - isSubType(tp1.dealias, hi2.dealias) - else if (sym2 is GADTFlexType) - isSubType(tp1, lo2) || trySetType(tp2, TypeBounds(lo2 | tp1, hi2)) - else { - (frozenConstraint || !isCappable(tp1)) && isSubType(tp1, lo2) || - leftIsSub(tp1, tp2) - } - case _ => - leftIsSub(tp1, tp2) - } - } - compareNamed - - case tp2 @ RefinedType(parent2, name2) => - def matchRefinements(tp1: Type, tp2: Type, seen: Set[Name]): Type = tp1 match { - case tp1 @ RefinedType(parent1, name1) if !(seen contains name1) => - tp2 match { - case tp2 @ RefinedType(parent2, name2) if nameMatches(name1, name2, tp1, tp2) => - if (isSubType(tp1.refinedInfo, tp2.refinedInfo)) - matchRefinements(parent1, parent2, seen + name1) - else NoType - case _ => tp2 - } - case _ => tp2 - } - def compareRefined: Boolean = tp1.widen match { - case tp1 @ RefinedType(parent1, name1) if nameMatches(name1, name2, tp1, tp2) => - // optimized case; all info on tp1.name2 is in refinement tp1.refinedInfo. - isSubType(tp1.refinedInfo, tp2.refinedInfo) && { - val ancestor2 = matchRefinements(parent1, parent2, Set.empty + name1) - ancestor2.exists && isSubType(tp1, ancestor2) - } - case _ => - def hasMatchingMember(name: Name): Boolean = traceIndented(s"hasMatchingMember($name) ${tp1.member(name)}") ( - tp1.member(name).hasAltWith(alt => isSubType(alt.info, tp2.refinedInfo)) - || - { // special case for situations like: - // foo <: C { type T = foo.T } - tp2.refinedInfo match { - case TypeBounds(lo, hi) if lo eq hi => - val ref = tp1 select name - isSubType(ref, lo) && isSubType(hi, ref) - case _ => false - } - } - || - name.isHkParamName && { - val idx = name.hkParamIndex - val tparams = tp1.typeParams - idx < tparams.length && hasMatchingMember(tparams(idx).name) - } - ) - isSubType(tp1, parent2) && ( - name2 == nme.WILDCARD - || hasMatchingMember(name2) - || leftIsSub2(tp1, tp2)) - } - compareRefined - - case param2: PolyParam => - def comparePoly = - param2 == tp1 || { - if (isConstrained(param2)) - isSubTypeWhenFrozen(tp1, bounds(param2).lo) || - addConstraint(param2, tp1.widen, fromBelow = true) - else - (ctx.mode is Mode.TypevarsMissContext) || - isNonBottomSubType(tp1, bounds(param2).lo) || - leftIsSub(tp1, tp2) - } - comparePoly - - case ThisType(cls) if (cls is ModuleClass) => - def compareThis: Boolean = { - tp1 match { - case tp1: TermRef => - if (tp1.symbol.moduleClass == cls) - return tp1.prefix <:< cls.owner.thisType - case _ => - } - leftIsSub(tp1, tp2) - } - compareThis - - case tp2: BoundType => - tp1 == tp2 || leftIsSub(tp1, tp2) - case OrType(tp21, tp22) => - isSubType(tp1, tp21) || isSubType(tp1, tp22) - - case tp2 @ MethodType(_, formals2) => - def compareMethod = tp1 match { - case tp1 @ MethodType(_, formals1) => - tp1.signature == tp2.signature && - (if (Config.newMatch) subsumeParams(formals1, formals2, tp1.isJava, tp2.isJava) - else matchingParams(formals1, formals2, tp1.isJava, tp2.isJava)) && - tp1.isImplicit == tp2.isImplicit && // needed? - isSubType(tp1.resultType, tp2.resultType.subst(tp2, tp1)) - case _ => - false - } - compareMethod - - case tp2: PolyType => - def comparePoly = tp1 match { - case tp1: PolyType => - (tp1.signature sameParams tp2.signature) && - matchingTypeParams(tp1, tp2) && - isSubType(tp1.resultType, tp2.resultType.subst(tp2, tp1)) - case _ => - false - } - comparePoly - - case tp2 @ ExprType(restpe2) => - def compareExpr = tp1 match { - // We allow ()T to be a subtype of => T. - // We need some subtype relationship between them so that e.g. - // def toString and def toString() don't clash when seen - // as members of the same type. And it seems most logical to take - // ()T <:< => T, since everything one can do with a => T one can - // also do with a ()T by automatic () insertion. - case tp1 @ MethodType(Nil, _) => isSubType(tp1.resultType, restpe2) - case _ => isSubType(tp1.widenExpr, restpe2) - } - compareExpr - - case tp2 @ TypeBounds(lo2, hi2) => - def compareBounds = tp1 match { - case tp1 @ TypeBounds(lo1, hi1) => - val v = tp1.variance + tp2.variance - ((v > 0) || (lo2 isRef NothingClass) || isSubType(lo2, lo1)) && - ((v < 0) || (hi2 isRef AnyClass) || isSubType(hi1, hi2)) - case tp1: ClassInfo => - val tt = tp1.typeRef - isSubType(lo2, tt) && isSubType(tt, hi2) - case _ => - false - } - compareBounds - - case ClassInfo(pre2, cls2, _, _, _) => - def compareClassInfo = tp1 match { - case ClassInfo(pre1, cls1, _, _, _) => - (cls1 eq cls2) && isSubType(pre2, pre1) - case _ => - false - } - compareClassInfo - - case _ => - leftIsSub(tp1, tp2) - } - - def leftIsSub(tp1: Type, tp2: Type): Boolean = tp1 match { - case tp1: TypeRef => - tp1.info match { - case TypeBounds(lo1, hi1) => isSubType(hi1, tp2) - case _ => false - } - case tp1: SingletonType => - isSubType(tp1.underlying.widenExpr, tp2) - case tp1: RefinedType => - isSubType(tp1.parent, tp2) - case _ => - leftIsSub2(tp1, tp2) - } - - def leftIsSub2(tp1: Type, tp2: Type): Boolean = tp1 match { - case param1: PolyParam => - assert(!isConstrained(param1)) - (ctx.mode is Mode.TypevarsMissContext) || isSubType(bounds(param1).hi, tp2) - case AndType(tp11, tp12) => - isSubType(tp11, tp2) || isSubType(tp12, tp2) - case _ => - false - } - -*/ From 76238566c281b9d87b17c7e361feb649ceb7a6d0 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 19 Jun 2014 13:02:28 +0200 Subject: [PATCH 15/23] Prevent a source of CyclicReference in refined printer --- src/dotty/tools/dotc/printing/RefinedPrinter.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 1fc6fbee021f..385b407b54e4 100644 --- a/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -109,7 +109,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { return (toTextLocal(tycon) ~ "[" ~ Text(args map argText, ", ") ~ "]").close } case tp: TypeRef => - if ((tp.symbol is TypeParam | TypeArgument) && !ctx.phase.erasedTypes) { + if ((tp.symbol is TypeParam | TypeArgument) && !ctx.phase.erasedTypes && !tp.symbol.isCompleting) { tp.info match { case TypeAlias(hi) => return toText(hi) case _ => if (tp.prefix.isInstanceOf[ThisType]) return nameString(tp.symbol) From 91e44df57185a32988c22823551d8049b3c43da8 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 19 Jun 2014 13:41:18 +0200 Subject: [PATCH 16/23] Fixes to lambda abstraction 1) Honor variance of Apply and $hkArgs when instantiating them 2) Eta-lifting a higher-kinded type is straight eta expansion, no arguments are applied. --- src/dotty/tools/dotc/core/Definitions.scala | 2 +- src/dotty/tools/dotc/core/TypeApplications.scala | 14 +++++++++----- src/dotty/tools/dotc/core/Types.scala | 7 +++++++ tests/{pending => }/pos/t1236a.scala | 0 4 files changed, 17 insertions(+), 6 deletions(-) rename tests/{pending => }/pos/t1236a.scala (100%) diff --git a/src/dotty/tools/dotc/core/Definitions.scala b/src/dotty/tools/dotc/core/Definitions.scala index bd323bab5cd2..6a318b0fe2cc 100644 --- a/src/dotty/tools/dotc/core/Definitions.scala +++ b/src/dotty/tools/dotc/core/Definitions.scala @@ -460,7 +460,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, EmptyFlags, paramDecls) + newTypeField(cls, tpnme.Apply, 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/TypeApplications.scala b/src/dotty/tools/dotc/core/TypeApplications.scala index 9411ed004e24..3452a499b6da 100644 --- a/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/src/dotty/tools/dotc/core/TypeApplications.scala @@ -108,7 +108,7 @@ class TypeApplications(val self: Type) extends AnyVal { * @param forcing if set, might force completion. If not, never forces * but returns NoSymbol when it would have to otherwise. */ - def LambdaClass(forcing: Boolean)(implicit ctx: Context): Symbol = ctx.traceIndented(i"LambdaClass($self)", hk) { self.stripTypeVar match { + def LambdaClass(forcing: Boolean)(implicit ctx: Context): Symbol = track("LambdaClass") { self.stripTypeVar match { case self: TypeRef => val sym = self.symbol if (sym.isLambdaTrait) sym @@ -432,7 +432,7 @@ class TypeApplications(val self: Type) extends AnyVal { val substitutedRHS = (rt: RefinedType) => { val argRefs = boundSyms.indices.toList.map(i => RefinedThis(rt).select(tpnme.lambdaArgName(i))) - tp.bounds.subst(boundSyms, argRefs) + tp.subst(boundSyms, argRefs).bounds.withVariance(1) } val res = RefinedType(lambda.typeRef, tpnme.Apply, substitutedRHS) //println(i"lambda abstract $self wrt $boundSyms%, % --> $res") @@ -475,8 +475,10 @@ class TypeApplications(val self: Type) extends AnyVal { param2.variance == param2.variance || param2.variance == 0 if ((tycon.typeParams corresponds tparams)(variancesMatch)) { val expanded = tycon.EtaExpand - val res = (expanded /: targs)((partialInst, arg) => - RefinedType(partialInst, partialInst.typeParams.head.name, arg.bounds)) + val res = (expanded /: targs) { (partialInst, targ) => + val tparam = partialInst.typeParams.head + RefinedType(partialInst, tparam.name, targ.bounds.withVariance(tparam.variance)) + } hk.println(i"eta lifting $self --> $res") res } @@ -484,6 +486,8 @@ class TypeApplications(val self: Type) extends AnyVal { case nil => NoType } - if (tparams.isEmpty) NoType else tryLift(self.baseClasses) + if (tparams.isEmpty) NoType + else if (typeParams.nonEmpty) EtaExpand + else tryLift(self.baseClasses) } } \ No newline at end of file diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 794f05319c2b..718d01ebe617 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -2096,6 +2096,13 @@ object Types { if (lo eq tp) this else TypeAlias(tp, variance) + /** If this is an alias, a derived alias with the new variance, + * Otherwise the type itself. + */ + def withVariance(variance: Int)(implicit ctx: Context) = + if (lo ne hi) this + else derivedTypeBounds(lo, hi, variance) + def contains(tp: Type)(implicit ctx: Context) = tp match { case tp: TypeBounds => lo <:< tp.lo && tp.hi <:< hi case _ => lo <:< tp && tp <:< hi diff --git a/tests/pending/pos/t1236a.scala b/tests/pos/t1236a.scala similarity index 100% rename from tests/pending/pos/t1236a.scala rename to tests/pos/t1236a.scala From ad0600fbfaf0c9993f993b8577c82ba447baea26 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 20 Jun 2014 14:52:36 +0200 Subject: [PATCH 17/23] Add missing case where isHKSubType is needed Was not called when comparing a named type with an #Apply projection. The commit fixes this. --- src/dotty/tools/dotc/core/TypeComparer.scala | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index c3a4fa98167e..1e1d02be2fd5 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -450,9 +450,10 @@ class TypeComparer(initctx: Context) extends DotClass { } case _ => false } - def isHKSubType = { + def isHKSubType = tp2.name == tpnme.Apply && { val lambda2 = tp2.prefix.LambdaClass(forcing = true) - lambda2.exists && isSubType(tp1.EtaLiftTo(lambda2.typeParams), tp2.prefix) + lambda2.exists && !tp1.isLambda && + isSubType(tp1.EtaLiftTo(lambda2.typeParams), tp2.prefix) } def compareNamed = { implicit val ctx: Context = this.ctx // Dotty deviation: implicits need explicit type @@ -479,11 +480,11 @@ class TypeComparer(initctx: Context) extends DotClass { } ) else (tp1.name eq tp2.name) && isSameType(tp1.prefix, tp2.prefix) - ) || secondTryNamed(tp1, tp2) + ) || isHKSubType || secondTryNamed(tp1, tp2) case ThisType(cls) if cls eq tp2.symbol.moduleClass => isSubType(cls.owner.thisType, tp2.prefix) case _ => - tp2.name == tpnme.Apply && isHKSubType || secondTry(tp1, tp2) + isHKSubType || secondTry(tp1, tp2) } } compareNamed From 7cf6202ea94c016ffc2d2528ad8add186e9f3827 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 20 Jun 2014 14:54:18 +0200 Subject: [PATCH 18/23] Make refines work for aliases refines needs to be made more stable, so that also aliases and derefernces typevars are recognized as prefixes. --- src/dotty/tools/dotc/core/Types.scala | 38 +++++++++++++++------------ 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 718d01ebe617..f1cdfe54be30 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -116,7 +116,7 @@ object Types { false } - /** A type T is a legal prefix in a type selection T#A if + /** A type T is a legal prefix in a type selection T#A if * T is stable or T contains no abstract types except possibly A. * !!! Todo: What about non-final vals that contain abstract types? */ @@ -169,19 +169,23 @@ object Types { * non-singleton proxies that lead to the `prefix` type. ClassInfos with * the same class are counted as equal for this purpose. */ - def refines(prefix: Type)(implicit ctx: Context): Boolean = - (this eq prefix) || { - this match { - case base: ClassInfo => - prefix match { - case prefix: ClassInfo => base.cls eq prefix.cls - case _ => false - } - case base: SingletonType => false - case base: TypeProxy => base.underlying refines prefix - case _ => false + def refines(prefix: Type)(implicit ctx: Context): Boolean = { + val prefix1 = prefix.dealias + def loop(tp: Type): Boolean = + (tp eq prefix1) || { + tp match { + case base: ClassInfo => + prefix1 match { + case prefix1: ClassInfo => base.cls eq prefix1.cls + case _ => false + } + case base: SingletonType => false + case base: TypeProxy => loop(base.underlying) + case _ => false + } } - } + loop(this) + } // ----- Higher-order combinators ----------------------------------- @@ -394,7 +398,7 @@ object Types { def goRefined(tp: RefinedType) = { val pdenot = go(tp.parent) val rinfo = tp.refinedInfo.substThis(tp, pre) - if (name.isTypeName) {// simplified case that runs more efficiently + if (name.isTypeName) { // simplified case that runs more efficiently val jointInfo = if (rinfo.isAlias) rinfo else pdenot.info & rinfo pdenot.asSingleDenotation.derivedSingleDenotation(pdenot.symbol, jointInfo) } else @@ -529,7 +533,7 @@ object Types { */ final def baseTypeRef(base: Symbol)(implicit ctx: Context): Type = /*ctx.traceIndented(s"$this baseTypeRef $base")*/ /*>|>*/ track("baseTypeRef") /*<|<*/ { base.denot match { - case classd: ClassDenotation => classd.baseTypeRefOf(this)//widen.dealias) + case classd: ClassDenotation => classd.baseTypeRefOf(this) //widen.dealias) case _ => NoType } } @@ -1154,7 +1158,7 @@ object Types { else prefix.member(name) if (d.exists || ctx.phaseId == FirstPhaseId || !lastDenotation.isInstanceOf[SymDenotation]) d - else {// name has changed; try load in earlier phase and make current + else { // name has changed; try load in earlier phase and make current val d = loadDenot(ctx.withPhase(ctx.phaseId - 1)).current if (d.exists) d else throw new Error(s"failure to reload $this") @@ -2270,7 +2274,7 @@ object Types { if (absMems.size == 1) absMems.head.info match { case mt: MethodType if !mt.isDependent => Some(absMems.head) - case _=> None + case _ => None } else if (tp isRef defn.PartialFunctionClass) // To maintain compatibility with 2.x, we treat PartialFunction specially, From b3364db33ff2ee2d57b4d0eaed03632099244f63 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 20 Jun 2014 15:04:03 +0200 Subject: [PATCH 19/23] Avoid caching values that depend on typevar state. TypeVars flip from the initial state, where underlying == origin to the final state where underlying == inst. This flip can invalidate information that depends on the underlying type of a TypeVar. Since we do not know when the flip occurs, we need to avoid keeping any such information in a cache. The commit makes three caches depend on a new value: typerState.ephemeral. The value is set to `true` each time we follow the underlying type of a TypeVar, and this disables cached information to be retained. A test case for this commit is t2693.scala. This test passes typechecking with the previous commit, but fails in -Ycheck:front because of stale cache info in an "#Apply" typeref. The present commit fixes that. --- src/dotty/tools/dotc/core/TyperState.scala | 12 +++++- src/dotty/tools/dotc/core/Types.scala | 48 ++++++++++++++-------- src/dotty/tools/dotc/typer/Implicits.scala | 33 +++++++++++---- tests/{pending => }/pos/t2693.scala | 0 4 files changed, 66 insertions(+), 27 deletions(-) rename tests/{pending => }/pos/t2693.scala (100%) diff --git a/src/dotty/tools/dotc/core/TyperState.scala b/src/dotty/tools/dotc/core/TyperState.scala index 8c742edabe26..fd8a534d4aa8 100644 --- a/src/dotty/tools/dotc/core/TyperState.scala +++ b/src/dotty/tools/dotc/core/TyperState.scala @@ -23,6 +23,10 @@ class TyperState(r: Reporter) extends DotClass with Showable { /** The uninstantiated variables */ def uninstVars = constraint.uninstVars + /** The ephemeral flag */ + def ephemeral: Boolean = false + def ephemeral_=(x: Boolean): Unit = () + /** Gives for each instantiated type var that does not yet have its `inst` field * set, the instance value stored in the constraint. Storing instances in constraints * is done only in a temporary way for contexts that may be retracted @@ -76,6 +80,12 @@ extends TyperState(r) { override def constraint = myConstraint override def constraint_=(c: Constraint) = myConstraint = c + private var myEphemeral: Boolean = previous.ephemeral + + override def ephemeral = myEphemeral + override def ephemeral_=(x: Boolean): Unit = { myEphemeral = x } + + override def fresh(isCommittable: Boolean): TyperState = new MutableTyperState(this, new StoreReporter, isCommittable) @@ -96,11 +106,11 @@ extends TyperState(r) { val targetState = ctx.typerState assert(isCommittable) targetState.constraint = constraint - constraint foreachTypeVar { tvar => if (tvar.owningState eq this) tvar.owningState = targetState } + targetState.ephemeral = ephemeral targetState.gc() reporter.flush() } diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index f1cdfe54be30..f3e10c5b09cc 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -1075,24 +1075,32 @@ object Types { /** A second fallback to recompute the denotation if necessary */ private def computeDenot(implicit ctx: Context): Denotation = { - val d = lastDenotation match { - case null => - val sym = lastSymbol - if (sym == null) loadDenot else denotOfSym(sym) - case d: SymDenotation => - if (d.validFor.runId == ctx.runId || ctx.stillValid(d)) d.current - else { - val newd = loadDenot - if (newd.exists) newd else d.staleSymbolError - } - case d => - if (d.validFor.runId == ctx.period.runId) d.current - else loadDenot + val savedEphemeral = ctx.typerState.ephemeral + ctx.typerState.ephemeral = false + try { + val d = lastDenotation match { + case null => + val sym = lastSymbol + if (sym == null) loadDenot else denotOfSym(sym) + case d: SymDenotation => + if (d.validFor.runId == ctx.runId || ctx.stillValid(d)) d.current + else { + val newd = loadDenot + if (newd.exists) newd else d.staleSymbolError + } + case d => + if (d.validFor.runId == ctx.period.runId) d.current + else loadDenot + } + if (ctx.typerState.ephemeral) record("ephemeral cache miss: loadDenot") + else { + lastDenotation = d + lastSymbol = d.symbol + checkedPeriod = ctx.period + } + d } - lastDenotation = d - lastSymbol = d.symbol - checkedPeriod = ctx.period - d + finally ctx.typerState.ephemeral |= savedEphemeral } private def denotOfSym(sym: Symbol)(implicit ctx: Context): Denotation = { @@ -1974,7 +1982,11 @@ object Types { /** If the variable is instantiated, its instance, otherwise its origin */ override def underlying(implicit ctx: Context): Type = { val inst = instanceOpt - if (inst.exists) inst else origin + if (inst.exists) inst + else { + ctx.typerState.ephemeral = true + origin + } } override def computeHash: Int = identityHash diff --git a/src/dotty/tools/dotc/typer/Implicits.scala b/src/dotty/tools/dotc/typer/Implicits.scala index 86d513fff902..da1492d611b5 100644 --- a/src/dotty/tools/dotc/typer/Implicits.scala +++ b/src/dotty/tools/dotc/typer/Implicits.scala @@ -142,9 +142,15 @@ object Implicits { if (monitored) record(s"elided eligible refs", elided(this)) eligibles case None => - val eligibles = computeEligible(tp) - eligibleCache(tp) = eligibles - eligibles + val savedEphemeral = ctx.typerState.ephemeral + ctx.typerState.ephemeral = false + try { + val result = computeEligible(tp) + if (ctx.typerState.ephemeral) record("ephemeral cache miss: eligible") + else eligibleCache(tp) = result + result + } + finally ctx.typerState.ephemeral |= savedEphemeral } } @@ -334,11 +340,22 @@ trait ImplicitRunInfo { self: RunInfo => def iscope(tp: Type, isLifted: Boolean = false): OfTypeImplicits = if (tp.hash == NotCached || !Config.cacheImplicitScopes) ofTypeImplicits(collectCompanions(tp)) - else implicitScopeCache.getOrElseUpdate(tp, { - val liftedTp = if (isLifted) tp else liftToClasses(tp) - if (liftedTp ne tp) iscope(liftedTp, isLifted = true) - else ofTypeImplicits(collectCompanions(tp)) - }) + else implicitScopeCache get tp match { + case Some(is) => is + case None => + val savedEphemeral = ctx.typerState.ephemeral + ctx.typerState.ephemeral = false + try { + val liftedTp = if (isLifted) tp else liftToClasses(tp) + val result = + if (liftedTp ne tp) iscope(liftedTp, isLifted = true) + else ofTypeImplicits(collectCompanions(tp)) + if (ctx.typerState.ephemeral) record("ephemeral cache miss: implicitScope") + else implicitScopeCache(tp) = result + result + } + finally ctx.typerState.ephemeral |= savedEphemeral + } iscope(tp) } diff --git a/tests/pending/pos/t2693.scala b/tests/pos/t2693.scala similarity index 100% rename from tests/pending/pos/t2693.scala rename to tests/pos/t2693.scala From c1721485725bb1050730beb2055700d5fa17c4dd Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 26 Jun 2014 12:22:30 +0200 Subject: [PATCH 20/23] Avoid crashing on name tests when name is empty. Guard every occurrence of name.head with a test whether name.length > 0 --- src/dotty/tools/dotc/core/NameOps.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/dotty/tools/dotc/core/NameOps.scala b/src/dotty/tools/dotc/core/NameOps.scala index 55354cc1796b..5bdafcf8a854 100644 --- a/src/dotty/tools/dotc/core/NameOps.scala +++ b/src/dotty/tools/dotc/core/NameOps.scala @@ -66,13 +66,13 @@ object NameOps { def isSingletonName = name endsWith SINGLETON_SUFFIX def isModuleClassName = name endsWith MODULE_SUFFIX def isImportName = name startsWith IMPORT - def isInheritedName = name.head == '(' && name.startsWith(nme.INHERITED) + def isInheritedName = name.length > 0 && name.head == '(' && name.startsWith(nme.INHERITED) def isModuleVarName(name: Name): Boolean = name.stripAnonNumberSuffix endsWith MODULE_VAR_SUFFIX /** Is name a variable name? */ - def isVariableName: Boolean = { + def isVariableName: Boolean = name.length > 0 && { val first = name.head (((first.isLower && first.isLetter) || first == '_') && (name != false_) @@ -84,12 +84,12 @@ object NameOps { case raw.NE | raw.LE | raw.GE | EMPTY => false case _ => - name.last == '=' && name.head != '=' && isOperatorPart(name.head) + name.length > 0 && name.last == '=' && name.head != '=' && isOperatorPart(name.head) } /** Is this the name of a higher-kinded type parameter of a Lambda? */ def isLambdaArgName = - name(0) == tpnme.LAMBDA_ARG_PREFIXhead && name.startsWith(tpnme.LAMBDA_ARG_PREFIX) + name.length > 0 && name.head == tpnme.LAMBDA_ARG_PREFIXhead && name.startsWith(tpnme.LAMBDA_ARG_PREFIX) /** The index of the higher-kinded type parameter with this name. * Pre: isLambdaArgName. From f823e422478232aa083f3510fa8ce4914ec99d53 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 26 Jun 2014 13:17:24 +0200 Subject: [PATCH 21/23] Bring LambdaClass inline with its doc comment. Avoid special treating TermRef, as this is not demanded by the comment and does not change any of the test outcomes. --- src/dotty/tools/dotc/core/TypeApplications.scala | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeApplications.scala b/src/dotty/tools/dotc/core/TypeApplications.scala index 3452a499b6da..359be171d6a8 100644 --- a/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/src/dotty/tools/dotc/core/TypeApplications.scala @@ -48,8 +48,8 @@ class TypeApplications(val self: Type) extends AnyVal { * 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 * its right hand side or upper bound. - * For a refinement type, the type parameters of its parent, unless there's a - * refinement with the same name. + * For a refinement type, the type parameters of its parent, unless the refinement + * re-binds the type parameter with a type-alias. * For any other non-singleton type proxy, the type parameters of its underlying type. * For any other type, the empty list. */ @@ -105,6 +105,7 @@ class TypeApplications(val self: Type) extends AnyVal { /** If type `tp` is equal, aliased-to, or upperbounded-by a type of the form * `LambdaXYZ { ... }`, the class symbol of that type, otherwise NoSymbol. + * symbol of that type, otherwise NoSymbol. * @param forcing if set, might force completion. If not, never forces * but returns NoSymbol when it would have to otherwise. */ @@ -114,8 +115,6 @@ class TypeApplications(val self: Type) extends AnyVal { if (sym.isLambdaTrait) sym else if (sym.isClass || sym.isCompleting && !forcing) NoSymbol else self.info.LambdaClass(forcing) - case self: TermRef => - NoSymbol case self: TypeProxy => self.underlying.LambdaClass(forcing) case _ => From f600df414d01f44604f47122fe00199842d02baf Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 26 Jun 2014 13:17:55 +0200 Subject: [PATCH 22/23] Improved documentation Added explanations where suggested by Adriaan in his review. --- src/dotty/tools/dotc/core/Definitions.scala | 11 ++++++++--- src/dotty/tools/dotc/core/TyperState.scala | 8 +++++++- src/dotty/tools/dotc/core/Types.scala | 3 ++- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/dotty/tools/dotc/core/Definitions.scala b/src/dotty/tools/dotc/core/Definitions.scala index 6a318b0fe2cc..5e335e240426 100644 --- a/src/dotty/tools/dotc/core/Definitions.scala +++ b/src/dotty/tools/dotc/core/Definitions.scala @@ -437,13 +437,18 @@ class Definitions { * to be the type parameters of a higher-kided type). This is a class symbol that * would be generated by the following schema. * - * class LambdaXYZ { type v_1 Arg1; ...; type v_N ArgN; type Apply } + * class LambdaXYZ extends Object with P1 with ... with Pn { + * type v_1 $hk$Arg0; ...; type v_N $hk$ArgN; + * type Apply + * } * * Here: * - * - XYZ is a string of length N with one letter for each variant of a bound symbols, - * using `P` (positive variance), `N` (negative variance), `I` (invariant). * - v_i are the variances of the bound symbols (i.e. +, -, or empty). + * - XYZ is a string of length N with one letter for each variant of a bound symbol, + * using `P` (positive variance), `N` (negative variance), `I` (invariant). + * - 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 = { assert(vcs.nonEmpty) diff --git a/src/dotty/tools/dotc/core/TyperState.scala b/src/dotty/tools/dotc/core/TyperState.scala index fd8a534d4aa8..de5e0a96168c 100644 --- a/src/dotty/tools/dotc/core/TyperState.scala +++ b/src/dotty/tools/dotc/core/TyperState.scala @@ -23,7 +23,13 @@ class TyperState(r: Reporter) extends DotClass with Showable { /** The uninstantiated variables */ def uninstVars = constraint.uninstVars - /** The ephemeral flag */ + /** The ephemeral flag is set as a side effect if an operation accesses + * the underlying type of a type variable. The reason we need this flag is + * that any such operation is not referentially transparent; it might logically change + * its value at the moment the type variable is instantiated. Caching code needs to + * check the ephemeral flag; If the flag is set during an operation, the result + * of that operation should not be cached. + */ def ephemeral: Boolean = false def ephemeral_=(x: Boolean): Unit = () diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index f3e10c5b09cc..4885b30d8516 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -654,7 +654,8 @@ object Types { * * P { ... type T = / += / -= U ... } # T * - * to just U + * to just U. Does not perform the reduction if the resulting type would contain + * a reference to the "this" of the current refined type. */ def lookupRefined(name: Name)(implicit ctx: Context): Type = stripTypeVar match { case pre: RefinedType => From e710af6ad48b9c2749785e68aee1e4195d7264bb Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 26 Jun 2014 16:18:30 +0200 Subject: [PATCH 23/23] Added test case for LMS inspired HK code. Closes #94. --- tests/pos/i94-nada.scala | 43 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 tests/pos/i94-nada.scala diff --git a/tests/pos/i94-nada.scala b/tests/pos/i94-nada.scala new file mode 100644 index 000000000000..f8263ccf2b01 --- /dev/null +++ b/tests/pos/i94-nada.scala @@ -0,0 +1,43 @@ +import scala.language.higherKinds + +trait Base { + type Rep[T] +} + +trait BaseExp extends Base { + type Rep[T] = Exp[T] + case class Exp[T](v: T) +} + +trait BaseStr extends Base { + type Rep[T] = String +} + +trait BaseDirect extends Base { + type Rep[T] = T +} + +trait Test1 { + trait Monad[X] { + def x: X + } + sealed abstract class Either[A,B] + case class Left[A,B](x: A) extends Either[A,B] with Monad[A] + case class Right[A,B](x: B) extends Either[A,B] with Monad[B] + def flatMap[X,Y,M[X]<:Monad[X]](m: M[X], f: X => M[Y]): M[Y] = f(m.x) + println(flatMap(Left(1), {x: Int => Left(x)})) +} +trait Test2 { + trait Monad[X] { + def x: X + } + sealed abstract class Either[A,B] + case class Left[A,B](x: A) extends Either[A,B] with Monad[A] + case class Right[A,B](x: B) extends Either[A,B] with Monad[B] + def flatMap[X,Y,M[X]](m: M[X], f: X => M[Y]): M[Y] + println(flatMap(Left(1), {x: Int => Left(x)})) +} +trait Test3 { + def flatMap[X,Y,M[X]](m: M[X], f: X => M[Y]): M[Y] + println(flatMap(Some(1), {x: Int => Some(x)})) +}