From 6545606cefaf8ec7090f8a601123bfae61b441a6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 14 Mar 2017 16:47:55 +0100 Subject: Encode variances in parameter names This leads to a slight overall simplification, harmonizes pickle format with internal representation, and makes MethodTypes and PolyTypes more similar to each other. I believe the change is useful as it is, but in particular it is a useful step for an eventual unification of MethodTypes and PolyTypes. --- compiler/src/dotty/tools/dotc/ast/Desugar.scala | 6 ++++ .../src/dotty/tools/dotc/core/Definitions.scala | 2 +- compiler/src/dotty/tools/dotc/core/NameOps.scala | 26 +++++++++++++++- compiler/src/dotty/tools/dotc/core/StdNames.scala | 1 - .../dotty/tools/dotc/core/TypeApplications.scala | 9 ++++-- .../src/dotty/tools/dotc/core/TypeComparer.scala | 8 ++--- compiler/src/dotty/tools/dotc/core/Types.scala | 35 ++++++++++++++-------- .../dotty/tools/dotc/core/tasty/TastyFormat.scala | 6 +--- .../dotty/tools/dotc/core/tasty/TreePickler.scala | 4 +-- .../tools/dotc/core/tasty/TreeUnpickler.scala | 6 ++-- .../dotty/tools/dotc/printing/PlainPrinter.scala | 8 ++--- .../dotc/transform/FullParameterization.scala | 4 +-- .../src/dotty/tools/dotc/typer/ProtoTypes.scala | 2 +- 13 files changed, 76 insertions(+), 41 deletions(-) (limited to 'compiler/src') diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 8499330fb..5f9d48798 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -242,6 +242,12 @@ object desugar { * class C { type v C$T; type v T = C$T } */ def typeDef(tdef: TypeDef)(implicit ctx: Context): Tree = { + val name = + if (tdef.name.hasVariance && tdef.mods.is(Param)) { + ctx.error(em"type parameter name may not start with `+' or `-'", tdef.pos) + ("$" + tdef.name).toTypeName + } + else tdef.name if (tdef.mods is PrivateLocalParam) { val tparam = cpy.TypeDef(tdef)(name = tdef.name.expandedName(ctx.owner)) .withMods(tdef.mods &~ PrivateLocal | ExpandedName) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 39b46cbfe..c48016b67 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -152,7 +152,7 @@ class Definitions { resultTypeFn: PolyType => Type, flags: FlagSet = EmptyFlags) = { val tparamNames = tpnme.syntheticTypeParamNames(typeParamCount) val tparamBounds = tparamNames map (_ => TypeBounds.empty) - val ptype = PolyType(tparamNames, tparamNames.map(alwaysZero))(_ => tparamBounds, resultTypeFn) + val ptype = PolyType(tparamNames)(_ => tparamBounds, resultTypeFn) enterMethod(cls, name, ptype, flags) } diff --git a/compiler/src/dotty/tools/dotc/core/NameOps.scala b/compiler/src/dotty/tools/dotc/core/NameOps.scala index ea905c19f..240ad359b 100644 --- a/compiler/src/dotty/tools/dotc/core/NameOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NameOps.scala @@ -196,6 +196,31 @@ object NameOps { if (name.isModuleClassName) name.stripModuleClassSuffix.freshened.moduleClassName else likeTyped(ctx.freshName(name ++ NameTransformer.NAME_JOIN_STRING))) + /** Name with variance prefix: `+` for covariant, `-` for contravariant */ + def withVariance(v: Int): N = + if (hasVariance) dropVariance.withVariance(v) + else v match { + case -1 => likeTyped('-' +: name) + case 1 => likeTyped('+' +: name) + case 0 => name + } + + /** Does name have a `+`/`-` variance prefix? */ + def hasVariance: Boolean = + name.nonEmpty && name.head == '+' || name.head == '-' + + /** Drop variance prefix if name has one */ + def dropVariance: N = if (hasVariance) likeTyped(name.tail) else name + + /** The variance as implied by the variance prefix, or 0 if there is + * no variance prefix. + */ + def variance = name.head match { + case '-' => -1 + case '+' => 1 + case _ => 0 + } + /** Translate a name into a list of simple TypeNames and TermNames. * In all segments before the last, type/term is determined by whether * the following separator char is '.' or '#'. The last segment @@ -271,7 +296,6 @@ object NameOps { else -1 } - /** The number of hops specified in an outer-select name */ def outerSelectHops: Int = { require(isOuterSelect) diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index bc3f96d91..30fa03537 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -850,5 +850,4 @@ object StdNames { val tpnme = new ScalaTypeNames val jnme = new JavaTermNames val jtpnme = new JavaTypeNames - } diff --git a/compiler/src/dotty/tools/dotc/core/TypeApplications.scala b/compiler/src/dotty/tools/dotc/core/TypeApplications.scala index ba3e6a461..a7ce1ee85 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeApplications.scala @@ -278,9 +278,10 @@ class TypeApplications(val self: Type) extends AnyVal { * TODO: Handle parameterized lower bounds */ def LambdaAbstract(tparams: List[TypeParamInfo])(implicit ctx: Context): Type = { + def nameWithVariance(tparam: TypeParamInfo) = + tparam.paramName.withVariance(tparam.paramVariance) def expand(tp: Type) = - PolyType( - tparams.map(_.paramName), tparams.map(_.paramVariance))( + PolyType(tparams.map(nameWithVariance))( tl => tparams.map(tparam => tl.lifted(tparams, tparam.paramBounds).bounds), tl => tl.lifted(tparams, tp)) if (tparams.isEmpty) self @@ -365,7 +366,9 @@ class TypeApplications(val self: Type) extends AnyVal { case arg @ PolyType(tparams, body) if !tparams.corresponds(hkParams)(_.paramVariance == _.paramVariance) && tparams.corresponds(hkParams)(varianceConforms) => - PolyType(tparams.map(_.paramName), hkParams.map(_.paramVariance))( + PolyType( + (tparams, hkParams).zipped.map((tparam, hkparam) => + tparam.paramName.withVariance(hkparam.paramVariance)))( tl => arg.paramBounds.map(_.subst(arg, tl).bounds), tl => arg.resultType.subst(arg, tl) ) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 57dde3288..a8f92522c 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -669,7 +669,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { val tparams1 = tparams1a.drop(lengthDiff) variancesConform(tparams1, tparams) && { if (lengthDiff > 0) - tycon1b = PolyType(tparams1.map(_.paramName), tparams1.map(_.paramVariance))( + tycon1b = PolyType(tparams1.map(_.paramName))( tl => tparams1.map(tparam => tl.lifted(tparams, tparam.paramBounds).bounds), tl => tycon1a.appliedTo(args1.take(lengthDiff) ++ tparams1.indices.toList.map(PolyParam(tl, _)))) @@ -1281,9 +1281,9 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { original(tp1.appliedTo(tp1.typeParams.map(_.paramBoundsAsSeenFrom(tp1))), tp2) else PolyType( - paramNames = tpnme.syntheticTypeParamNames(tparams1.length), - variances = (tparams1, tparams2).zipped.map((tparam1, tparam2) => - (tparam1.paramVariance + tparam2.paramVariance) / 2))( + paramNames = (tpnme.syntheticTypeParamNames(tparams1.length), tparams1, tparams2) + .zipped.map((pname, tparam1, tparam2) => + pname.withVariance((tparam1.paramVariance + tparam2.paramVariance) / 2)))( paramBoundsExp = tl => (tparams1, tparams2).zipped.map((tparam1, tparam2) => tl.lifted(tparams1, tparam1.paramBoundsAsSeenFrom(tp1)).bounds & tl.lifted(tparams2, tparam2.paramBoundsAsSeenFrom(tp2)).bounds), diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index c80107f93..6b5be5489 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -2572,8 +2572,20 @@ object Types { } } - /** A type lambda of the form `[v_0 X_0, ..., v_n X_n] => T` */ - class PolyType(val paramNames: List[TypeName], val variances: List[Int])( + /** A type lambda of the form `[X_0 B_0, ..., X_n B_n] => T` + * This is used both as a type of a polymorphic method and as a type of + * a higher-kidned type parameter. Variances are encoded in parameter + * names. A name starting with `+` designates a covariant parameter, + * a name starting with `-` designates a contravariant parameter, + * and every other name designates a non-variant parameter. + * + * @param paramNames The names `X_0`, ..., `X_n` + * @param paramBoundsExp A function that, given the polytype itself, returns the + * parameter bounds `B_1`, ..., `B_n` + * @param resultTypeExp A function that, given the polytype itself, returns the + * result type `T`. + */ + class PolyType(val paramNames: List[TypeName])( paramBoundsExp: PolyType => List[TypeBounds], resultTypeExp: PolyType => Type) extends CachedProxyType with BindingType with MethodOrPoly { @@ -2611,7 +2623,7 @@ object Types { paramBounds.mapConserve(_.substParams(this, argTypes).bounds) def newLikeThis(paramNames: List[TypeName], paramBounds: List[TypeBounds], resType: Type)(implicit ctx: Context): PolyType = - PolyType.apply(paramNames, variances)( + PolyType.apply(paramNames)( x => paramBounds mapConserve (_.subst(this, x).bounds), x => resType.subst(this, x)) @@ -2644,7 +2656,7 @@ object Types { case t => mapOver(t) } } - PolyType(paramNames ++ that.paramNames, variances ++ that.variances)( + PolyType(paramNames ++ that.paramNames)( x => this.paramBounds.mapConserve(_.subst(this, x).bounds) ++ that.paramBounds.mapConserve(shift(_).subst(that, x).bounds), x => shift(that.resultType).subst(that, x).subst(this, x)) @@ -2664,28 +2676,27 @@ object Types { case other: PolyType => other.paramNames == this.paramNames && other.paramBounds == this.paramBounds && - other.resType == this.resType && - other.variances == this.variances + other.resType == this.resType case _ => false } - override def toString = s"PolyType($variances, $paramNames, $paramBounds, $resType)" + override def toString = s"PolyType($paramNames, $paramBounds, $resType)" - override def computeHash = doHash(variances ::: paramNames, resType, paramBounds) + override def computeHash = doHash(paramNames, resType, paramBounds) } object PolyType { - def apply(paramNames: List[TypeName], variances: List[Int])( + def apply(paramNames: List[TypeName])( paramBoundsExp: PolyType => List[TypeBounds], resultTypeExp: PolyType => Type)(implicit ctx: Context): PolyType = { - unique(new PolyType(paramNames, variances)(paramBoundsExp, resultTypeExp)) + unique(new PolyType(paramNames)(paramBoundsExp, resultTypeExp)) } def unapply(tl: PolyType): Some[(List[LambdaParam], Type)] = Some((tl.typeParams, tl.resType)) def any(n: Int)(implicit ctx: Context) = - apply(tpnme.syntheticTypeParamNames(n), List.fill(n)(0))( + apply(tpnme.syntheticTypeParamNames(n))( pt => List.fill(n)(TypeBounds.empty), pt => defn.AnyType) } @@ -2698,7 +2709,7 @@ object Types { def paramBounds(implicit ctx: Context): TypeBounds = tl.paramBounds(n) def paramBoundsAsSeenFrom(pre: Type)(implicit ctx: Context): TypeBounds = paramBounds def paramBoundsOrCompleter(implicit ctx: Context): Type = paramBounds - def paramVariance(implicit ctx: Context): Int = tl.variances(n) + def paramVariance(implicit ctx: Context): Int = tl.paramNames(n).variance def toArg: Type = PolyParam(tl, n) def paramRef(implicit ctx: Context): Type = PolyParam(tl, n) } diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala index cb1b56c3c..98577f3c5 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala @@ -151,7 +151,7 @@ Standard-Section: "ASTs" TopLevelStat* BIND Length boundName_NameRef bounds_Type // for type-variables defined in a type pattern BYNAMEtype underlying_Type - POLYtype Length result_Type NamesTypes // variance encoded in front of name: +/-/= + POLYtype Length result_Type NamesTypes // variance encoded in front of name: +/-/(nothing) METHODtype Length result_Type NamesTypes // needed for refinements PARAMtype Length binder_ASTref paramNum_Nat // needed for refinements SHARED type_ASTRef @@ -546,8 +546,4 @@ object TastyFormat { case POLYtype | METHODtype => -1 case _ => 0 } - - /** Map between variances and name prefixes */ - val varianceToPrefix = Map(-1 -> '-', 0 -> '=', 1 -> '+') - val prefixToVariance = Map('-' -> -1, '=' -> 0, '+' -> 1) } diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 80270aa25..871f39838 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -255,9 +255,7 @@ class TreePickler(pickler: TastyPickler) { pickleType(tpe.underlying) case tpe: PolyType => writeByte(POLYtype) - val paramNames = tpe.typeParams.map(tparam => - varianceToPrefix(tparam.paramVariance) +: tparam.paramName) - pickleMethodic(tpe.resultType, paramNames, tpe.paramBounds) + pickleMethodic(tpe.resultType, tpe.paramNames, tpe.paramBounds) case tpe: MethodType if richTypes => writeByte(METHODtype) pickleMethodic(tpe.resultType, tpe.paramNames, tpe.paramTypes) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 88b6eef7a..6c6ebc0a3 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -268,10 +268,8 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table, posUnpickle registerSym(start, sym) TypeRef.withFixedSym(NoPrefix, sym.name, sym) case POLYtype => - val (rawNames, paramReader) = readNamesSkipParams - val (variances, paramNames) = rawNames - .map(name => (prefixToVariance(name.head), name.tail.toTypeName)).unzip - val result = PolyType(paramNames, variances)( + val (paramNames, paramReader) = readNamesSkipParams + val result = PolyType(paramNames.map(_.toTypeName))( pt => registeringType(pt, paramReader.readParamTypes[TypeBounds](end)), pt => readType()) goto(end) diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index 67d44daa1..ba5592bd6 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -176,10 +176,9 @@ class PlainPrinter(_ctx: Context) extends Printer { case tp: ExprType => changePrec(GlobalPrec) { "=> " ~ toText(tp.resultType) } case tp: PolyType => - def paramText(variance: Int, name: Name, bounds: TypeBounds): Text = - varianceString(variance) ~ name.toString ~ toText(bounds) + def paramText(name: Name, bounds: TypeBounds): Text = name.toString ~ toText(bounds) changePrec(GlobalPrec) { - "[" ~ Text((tp.variances, tp.paramNames, tp.paramBounds).zipped.map(paramText), ", ") ~ + "[" ~ Text((tp.paramNames, tp.paramBounds).zipped.map(paramText), ", ") ~ "]" ~ (" => " provided !tp.resultType.isInstanceOf[MethodType]) ~ toTextGlobal(tp.resultType) } @@ -209,7 +208,8 @@ class PlainPrinter(_ctx: Context) extends Printer { protected def polyParamNameString(name: TypeName): String = name.toString - protected def polyParamNameString(param: PolyParam): String = polyParamNameString(param.binder.paramNames(param.paramNum)) + protected def polyParamNameString(param: PolyParam): String = + polyParamNameString(param.binder.paramNames(param.paramNum)) /** The name of the symbol without a unique id. Under refined printing, * the decoded original name. diff --git a/compiler/src/dotty/tools/dotc/transform/FullParameterization.scala b/compiler/src/dotty/tools/dotc/transform/FullParameterization.scala index 0cb453b4c..cdc28b23f 100644 --- a/compiler/src/dotty/tools/dotc/transform/FullParameterization.scala +++ b/compiler/src/dotty/tools/dotc/transform/FullParameterization.scala @@ -122,14 +122,14 @@ trait FullParameterization { info match { case info: PolyType => - PolyType(info.paramNames ++ ctnames, info.variances ++ ctvariances)( + PolyType(info.paramNames ++ ctnames)( pt => (info.paramBounds.map(mapClassParams(_, pt).bounds) ++ mappedClassBounds(pt)).mapConserve(_.subst(info, pt).bounds), pt => resultType(mapClassParams(_, pt)).subst(info, pt)) case _ => if (ctparams.isEmpty) resultType(identity) - else PolyType(ctnames, ctvariances)(mappedClassBounds, pt => resultType(mapClassParams(_, pt))) + else PolyType(ctnames)(mappedClassBounds, pt => resultType(mapClassParams(_, pt))) } } diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index f96b8ae1d..d61e24261 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -401,7 +401,7 @@ object ProtoTypes { /** Create a new polyparam that represents a dependent method parameter singleton */ def newDepPolyParam(tp: Type)(implicit ctx: Context): PolyParam = { - val poly = PolyType(ctx.freshName(nme.DEP_PARAM_PREFIX).toTypeName :: Nil, 0 :: Nil)( + val poly = PolyType(ctx.freshName(nme.DEP_PARAM_PREFIX).toTypeName :: Nil)( pt => TypeBounds.upper(AndType(tp, defn.SingletonType)) :: Nil, pt => defn.AnyType) ctx.typeComparer.addToConstraint(poly, Nil) -- cgit v1.2.3