From 053cd236ee12f877cfd71affb9a62a417f917d3d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 10 Apr 2017 16:35:01 +0200 Subject: Fix #2152: Instantiate dependent result type parameters #2152 shows that dependent result type parameters can end up in the types of terms, so we have to eliminate them. If we don't we get orphan parameters in pickling. Fix method name and comment --- compiler/src/dotty/tools/dotc/core/NameKinds.scala | 2 +- compiler/src/dotty/tools/dotc/core/TypeOps.scala | 4 ++++ compiler/src/dotty/tools/dotc/core/Types.scala | 2 +- compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala | 17 +++++++++-------- 4 files changed, 15 insertions(+), 10 deletions(-) (limited to 'compiler/src/dotty/tools/dotc') diff --git a/compiler/src/dotty/tools/dotc/core/NameKinds.scala b/compiler/src/dotty/tools/dotc/core/NameKinds.scala index 0f08e4701..81ac3a02f 100644 --- a/compiler/src/dotty/tools/dotc/core/NameKinds.scala +++ b/compiler/src/dotty/tools/dotc/core/NameKinds.scala @@ -209,7 +209,7 @@ object NameKinds { val InlineAccessorName = new UniqueNameKind("$_inlineAccessor_$") val TempResultName = new UniqueNameKind("ev$") val EvidenceParamName = new UniqueNameKind("evidence$") - val DepParamName = new UniqueNameKind("") + val DepParamName = new UniqueNameKind("(param)") val LazyImplicitName = new UniqueNameKind("$_lazy_implicit_$") val LazyLocalName = new UniqueNameKind("$lzy") val LazyLocalInitName = new UniqueNameKind("$lzyINIT") diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index 9593bfe93..0de823e97 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -7,6 +7,7 @@ import SymDenotations._, Denotations.SingleDenotation import config.Printers.typr import util.Positions._ import NameOps._ +import NameKinds.DepParamName import Decorators._ import StdNames._ import Annotations._ @@ -169,6 +170,9 @@ trait TypeOps { this: Context => // TODO: Make standalone object. simplify(l, theMap) & simplify(r, theMap) case OrType(l, r) => simplify(l, theMap) | simplify(r, theMap) + case tp: TypeVar if tp.origin.paramName.is(DepParamName) => + val effectiveVariance = if (theMap == null) 1 else theMap.variance + tp.instanceOpt orElse tp.instantiate(fromBelow = effectiveVariance != -1) case _ => (if (theMap != null) theMap else new SimplifyMap).mapOver(tp) } diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index c8c1886cc..955a5a11c 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -3509,7 +3509,7 @@ object Types { def apply(tp: Type): Type - protected var variance = 1 + protected[core] var variance = 1 protected def derivedSelect(tp: NamedType, pre: Type): Type = tp.derivedSelect(pre) diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 398a7a17e..5d8240362 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -377,9 +377,10 @@ object ProtoTypes { * Also, if `owningTree` is non-empty, add a type variable for each parameter. * @return The added type lambda, and the list of created type variables. */ - def constrained(tl: TypeLambda, owningTree: untpd.Tree)(implicit ctx: Context): (TypeLambda, List[TypeTree]) = { + def constrained(tl: TypeLambda, owningTree: untpd.Tree, alwaysAddTypeVars: Boolean = false)(implicit ctx: Context): (TypeLambda, List[TypeTree]) = { val state = ctx.typerState - assert(!(ctx.typerState.isCommittable && owningTree.isEmpty), + val addTypeVars = alwaysAddTypeVars || !owningTree.isEmpty + assert(!(ctx.typerState.isCommittable && !addTypeVars), s"inconsistent: no typevars were added to committable constraint ${state.constraint}") def newTypeVars(tl: TypeLambda): List[TypeTree] = @@ -392,7 +393,7 @@ object ProtoTypes { val added = if (state.constraint contains tl) tl.newLikeThis(tl.paramNames, tl.paramInfos, tl.resultType) else tl - val tvars = if (owningTree.isEmpty) Nil else newTypeVars(added) + val tvars = if (addTypeVars) newTypeVars(added) else Nil ctx.typeComparer.addToConstraint(added, tvars.tpes.asInstanceOf[List[TypeVar]]) (added, tvars) } @@ -400,13 +401,13 @@ object ProtoTypes { /** Same as `constrained(tl, EmptyTree)`, but returns just the created type lambda */ def constrained(tl: TypeLambda)(implicit ctx: Context): TypeLambda = constrained(tl, EmptyTree)._1 - /** Create a new TypeParamRef that represents a dependent method parameter singleton */ - def newDepTypeParamRef(tp: Type)(implicit ctx: Context): TypeParamRef = { + /** Create a new TypeVar that represents a dependent method parameter singleton */ + def newDepTypeVar(tp: Type)(implicit ctx: Context): TypeVar = { val poly = PolyType(DepParamName.fresh().toTypeName :: Nil)( pt => TypeBounds.upper(AndType(tp, defn.SingletonType)) :: Nil, pt => defn.AnyType) - ctx.typeComparer.addToConstraint(poly, Nil) - TypeParamRef(poly, 0) + constrained(poly, untpd.EmptyTree, alwaysAddTypeVars = true) + ._2.head.tpe.asInstanceOf[TypeVar] } /** The result type of `mt`, where all references to parameters of `mt` are @@ -415,7 +416,7 @@ object ProtoTypes { def resultTypeApprox(mt: MethodType)(implicit ctx: Context): Type = if (mt.isDependent) { def replacement(tp: Type) = - if (ctx.mode.is(Mode.TypevarsMissContext)) WildcardType else newDepTypeParamRef(tp) + if (ctx.mode.is(Mode.TypevarsMissContext)) WildcardType else newDepTypeVar(tp) mt.resultType.substParams(mt, mt.paramInfos.map(replacement)) } else mt.resultType -- cgit v1.2.3 From aaff621f552e6824db4f7c1d73d9e8941e088d96 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 11 Apr 2017 23:36:16 +0200 Subject: Alternative fix The original fix made run/hmap-covariant fail because a type variable representing a dependent result parameter was instantiated. Trying something else now. --- compiler/src/dotty/tools/dotc/core/TypeOps.scala | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'compiler/src/dotty/tools/dotc') diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index 0de823e97..4a1c3d044 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -159,7 +159,11 @@ trait TypeOps { this: Context => // TODO: Make standalone object. case tp1 => tp1 } case tp: TypeParamRef => - typerState.constraint.typeVarOfParam(tp) orElse tp + if (tp.paramName.is(DepParamName)) { + val bounds = ctx.typeComparer.bounds(tp) + if (bounds.lo.isRef(defn.NothingClass)) bounds.hi else bounds.lo + } + else typerState.constraint.typeVarOfParam(tp) orElse tp case _: ThisType | _: BoundType | NoPrefix => tp case tp: RefinedType => @@ -170,9 +174,6 @@ trait TypeOps { this: Context => // TODO: Make standalone object. simplify(l, theMap) & simplify(r, theMap) case OrType(l, r) => simplify(l, theMap) | simplify(r, theMap) - case tp: TypeVar if tp.origin.paramName.is(DepParamName) => - val effectiveVariance = if (theMap == null) 1 else theMap.variance - tp.instanceOpt orElse tp.instantiate(fromBelow = effectiveVariance != -1) case _ => (if (theMap != null) theMap else new SimplifyMap).mapOver(tp) } -- cgit v1.2.3 From 3e04b6f3aa4e4088220f199bd6aa5c6644c22354 Mon Sep 17 00:00:00 2001 From: Olivier Blanvillain Date: Thu, 13 Apr 2017 09:18:46 +0200 Subject: Revert <: Product requierment in pattern matching The change in question broke the following pattern, commonly used in name based pattern matching: ```scala object ProdEmpty { def _1: Int = ??? def _2: String = ??? def isEmpty = true def get = this } ``` This type define both `_1` and `get` + `isEmpty` (but is not <: Product). After #1938, `ProdEmpty` became eligibles for both product and name based pattern. Because "in case of ambiguities, *Product Pattern* is preferred over *Name Based Pattern*", isEmpty wouldn't be used, breaking the scalac semantics. --- .../src/dotty/tools/dotc/core/Definitions.scala | 4 ++- .../tools/dotc/transform/PatternMatcher.scala | 2 +- .../src/dotty/tools/dotc/typer/Applications.scala | 7 ++-- compiler/src/dotty/tools/dotc/typer/Typer.scala | 2 +- tests/run/1938-2.scala | 37 ++++++++++++++++++++++ 5 files changed, 44 insertions(+), 8 deletions(-) create mode 100644 tests/run/1938-2.scala (limited to 'compiler/src/dotty/tools/dotc') diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index a97589d73..eee6ba785 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -10,7 +10,6 @@ import scala.collection.{ mutable, immutable } import PartialFunction._ import collection.mutable import util.common.alwaysZero -import typer.Applications object Definitions { @@ -846,6 +845,9 @@ class Definitions { TupleType(elems.size).appliedTo(elems) } + def isProductSubType(tp: Type)(implicit ctx: Context) = + tp.derivesFrom(ProductType.symbol) + /** Is `tp` (an alias) of either a scala.FunctionN or a scala.ImplicitFunctionN? */ def isFunctionType(tp: Type)(implicit ctx: Context) = { val arity = functionArity(tp) diff --git a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala index 41a1218eb..447a003e7 100644 --- a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -1408,7 +1408,7 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer { protected def seqTree(binder: Symbol) = tupleSel(binder)(firstIndexingBinder + 1) protected def tupleSel(binder: Symbol)(i: Int): Tree = { val accessors = - if (Applications.canProductMatch(binder.info)) + if (defn.isProductSubType(binder.info)) productSelectors(binder.info) else binder.caseAccessors val res = diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index c4d3e2292..7e17abbcd 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -48,9 +48,6 @@ object Applications { ref.info.widenExpr.dealias } - def canProductMatch(tp: Type)(implicit ctx: Context) = - extractorMemberType(tp, nme._1).exists - /** Does `tp` fit the "product match" conditions as an unapply result type * for a pattern with `numArgs` subpatterns? * This is the case of `tp` has members `_1` to `_N` where `N == numArgs`. @@ -72,7 +69,7 @@ object Applications { } def productArity(tp: Type)(implicit ctx: Context) = - if (canProductMatch(tp)) productSelectorTypes(tp).size else -1 + if (defn.isProductSubType(tp)) productSelectorTypes(tp).size else -1 def productSelectors(tp: Type)(implicit ctx: Context): List[Symbol] = { val sels = for (n <- Iterator.from(0)) yield tp.member(nme.selectorName(n)).symbol @@ -114,7 +111,7 @@ object Applications { getUnapplySelectors(getTp, args, pos) else if (unapplyResult isRef defn.BooleanClass) Nil - else if (canProductMatch(unapplyResult)) + else if (defn.isProductSubType(unapplyResult)) productSelectorTypes(unapplyResult) // this will cause a "wrong number of arguments in pattern" error later on, // which is better than the message in `fail`. diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 4bf938fd4..02538671e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -759,7 +759,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit /** Is `formal` a product type which is elementwise compatible with `params`? */ def ptIsCorrectProduct(formal: Type) = { isFullyDefined(formal, ForceDegree.noBottom) && - Applications.canProductMatch(formal) && + defn.isProductSubType(formal) && Applications.productSelectorTypes(formal).corresponds(params) { (argType, param) => param.tpt.isEmpty || argType <:< typedAheadType(param.tpt).tpe diff --git a/tests/run/1938-2.scala b/tests/run/1938-2.scala new file mode 100644 index 000000000..32e4c4518 --- /dev/null +++ b/tests/run/1938-2.scala @@ -0,0 +1,37 @@ +object ProdNonEmpty { + def _1: Int = 0 + def _2: String = "???" // Slight variation with scalac: this test passes + // with ??? here. I think dotty behavior is fine + // according to the spec given that methods involved + // in pattern matching should be pure. + def isEmpty = false + def unapply(s: String): this.type = this + def get = this +} + +object ProdEmpty { + def _1: Int = ??? + def _2: String = ??? + def isEmpty = true + def unapply(s: String): this.type = this + def get = this +} + +object Test { + def main(args: Array[String]): Unit = { + "" match { + case ProdNonEmpty(0, _) => () + case _ => ??? + } + + "" match { + case ProdNonEmpty(1, _) => ??? + case _ => () + } + + "" match { + case ProdEmpty(_, _) => ??? + case _ => () + } + } +} -- cgit v1.2.3