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/Definitions.scala b/src/dotty/tools/dotc/core/Definitions.scala index 6f34efc8bcc8..5e335e240426 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) @@ -421,34 +424,39 @@ class Definitions { def functionArity(tp: Type) = tp.dealias.argInfos.length - 1 - // ----- Higher kinds machinery ------------------------------------------ + // ----- LambdaXYZ traits ------------------------------------------ - private var _hkTraits: Set[Symbol] = Set() + private var myLambdaTraits: Set[Symbol] = Set() /** The set of HigherKindedXYZ traits encountered so far */ - def hkTraits: Set[Symbol] = _hkTraits + def lambdaTraits: Set[Symbol] = myLambdaTraits - private var hkTraitOfArity = mutable.Map[List[Int], ClassSymbol]() + private var lambdaTraitForVariances = mutable.Map[List[Int], ClassSymbol]() /** The HigherKinded trait corresponding to symbols `boundSyms` (which are assumed * to be the type parameters of a higher-kided type). This is a class symbol that * would be generated by the following schema. * - * class HigherKindedXYZ { type v_n _$hk$0; ...; type v_n _$Hk$n } + * 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 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. + * - 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 hkTrait(vcs: List[Int]) = { + def lambdaTrait(vcs: List[Int]): ClassSymbol = { + assert(vcs.nonEmpty) def varianceFlags(v: Int) = v match { case -1 => Contravariant - case 0 => Covariant - case 1 => EmptyFlags + case 0 => EmptyFlags + case 1 => Covariant } val completer = new LazyType { @@ -456,12 +464,17 @@ class Definitions { 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) + newTypeParam(cls, tpnme.lambdaArgName(i), varianceFlags(vcs(i)), 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 + denot.info = ClassInfo( + ScalaPackageClass.thisType, cls, ObjectClass.typeRef :: parentTraitRefs.toList, paramDecls) } } - val traitName = tpnme.higherKindedTraitName(vcs) + val traitName = tpnme.lambdaTraitName(vcs) def createTrait = { val cls = newClassSymbol( @@ -469,11 +482,11 @@ class Definitions { traitName, Trait | Interface | Synthetic, completer) - _hkTraits += cls + myLambdaTraits += cls cls } - hkTraitOfArity.getOrElseUpdate(vcs, createTrait) + lambdaTraitForVariances.getOrElseUpdate(vcs, createTrait) } // ----- primitive value class machinery ------------------------------------------ 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/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/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/NameOps.scala b/src/dotty/tools/dotc/core/NameOps.scala index 22a7e5734048..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,16 +84,17 @@ 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? */ - 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.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: isHkParamName. + * Pre: isLambdaArgName. */ - def hkParamIndex: Int = name.drop(name.lastIndexOf('$') + 1).toString.toInt + def lambdaArgIndex: Int = name.drop(name.lastIndexOf('$') + 1).toString.toInt /** If the name ends with $nn where nn are * all digits, strip the $ and the digits. @@ -177,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] = { - 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 593feb9092e5..3ab0ec36edcd 100644 --- a/src/dotty/tools/dotc/core/StdNames.scala +++ b/src/dotty/tools/dotc/core/StdNames.scala @@ -166,6 +166,8 @@ 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 LAMBDA_ARG_PREFIXhead: Char = LAMBDA_ARG_PREFIX.head final val Any: N = "Any" final val AnyVal: N = "AnyVal" @@ -249,8 +251,6 @@ 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$" final val Nil: N = "Nil" final val Predef: N = "Predef" @@ -286,6 +286,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 +646,8 @@ 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 lambdaTraitName(vcs: List[Int]): TypeName = LambdaPrefix ++ vcs.map(varianceSuffix).mkString + def lambdaArgName(n: Int) = LAMBDA_ARG_PREFIX ++ n.toString final val Conforms = encode("<:<") diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index 606966be9f76..8027620458dc 100644 --- a/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/src/dotty/tools/dotc/core/SymDenotations.scala @@ -321,9 +321,17 @@ 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) + /** 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 @@ -699,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 diff --git a/src/dotty/tools/dotc/core/TypeApplications.scala b/src/dotty/tools/dotc/core/TypeApplications.scala index c4845a249de4..359be171d6a8 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 @@ -44,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 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. + * 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 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. */ final def typeParams(implicit ctx: Context): List[TypeSymbol] = /*>|>*/ track("typeParams") /*<|<*/ { self match { @@ -59,58 +60,85 @@ 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 + * `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. + */ + def LambdaClass(forcing: Boolean)(implicit ctx: Context): Symbol = track("LambdaClass") { 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: 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.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) { @@ -118,7 +146,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 } @@ -137,8 +169,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) } @@ -149,8 +181,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 => @@ -158,7 +188,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) @@ -326,12 +359,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: * @@ -343,48 +375,118 @@ 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.subst(boundSyms, argRefs).bounds.withVariance(1) } + 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, targ) => + val tparam = partialInst.typeParams.head + RefinedType(partialInst, tparam.name, targ.bounds.withVariance(tparam.variance)) + } + hk.println(i"eta lifting $self --> $res") + res + } + else tryLift(bcs1) + case nil => + NoType + } + 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/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index 4153857195f6..1e1d02be2fd5 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) || @@ -360,26 +438,53 @@ 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 = tp2.name == tpnme.Apply && { + val lambda2 = tp2.prefix.LambdaClass(forcing = true) + 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 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) + ) || isHKSubType || secondTryNamed(tp1, tp2) case ThisType(cls) if cls eq tp2.symbol.moduleClass => isSubType(cls.owner.thisType, tp2.prefix) case _ => - secondTry(tp1, tp2) + isHKSubType || secondTry(tp1, tp2) } } compareNamed @@ -448,6 +553,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 +576,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 +621,20 @@ 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 name1 == name2 && name1.isTypeName => + // 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,27 +642,34 @@ 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 } } - || - name.isHkParamName && { - val idx = name.hkParamIndex - val tparams = tp1.typeParams - idx < tparams.length && hasMatchingMember(tparams(idx).name) - } + ) + } + 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) + ) + || needsEtaLift(tp1, tp2) && isSubType(tp1.EtaLiftTo(tp2.typeParams), tp2) ) - isSubType(tp1, parent2) && ( - name2 == nme.WILDCARD - || hasMatchingMember(name2) - || fourthTry(tp1, tp2)) } compareRefined case OrType(tp21, tp22) => @@ -637,7 +757,13 @@ 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 + } || needsEtaLift(tp2, tp1) && isSubType(tp1, tp2.EtaLiftTo(tp1.typeParams)) case AndType(tp11, tp12) => isNewSubType(tp11, tp2) || isNewSubType(tp12, tp2) case _ => @@ -645,7 +771,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)) { @@ -679,30 +805,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, @@ -716,44 +818,19 @@ 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 = 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 +912,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 @@ -987,9 +1066,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) } } } @@ -1011,9 +1091,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) } } } @@ -1286,286 +1367,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 - } - -*/ diff --git a/src/dotty/tools/dotc/core/TyperState.scala b/src/dotty/tools/dotc/core/TyperState.scala index 8c742edabe26..de5e0a96168c 100644 --- a/src/dotty/tools/dotc/core/TyperState.scala +++ b/src/dotty/tools/dotc/core/TyperState.scala @@ -23,6 +23,16 @@ class TyperState(r: Reporter) extends DotClass with Showable { /** The uninstantiated variables */ def uninstVars = constraint.uninstVars + /** 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 = () + /** 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 +86,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 +112,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 89facfee52c0..4885b30d8516 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -116,15 +116,16 @@ object Types { false } - /** A type T is a legal prefix in a type selection T#A if - * T is stable or T contains no abstract types + /** 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? */ - 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? @@ -163,6 +164,29 @@ object Types { case _ => false } + /** 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. ClassInfos with + * the same class are counted as equal for this purpose. + */ + 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 ----------------------------------- /** Returns true if there is a part of this type that satisfies predicate `p`. @@ -374,9 +398,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) = { @@ -485,7 +510,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 @@ -508,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 } } @@ -629,15 +654,30 @@ 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 => - 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 _ => @@ -1036,24 +1076,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 = { @@ -1119,7 +1167,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") @@ -1418,30 +1466,32 @@ 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. */ 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))) } @@ -1474,10 +1524,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 } } @@ -1756,7 +1806,7 @@ object Types { } } - // ----- Bound types: MethodParam, PolyParam, RefiendThis -------------------------- + // ----- Bound types: MethodParam, PolyParam, RefinedThis -------------------------- abstract class BoundType extends CachedProxyType with ValueType { type BT <: BindingType @@ -1820,7 +1870,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 @@ -1933,7 +1983,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 @@ -2059,6 +2113,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 @@ -2093,47 +2154,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)" @@ -2267,7 +2287,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, 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/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..385b407b54e4 100644 --- a/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -109,10 +109,10 @@ 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) { - return tp.info match { - case TypeAlias(hi) => toText(hi) - case _ => nameString(tp.symbol) + 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) } } else if (tp.symbol.isAnonymousClass) 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 } 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/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/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/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/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..d25288548e2d 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) @@ -75,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) @@ -84,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/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/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/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/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)})) +} 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/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 + +} 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/t1236a.scala b/tests/pos/t1236a.scala new file mode 100644 index 000000000000..a1a5a81f460b --- /dev/null +++ b/tests/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/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 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] +}