diff options
-rw-r--r-- | src/dotty/tools/dotc/core/Definitions.scala | 5 | ||||
-rw-r--r-- | src/dotty/tools/dotc/typer/Applications.scala | 87 | ||||
-rw-r--r-- | src/dotty/tools/dotc/typer/Inferencing.scala | 50 | ||||
-rw-r--r-- | src/dotty/tools/dotc/typer/Typer.scala | 16 |
4 files changed, 105 insertions, 53 deletions
diff --git a/src/dotty/tools/dotc/core/Definitions.scala b/src/dotty/tools/dotc/core/Definitions.scala index 96fe172b3..9d03d001f 100644 --- a/src/dotty/tools/dotc/core/Definitions.scala +++ b/src/dotty/tools/dotc/core/Definitions.scala @@ -315,6 +315,11 @@ class Definitions(implicit ctx: Context) { lazy val RootImports = Set[Symbol](PredefModule, ScalaPackageVal, JavaLangPackageVal) + def isTupleType(tp: Type) = { + val arity = tp.typeArgs.length + arity <= MaxTupleArity && (tp isRef TupleClass(arity)) + } + // ----- Higher kinds machinery ------------------------------------------ private var _hkTraits: Set[Symbol] = Set() diff --git a/src/dotty/tools/dotc/typer/Applications.scala b/src/dotty/tools/dotc/typer/Applications.scala index 794c7e752..f4936cff0 100644 --- a/src/dotty/tools/dotc/typer/Applications.scala +++ b/src/dotty/tools/dotc/typer/Applications.scala @@ -429,9 +429,9 @@ trait Applications extends Compatibility { self: Typer => } /** Subclass of Application for type checking an Apply node with untyped arguments. */ - class ApplyToUntyped(app: untpd.Apply, fun: Tree, methRef: TermRef, args: List[untpd.Tree], resultType: Type)(implicit ctx: Context) - extends TypedApply(app, fun, methRef, args, resultType) { - def typedArg(arg: untpd.Tree, formal: Type): TypedArg = typed(arg, formal) + class ApplyToUntyped(app: untpd.Apply, fun: Tree, methRef: TermRef, proto: FunProto, resultType: Type)(implicit ctx: Context) + extends TypedApply(app, fun, methRef, proto.args, resultType) { + def typedArg(arg: untpd.Tree, formal: Type): TypedArg = proto.typedArg(arg, formal) def treeToArg(arg: Tree): untpd.Tree = untpd.TypedSplice(arg) } @@ -462,7 +462,7 @@ trait Applications extends Compatibility { self: Typer => tryEither { implicit ctx => val app = if (proto.argsAreTyped) new ApplyToTyped(tree, fun1, funRef, proto.typedArgs, pt) - else new ApplyToUntyped(tree, fun1, funRef, tree.args, pt) + else new ApplyToUntyped(tree, fun1, funRef, proto, pt) val result = app.result ConstFold(result) orElse result } { failed => fun1 match { @@ -530,6 +530,26 @@ trait Applications extends Compatibility { self: Typer => def typedUnApply(tree: untpd.Apply, pt: Type)(implicit ctx: Context): Tree = track("typedUnApply") { val Apply(qual, args) = tree + def notAnExtractor(tree: Tree) = + errorTree(tree, s"${qual.show} cannot be used as an extractor in a pattern because it lacks an unapply or unapplySeq method") + + val unapply = { + val dummyArg = untpd.TypedSplice(dummyTreeOfType(WildcardType)) + val unappProto = FunProto(dummyArg :: Nil, pt, this) + tryEither { + implicit ctx => typedExpr(untpd.Select(qual, nme.unapply), unappProto) + } { + s => + tryEither { + implicit ctx => typedExpr(untpd.Select(qual, nme.unapplySeq), unappProto) // for backwards compatibility; will be dropped + } { + _ => notAnExtractor(s.value) + } + } + } + + def fromScala2x = unapply.symbol.exists && (unapply.symbol.owner is Scala2x) + def unapplyArgs(unapplyResult: Type)(implicit ctx: Context): List[Type] = { def recur(tp: Type): List[Type] = { def extractorMemberType(name: Name) = { @@ -547,8 +567,11 @@ trait Applications extends Compatibility { self: Typer => sels.takeWhile(_.exists).toList } def seqSelector = defn.RepeatedParamType.appliedTo(tp.elemType :: Nil) - - if (tp derivesFrom defn.ProductClass) productSelectors + def optionSelectors(tp: Type): List[Type] = + if (defn.isTupleType(tp)) tp.typeArgs else tp :: Nil + if (fromScala2x && (tp isRef defn.OptionClass) && tp.typeArgs.length == 1) + optionSelectors(tp.typeArgs.head) + else if (tp derivesFrom defn.ProductClass) productSelectors else if (tp derivesFrom defn.SeqClass) seqSelector :: Nil else if (tp isRef defn.BooleanClass) Nil else if (extractorMemberType(nme.isDefined).exists && @@ -562,43 +585,42 @@ trait Applications extends Compatibility { self: Typer => recur(unapplyResult) } - def notAnExtractor(tree: Tree) = - errorTree(tree, s"${qual.show} cannot be used as an extractor in a pattern because it lacks an unapply or unapplySeq method") - - val unapply = { - val dummyArg = untpd.TypedSplice(dummyTreeOfType(WildcardType)) - val unappProto = FunProto(dummyArg :: Nil, pt, this) - tryEither { - implicit ctx => typedExpr(untpd.Select(qual, nme.unapply), unappProto) - } { - s => - tryEither { - implicit ctx => typedExpr(untpd.Select(qual, nme.unapplySeq), unappProto) // for backwards compatibility; will be dropped - } { - _ => notAnExtractor(s.value) - } - } - } - unapply.tpe.widen match { case mt: MethodType if !mt.isDependent => val unapplyArgType = mt.paramTypes.head + println(s"unapp arg tpe = ${unapplyArgType.show}, pt = ${pt.show}") val ownType = if (pt <:< unapplyArgType) { - assert(isFullyDefined(unapplyArgType)) + fullyDefinedType(unapplyArgType, "extractor argument", tree.pos) + println(i"case 1 $unapplyArgType ${ctx.typerState.constraint}") pt } - else if (unapplyArgType <:< widenForSelector(pt)) + else if (unapplyArgType <:< widenForSelector(pt)) { ctx.maximizeType(unapplyArgType) match { - case None => unapplyArgType case Some(tvar) => - errorType( + def msg = s"""There is no best instantiation of pattern type ${unapplyArgType.show} |that makes it a subtype of selector type ${pt.show}. - |Non-variant type variable ${tvar.origin.show} cannot be uniquely instantiated.""".stripMargin, - tree.pos) + |Non-variant type variable ${tvar.origin.show} cannot be uniquely instantiated.""".stripMargin + if (fromScala2x) { + // We can't issue an error here, because in Scala 2, ::[B] is invariant + // whereas List[+T] is covariant. According to the strict rule, a pattern + // match of a List[C] against a case x :: xs is illegal, because + // B cannot be uniquely instantiated. Of course :: should have been + // covariant in the first place, but in the Scala libraries it isn't. + // So for now we allow these kinds of patterns, even though they + // can open unsoundness holes. See SI-7952 for an example of the hole this opens. + if (ctx.settings.verbose.value) ctx.warning(msg, tree.pos) + } + else { + println(s" ${unapply.symbol.owner} ${unapply.symbol.owner is Scala2x}") + ctx.error(msg, tree.pos) + } + case _ => } - else errorType( + println(i"case 2 $unapplyArgType ${ctx.typerState.constraint}") + unapplyArgType + } else errorType( s"Pattern type ${unapplyArgType.show} is neither a subtype nor a supertype of selector type ${pt.show}", tree.pos) @@ -608,12 +630,13 @@ trait Applications extends Compatibility { self: Typer => case _ => args } if (argTypes.length != bunchedArgs.length) { - ctx.error(s"wrong number of argument patterns for ${err.patternConstrStr(unapply)}", tree.pos) + ctx.error(i"wrong number of argument patterns for $qual; expected: ($argTypes%, %)", tree.pos) argTypes = argTypes.take(args.length) ++ List.fill(argTypes.length - args.length)(WildcardType) } val typedArgs = (bunchedArgs, argTypes).zipped map (typed(_, _)) val result = cpy.UnApply(tree, unapply, typedArgs) withType ownType + println(s"typedargs = $typedArgs") if ((ownType eq pt) || ownType.isError) result else Typed(result, TypeTree(ownType)) case tp => diff --git a/src/dotty/tools/dotc/typer/Inferencing.scala b/src/dotty/tools/dotc/typer/Inferencing.scala index 8fbff0914..805d64898 100644 --- a/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/src/dotty/tools/dotc/typer/Inferencing.scala @@ -8,7 +8,7 @@ import Contexts._, Types._, Flags._, Denotations._, Names._, StdNames._, NameOps import Trees._ import annotation.unchecked import util.Positions._ -import util.Stats +import util.{Stats, SimpleMap} import Decorators._ import ErrorReporting.{errorType, InfoString} import collection.mutable.ListBuffer @@ -64,18 +64,32 @@ object Inferencing { object AnySelectionProto extends SelectionProto(nme.WILDCARD, WildcardType) case class FunProto(args: List[untpd.Tree], override val resultType: Type, typer: Typer)(implicit ctx: Context) extends UncachedGroundType with ProtoType { - private var myTypedArgs: List[Tree] = null + private var myTypedArgs: List[Tree] = Nil def isMatchedBy(tp: Type)(implicit ctx: Context) = typer.isApplicableToTrees(tp, typedArgs, resultType) - def argsAreTyped: Boolean = myTypedArgs != null + def argsAreTyped: Boolean = myTypedArgs.nonEmpty || args.isEmpty def typedArgs: List[Tree] = { - if (myTypedArgs == null) - myTypedArgs = args mapconserve (typer.typed(_)) + if (!argsAreTyped) + myTypedArgs = args mapconserve { arg => + val targ = myTypedArg(arg) + if (targ != null) targ else typer.typed(arg) + } myTypedArgs } + + private var myTypedArg: SimpleMap[untpd.Tree, Tree] = SimpleMap.Empty + + def typedArg(arg: untpd.Tree, formal: Type)(implicit ctx: Context): Tree = { + var targ = myTypedArg(arg) + if (targ == null) { + targ = typer.typedUnadapted(arg, formal) + myTypedArg = myTypedArg.updated(arg, targ) + } + typer.interpolateAndAdapt(targ, formal) + } } case class ViewProto(argType: Type, override val resultType: Type)(implicit ctx: Context) extends CachedGroundType with ProtoType { @@ -109,6 +123,13 @@ object Inferencing { } } + /** An enumeration controlling the degree of forcing in "is-dully-defined" checks. */ + object ForceDegree extends Enumeration { + val none, // don't force type variables + noBottom, // force type variables, fail if forced to Nothing or Null + all = Value // force type variables, don't fail + } + /** Is type fully defined, meaning the type does not contain wildcard types * or uninstantiated type variables. As a side effect, this will minimize * any uninstantiated type variables, provided that @@ -116,30 +137,27 @@ object Inferencing { * - the overall result of `isFullYDefined` is `true`. * Variables that are successfully minimized do not count as uninstantiated. */ - def isFullyDefined(tp: Type, forceIt: Boolean = false)(implicit ctx: Context): Boolean = { + def isFullyDefined(tp: Type, force: ForceDegree.Value)(implicit ctx: Context): Boolean = { val nestedCtx = ctx.fresh.withNewTyperState - val result = new IsFullyDefinedAccumulator(forceIt)(nestedCtx).traverse(tp) + val result = new IsFullyDefinedAccumulator(force)(nestedCtx).traverse(tp) if (result) nestedCtx.typerState.commit() result } - def forceFullyDefined(tp: Type)(implicit ctx: Context): Boolean = - isFullyDefined(tp, forceIt = true) - def fullyDefinedType(tp: Type, what: String, pos: Position)(implicit ctx: Context) = - if (forceFullyDefined(tp)) tp + if (isFullyDefined(tp, ForceDegree.all)) tp else errorType(i"internal error: type of $what $tp is not fully defined", pos) - private class IsFullyDefinedAccumulator(forceIt: Boolean)(implicit ctx: Context) extends TypeAccumulator[Boolean] { + private class IsFullyDefinedAccumulator(force: ForceDegree.Value)(implicit ctx: Context) extends TypeAccumulator[Boolean] { def traverse(tp: Type): Boolean = apply(true, tp) def apply(x: Boolean, tp: Type) = !x || isOK(tp) && foldOver(x, tp) def isOK(tp: Type): Boolean = tp match { case _: WildcardType => false - case tvar: TypeVar if forceIt && !tvar.isInstantiated => + case tvar: TypeVar if force != ForceDegree.none && !tvar.isInstantiated => val inst = tvar.instantiate(fromBelow = true) println(i"forced instantiation of ${tvar.origin} = $inst") - inst != defn.NothingType && inst != defn.NullType && traverse(inst) + (force == ForceDegree.all || inst != defn.NothingType && inst != defn.NullType) && traverse(inst) case _ => true } @@ -239,8 +257,8 @@ object Inferencing { else if (v == -1) tvar.instantiate(fromBelow = true) else { val bounds @ TypeBounds(lo, hi) = ctx.typerState.constraint(tvar.origin) - if (hi <:< lo) tvar.instantiate(fromBelow = false) - else result = Some(tvar) + if (!(hi <:< lo)) result = Some(tvar) + tvar.instantiate(fromBelow = false) } result } diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index efb89cc4c..e9c6b0d08 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -460,7 +460,7 @@ class Typer extends Namer with Applications with Implicits { val result = cpy.Block(tree, stats1, expr1).withType(blockType(stats1, expr1.tpe)) val leaks = CheckTrees.escapingRefs(result) if (leaks.isEmpty) result - else if (forceFullyDefined(pt)) { + else if (isFullyDefined(pt, ForceDegree.all)) { val expr2 = typed(untpd.Typed(untpd.TypedSplice(expr1), untpd.TypeTree(pt))) untpd.Block(stats1, expr2) withType expr2.tpe } else @@ -496,8 +496,14 @@ class Typer extends Namer with Applications with Implicits { if (!param.tpt.isEmpty) param else { val paramType = - if (forceFullyDefined(formal)) formal - else errorType("missing parameter type", param.pos) + if (isFullyDefined(formal, ForceDegree.noBottom)) formal + else { + val ofFun = + if (nme.syntheticParamNames(args.length + 1) contains param.name) + s" for expanded function ${tree.show}" + else "" + errorType(s"missing parameter type for parameter ${param.name}$ofFun, expected = ${pt.show}", param.pos) + } cpy.ValDef(param, param.mods, param.name, untpd.TypeTree(paramType), param.rhs) } typed(desugar.makeClosure(inferredParams, body), pt) @@ -617,7 +623,7 @@ class Typer extends Namer with Applications with Implicits { def typedTypeTree(tree: untpd.TypeTree, pt: Type)(implicit ctx: Context): TypeTree = track("typedTypeTree") { val (original1, ownType) = tree.original match { case untpd.EmptyTree => - assert(isFullyDefined(pt)) + assert(isFullyDefined(pt, ForceDegree.none)) (EmptyTree, pt) case original: ValDef => val meth = symbolOfTree(original) @@ -1073,7 +1079,7 @@ class Typer extends Namer with Applications with Implicits { if defn.isFunctionType(wtp) && !defn.isFunctionType(pt) => pt match { case SAMType(meth) - if wtp <:< meth.info.toFunctionType && isFullyDefined(pt) => + if wtp <:< meth.info.toFunctionType && isFullyDefined(pt, ForceDegree.noBottom) => return cpy.Closure(tree, Nil, id, TypeTree(pt)).withType(pt) case _ => } |