From 5212aaf439d0235b436aa71c156520ddecaf0397 Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Thu, 15 Aug 2013 15:02:17 -0700 Subject: Crasher in symbol tracer. --- src/reflect/scala/reflect/internal/util/TraceSymbolActivity.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/reflect/scala/reflect/internal/util/TraceSymbolActivity.scala b/src/reflect/scala/reflect/internal/util/TraceSymbolActivity.scala index 97cc19952c..f61c1f3c50 100644 --- a/src/reflect/scala/reflect/internal/util/TraceSymbolActivity.scala +++ b/src/reflect/scala/reflect/internal/util/TraceSymbolActivity.scala @@ -92,7 +92,7 @@ trait TraceSymbolActivity { while (ph != NoPhase && ph.name != "erasure") { ph = ph.prev } - ph + if (ph eq NoPhase) phase else ph } private def runBeforeErasure[T](body: => T): T = enteringPhase(findErasurePhase)(body) -- cgit v1.2.3 From 5eb5ad51c75573e51ad48d1b0bf06879de2b4ca6 Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Thu, 15 Aug 2013 15:02:18 -0700 Subject: Repair NPE in -Ytyper-debug output. --- src/reflect/scala/reflect/internal/TypeDebugging.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/reflect/scala/reflect/internal/TypeDebugging.scala b/src/reflect/scala/reflect/internal/TypeDebugging.scala index 9c1342e68e..fd64d98ca2 100644 --- a/src/reflect/scala/reflect/internal/TypeDebugging.scala +++ b/src/reflect/scala/reflect/internal/TypeDebugging.scala @@ -36,7 +36,7 @@ trait TypeDebugging { case ObjectClass => true case _ => sym.hasPackageFlag } - def skipType(tpe: Type): Boolean = skipSym(tpe.typeSymbolDirect) + def skipType(tpe: Type): Boolean = (tpe eq null) || skipSym(tpe.typeSymbolDirect) def skip(t: Tree): Boolean = t match { case EmptyTree => true -- cgit v1.2.3 From 130b5d79e039352f21ae5fb05ef987ba2ef56ac2 Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Thu, 15 Aug 2013 15:02:19 -0700 Subject: Make memberType less crashy. It's a source of constant peril that sym.tpe on NoSymbol is fine (it's NoType) but tpe memberType sym on NoSymbol throws a NSDNHO. The last thing we should be doing is discouraging people from using memberType in favor of sym.tpe, the latter being almost always the wrong thing. --- src/reflect/scala/reflect/internal/Types.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/reflect/scala/reflect/internal/Types.scala b/src/reflect/scala/reflect/internal/Types.scala index 71f46fedb7..0639a8e3f0 100644 --- a/src/reflect/scala/reflect/internal/Types.scala +++ b/src/reflect/scala/reflect/internal/Types.scala @@ -704,7 +704,7 @@ trait Types case OverloadedType(_, alts) => OverloadedType(this, alts) case tp => - tp.asSeenFrom(this, sym.owner) + if (sym eq NoSymbol) NoType else tp.asSeenFrom(this, sym.owner) } /** Substitute types `to` for occurrences of references to -- cgit v1.2.3 From 37e43d0aab15ba563d15442b1855e3355a8a1db6 Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Thu, 15 Aug 2013 15:02:18 -0700 Subject: Add some standard names used in pattern matcher. --- src/reflect/scala/reflect/internal/StdNames.scala | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src') diff --git a/src/reflect/scala/reflect/internal/StdNames.scala b/src/reflect/scala/reflect/internal/StdNames.scala index 64713b8d41..7a2287664a 100644 --- a/src/reflect/scala/reflect/internal/StdNames.scala +++ b/src/reflect/scala/reflect/internal/StdNames.scala @@ -626,6 +626,7 @@ trait StdNames { val clone_ : NameType = "clone" val collection: NameType = "collection" val conforms: NameType = "conforms" + val compare: NameType = "compare" val copy: NameType = "copy" val create: NameType = "create" val currentMirror: NameType = "currentMirror" @@ -657,6 +658,7 @@ trait StdNames { val get: NameType = "get" val hashCode_ : NameType = "hashCode" val hash_ : NameType = "hash" + val head : NameType = "head" val immutable: NameType = "immutable" val implicitly: NameType = "implicitly" val in: NameType = "in" @@ -725,6 +727,7 @@ trait StdNames { val toArray: NameType = "toArray" val toList: NameType = "toList" val toObjectArray : NameType = "toObjectArray" + val toSeq: NameType = "toSeq" val TopScope: NameType = "TopScope" val toString_ : NameType = "toString" val toTypeConstructor: NameType = "toTypeConstructor" -- cgit v1.2.3 From 4334f4c4a47f7e2dc0c382ada7d1a683bdfbf215 Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Thu, 15 Aug 2013 15:02:18 -0700 Subject: Some general purpose methods. Motivated by pattern matcher work, also useful elsewhere. --- src/compiler/scala/tools/nsc/ast/TreeDSL.scala | 1 + .../scala/reflect/internal/Definitions.scala | 67 +++++++++++++++++++++- src/reflect/scala/reflect/internal/TreeInfo.scala | 9 +-- src/reflect/scala/reflect/internal/Types.scala | 5 +- 4 files changed, 76 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/ast/TreeDSL.scala b/src/compiler/scala/tools/nsc/ast/TreeDSL.scala index 66ed0c8fae..d7a32c3be0 100644 --- a/src/compiler/scala/tools/nsc/ast/TreeDSL.scala +++ b/src/compiler/scala/tools/nsc/ast/TreeDSL.scala @@ -83,6 +83,7 @@ trait TreeDSL { def INT_>= (other: Tree) = fn(target, getMember(IntClass, nme.GE), other) def INT_== (other: Tree) = fn(target, getMember(IntClass, nme.EQ), other) + def INT_- (other: Tree) = fn(target, getMember(IntClass, nme.MINUS), other) // generic operations on ByteClass, IntClass, LongClass def GEN_| (other: Tree, kind: ClassSymbol) = fn(target, getMember(kind, nme.OR), other) diff --git a/src/reflect/scala/reflect/internal/Definitions.scala b/src/reflect/scala/reflect/internal/Definitions.scala index 6b7aa2dddf..f1480c6cbd 100644 --- a/src/reflect/scala/reflect/internal/Definitions.scala +++ b/src/reflect/scala/reflect/internal/Definitions.scala @@ -253,6 +253,13 @@ trait Definitions extends api.StandardDefinitions { || tp =:= AnyRefTpe ) + def hasMultipleNonImplicitParamLists(member: Symbol): Boolean = hasMultipleNonImplicitParamLists(member.info) + def hasMultipleNonImplicitParamLists(info: Type): Boolean = info match { + case PolyType(_, restpe) => hasMultipleNonImplicitParamLists(restpe) + case MethodType(_, MethodType(p :: _, _)) if !p.isImplicit => true + case _ => false + } + private def fixupAsAnyTrait(tpe: Type): Type = tpe match { case ClassInfoType(parents, decls, clazz) => if (parents.head.typeSymbol == AnyClass) tpe @@ -384,6 +391,7 @@ trait Definitions extends api.StandardDefinitions { def arrayCloneMethod = getMemberMethod(ScalaRunTimeModule, nme.array_clone) def ensureAccessibleMethod = getMemberMethod(ScalaRunTimeModule, nme.ensureAccessible) def arrayClassMethod = getMemberMethod(ScalaRunTimeModule, nme.arrayClass) + def traversableDropMethod = getMemberMethod(ScalaRunTimeModule, nme.drop) // classes with special meanings lazy val StringAddClass = requiredClass[scala.runtime.StringAdd] @@ -423,6 +431,15 @@ trait Definitions extends api.StandardDefinitions { def isVarArgsList(params: Seq[Symbol]) = params.nonEmpty && isRepeatedParamType(params.last.tpe) def isVarArgTypes(formals: Seq[Type]) = formals.nonEmpty && isRepeatedParamType(formals.last) + def firstParamType(tpe: Type): Type = tpe.paramTypes match { + case p :: _ => p + case _ => NoType + } + def isImplicitParamss(paramss: List[List[Symbol]]) = paramss match { + case (p :: _) :: _ => p.isImplicit + case _ => false + } + def hasRepeatedParam(tp: Type): Boolean = tp match { case MethodType(formals, restpe) => isScalaVarArgs(formals) || hasRepeatedParam(restpe) case PolyType(_, restpe) => hasRepeatedParam(restpe) @@ -430,7 +447,12 @@ trait Definitions extends api.StandardDefinitions { } // wrapping and unwrapping - def dropByName(tp: Type): Type = elementExtract(ByNameParamClass, tp) orElse tp + def dropByName(tp: Type): Type = elementExtract(ByNameParamClass, tp) orElse tp + def dropRepeated(tp: Type): Type = ( + if (isJavaRepeatedParamType(tp)) elementExtract(JavaRepeatedParamClass, tp) orElse tp + else if (isScalaRepeatedParamType(tp)) elementExtract(RepeatedParamClass, tp) orElse tp + else tp + ) def repeatedToSingle(tp: Type): Type = elementExtract(RepeatedParamClass, tp) orElse tp def repeatedToSeq(tp: Type): Type = elementTransform(RepeatedParamClass, tp)(seqType) orElse tp def seqToRepeated(tp: Type): Type = elementTransform(SeqClass, tp)(scalaRepeatedType) orElse tp @@ -663,6 +685,26 @@ trait Definitions extends api.StandardDefinitions { case Some(x) => tpe.baseType(x).typeArgs case _ => Nil } + def getNameBasedProductSelectors(tpe: Type): List[Symbol] = { + def loop(n: Int): List[Symbol] = tpe member TermName("_" + n) match { + case NoSymbol => Nil + case m if m.paramss.nonEmpty => Nil + case m => m :: loop(n + 1) + } + loop(1) + } + def getNameBasedProductSelectorTypes(tpe: Type): List[Type] = getProductArgs(tpe) match { + case xs if xs.nonEmpty => xs + case _ => getterMemberTypes(tpe, getNameBasedProductSelectors(tpe)) + } + + def getterMemberTypes(tpe: Type, getters: List[Symbol]): List[Type] = + getters map (m => dropNullaryMethod(tpe memberType m)) + + def getNameBasedProductSeqElementType(tpe: Type) = getNameBasedProductSelectorTypes(tpe) match { + case _ :+ elem => unapplySeqElementType(elem) + case _ => NoType + } def dropNullaryMethod(tp: Type) = tp match { case NullaryMethodType(restpe) => restpe @@ -696,6 +738,29 @@ trait Definitions extends api.StandardDefinitions { def scalaRepeatedType(arg: Type) = appliedType(RepeatedParamClass, arg) def seqType(arg: Type) = appliedType(SeqClass, arg) + def typeOfMemberNamedGet(tp: Type) = resultOfMatchingMethod(tp, nme.get)() + + def unapplySeqElementType(seqType: Type) = ( + resultOfMatchingMethod(seqType, nme.apply)(IntTpe) + orElse resultOfMatchingMethod(seqType, nme.head)() + ) + + /** If `tp` has a term member `name`, the first parameter list of which + * matches `paramTypes`, and which either has no further parameter + * lists or only an implicit one, then the result type of the matching + * method. Otherwise, NoType. + */ + def resultOfMatchingMethod(tp: Type, name: TermName)(paramTypes: Type*): Type = { + def matchesParams(member: Symbol) = member.paramss match { + case Nil => paramTypes.isEmpty + case ps :: rest => (rest.isEmpty || isImplicitParamss(rest)) && (ps corresponds paramTypes)(_.tpe =:= _) + } + tp member name filter matchesParams match { + case NoSymbol => NoType + case member => (tp memberType member).finalResultType + } + } + def ClassType(arg: Type) = if (phase.erasedTypes) ClassClass.tpe else appliedType(ClassClass, arg) /** Can we tell by inspecting the symbol that it will never diff --git a/src/reflect/scala/reflect/internal/TreeInfo.scala b/src/reflect/scala/reflect/internal/TreeInfo.scala index 5c92512193..d01f1ce681 100644 --- a/src/reflect/scala/reflect/internal/TreeInfo.scala +++ b/src/reflect/scala/reflect/internal/TreeInfo.scala @@ -488,7 +488,7 @@ abstract class TreeInfo { } object WildcardStarArg { - def unapply(tree: Typed): Option[Tree] = tree match { + def unapply(tree: Tree): Option[Tree] = tree match { case Typed(expr, Ident(tpnme.WILDCARD_STAR)) => Some(expr) case _ => None } @@ -628,11 +628,12 @@ abstract class TreeInfo { * case Extractor(a @ (b, c)) => 2 * }}} */ - def effectivePatternArity(args: List[Tree]): Int = (args.map(unbind) match { + def effectivePatternArity(args: List[Tree]): Int = flattenedPatternArgs(args).length + + def flattenedPatternArgs(args: List[Tree]): List[Tree] = args map unbind match { case Apply(fun, xs) :: Nil if isTupleSymbol(fun.symbol) => xs case xs => xs - }).length - + } // used in the symbols for labeldefs and valdefs emitted by the pattern matcher // tailcalls, cps,... use this flag combination to detect translated matches diff --git a/src/reflect/scala/reflect/internal/Types.scala b/src/reflect/scala/reflect/internal/Types.scala index 0639a8e3f0..94222565c4 100644 --- a/src/reflect/scala/reflect/internal/Types.scala +++ b/src/reflect/scala/reflect/internal/Types.scala @@ -4015,9 +4015,12 @@ trait Types def isErrorOrWildcard(tp: Type) = (tp eq ErrorType) || (tp eq WildcardType) + /** This appears to be equivalent to tp.isInstanceof[SingletonType], + * except it excludes ConstantTypes. + */ def isSingleType(tp: Type) = tp match { case ThisType(_) | SuperType(_, _) | SingleType(_, _) => true - case _ => false + case _ => false } def isConstantType(tp: Type) = tp match { -- cgit v1.2.3 From de1d8c3a89e95e1b934da05453f8e1fed925c838 Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Thu, 15 Aug 2013 15:02:18 -0700 Subject: Expand the understanding of bytecode tests. The new method is the same as sameMethodAndFieldSignatures, but ignores generic signatures. This allows for testing methods which receive the same descriptor but differing generic signatures. In particular, this happens with value classes, which get a generic signature where a method written in terms of the underlying values does not. --- src/partest/scala/tools/partest/AsmNode.scala | 7 ++++--- src/partest/scala/tools/partest/BytecodeTest.scala | 17 ++++++++++++++--- 2 files changed, 18 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/partest/scala/tools/partest/AsmNode.scala b/src/partest/scala/tools/partest/AsmNode.scala index d181436676..e6a91498d1 100644 --- a/src/partest/scala/tools/partest/AsmNode.scala +++ b/src/partest/scala/tools/partest/AsmNode.scala @@ -16,10 +16,11 @@ sealed trait AsmNode[+T] { def visibleAnnotations: List[AnnotationNode] def invisibleAnnotations: List[AnnotationNode] def characteristics = f"$name%15s $desc%-30s$accessString$sigString" + def erasedCharacteristics = f"$name%15s $desc%-30s$accessString" - private def accessString = if (access == 0) "" else " " + Modifier.toString(access) - private def sigString = if (signature == null) "" else " " + signature - override def toString = characteristics + private def accessString = if (access == 0) "" else " " + Modifier.toString(access) + private def sigString = if (signature == null) "" else " " + signature + override def toString = characteristics } object AsmNode { diff --git a/src/partest/scala/tools/partest/BytecodeTest.scala b/src/partest/scala/tools/partest/BytecodeTest.scala index 2690b784d1..7650a892fd 100644 --- a/src/partest/scala/tools/partest/BytecodeTest.scala +++ b/src/partest/scala/tools/partest/BytecodeTest.scala @@ -48,7 +48,18 @@ abstract class BytecodeTest extends ASMConverters { // descriptors and generic signatures? Method bodies are not considered, and // the names of the classes containing the methods are substituted so they do // not appear as differences. - def sameMethodAndFieldSignatures(clazzA: ClassNode, clazzB: ClassNode): Boolean = { + def sameMethodAndFieldSignatures(clazzA: ClassNode, clazzB: ClassNode) = + sameCharacteristics(clazzA, clazzB)(_.characteristics) + + // Same as sameMethodAndFieldSignatures, but ignoring generic signatures. + // This allows for methods which receive the same descriptor but differing + // generic signatures. In particular, this happens with value classes, + // which get a generic signature where a method written in terms of the + // underlying values does not. + def sameMethodAndFieldDescriptors(clazzA: ClassNode, clazzB: ClassNode) = + sameCharacteristics(clazzA, clazzB)(_.erasedCharacteristics) + + private def sameCharacteristics(clazzA: ClassNode, clazzB: ClassNode)(f: AsmNode[_] => String): Boolean = { val ms1 = clazzA.fieldsAndMethods.toIndexedSeq val ms2 = clazzB.fieldsAndMethods.toIndexedSeq val name1 = clazzA.name @@ -59,8 +70,8 @@ abstract class BytecodeTest extends ASMConverters { false } else (ms1, ms2).zipped forall { (m1, m2) => - val c1 = m1.characteristics - val c2 = m2.characteristics.replaceAllLiterally(name2, name1) + val c1 = f(m1) + val c2 = f(m2).replaceAllLiterally(name2, name1) if (c1 == c2) println(s"[ok] $m1") else -- cgit v1.2.3 From 44b4dcfc959d1305b96b21cc73a8e74aea865fa0 Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Thu, 15 Aug 2013 15:02:18 -0700 Subject: Add a helper method drop to ScalaRunTime. We should do a lot more of this - it's ridiculously difficult and error prone to generate code of this kind involving implicits, type inference, etc. where the same goal is trivially accomplished by generating a method call and letting the typer work out the details. --- src/library/scala/runtime/ScalaRunTime.scala | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/library/scala/runtime/ScalaRunTime.scala b/src/library/scala/runtime/ScalaRunTime.scala index 77fe2eb1e1..315f56bd4e 100644 --- a/src/library/scala/runtime/ScalaRunTime.scala +++ b/src/library/scala/runtime/ScalaRunTime.scala @@ -12,7 +12,7 @@ package runtime import scala.collection.{ Seq, IndexedSeq, TraversableView, AbstractIterator } import scala.collection.mutable.WrappedArray import scala.collection.immutable.{ StringLike, NumericRange, List, Stream, Nil, :: } -import scala.collection.generic.{ Sorted } +import scala.collection.generic.{ Sorted, IsTraversableLike } import scala.reflect.{ ClassTag, classTag } import scala.util.control.ControlThrowable import java.lang.{ Class => jClass } @@ -48,6 +48,10 @@ object ScalaRunTime { names.toSet } + // A helper method to make my life in the pattern matcher a lot easier. + def drop[Repr](coll: Repr, num: Int)(implicit traversable: IsTraversableLike[Repr]): Repr = + traversable conversion coll drop num + /** Return the class object representing an array with element class `clazz`. */ def arrayClass(clazz: jClass[_]): jClass[_] = { -- cgit v1.2.3 From 2a31f0a76b7c388d4be6d0d9dc642d455f91beca Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Thu, 15 Aug 2013 15:02:18 -0700 Subject: Pushed some noisy logging down to debuglog. --- src/compiler/scala/tools/nsc/CompilationUnits.scala | 2 +- .../scala/tools/nsc/symtab/classfile/ClassfileParser.scala | 4 ++-- src/reflect/scala/reflect/internal/SymbolTable.scala | 7 +++++++ src/reflect/scala/reflect/internal/tpe/TypeMaps.scala | 2 +- 4 files changed, 11 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/CompilationUnits.scala b/src/compiler/scala/tools/nsc/CompilationUnits.scala index efe436f004..f7437e4e6c 100644 --- a/src/compiler/scala/tools/nsc/CompilationUnits.scala +++ b/src/compiler/scala/tools/nsc/CompilationUnits.scala @@ -91,7 +91,7 @@ trait CompilationUnits { self: Global => debuglog(s"removing synthetic $sym from $self") map -= sym } - def get(sym: Symbol): Option[Tree] = logResultIf[Option[Tree]](s"found synthetic for $sym in $self", _.isDefined) { + def get(sym: Symbol): Option[Tree] = debuglogResultIf[Option[Tree]](s"found synthetic for $sym in $self", _.isDefined) { map get sym } def keys: Iterable[Symbol] = map.keys diff --git a/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala b/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala index 454c9db73c..14e3f5b642 100644 --- a/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala +++ b/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala @@ -659,14 +659,14 @@ abstract class ClassfileParser { } accept('>') assert(xs.length > 0, tp) - logResult("new existential")(newExistentialType(existentials.toList, typeRef(pre, classSym, xs.toList))) + debuglogResult("new existential")(newExistentialType(existentials.toList, typeRef(pre, classSym, xs.toList))) } // isMonomorphicType is false if the info is incomplete, as it usually is here // so have to check unsafeTypeParams.isEmpty before worrying about raw type case below, // or we'll create a boatload of needless existentials. else if (classSym.isMonomorphicType || classSym.unsafeTypeParams.isEmpty) tp // raw type - existentially quantify all type parameters - else logResult(s"raw type from $classSym")(unsafeClassExistentialType(classSym)) + else debuglogResult(s"raw type from $classSym")(unsafeClassExistentialType(classSym)) case tp => assert(sig.charAt(index) != '<', s"sig=$sig, index=$index, tp=$tp") tp diff --git a/src/reflect/scala/reflect/internal/SymbolTable.scala b/src/reflect/scala/reflect/internal/SymbolTable.scala index c340670635..afe2e41c3e 100644 --- a/src/reflect/scala/reflect/internal/SymbolTable.scala +++ b/src/reflect/scala/reflect/internal/SymbolTable.scala @@ -111,6 +111,13 @@ abstract class SymbolTable extends macros.Universe result } + @inline + final private[scala] def debuglogResultIf[T](msg: => String, cond: T => Boolean)(result: T): T = { + if (cond(result)) + debuglog(msg + ": " + result) + + result + } // For too long have we suffered in order to sort NAMES. // I'm pretty sure there's a reasonable default for that. diff --git a/src/reflect/scala/reflect/internal/tpe/TypeMaps.scala b/src/reflect/scala/reflect/internal/tpe/TypeMaps.scala index bebc419c7c..e73e0a542c 100644 --- a/src/reflect/scala/reflect/internal/tpe/TypeMaps.scala +++ b/src/reflect/scala/reflect/internal/tpe/TypeMaps.scala @@ -395,7 +395,7 @@ private[internal] trait TypeMaps { s"Widened lone occurrence of $tp1 inside existential to $word bound" } if (!repl.typeSymbol.isBottomClass && count == 1 && !containsTypeParam) - logResult(msg)(repl) + debuglogResult(msg)(repl) else tp1 case _ => -- cgit v1.2.3 From e7c61089df1773832b242b892bc7d4dbe5fcbc0f Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Thu, 15 Aug 2013 15:02:18 -0700 Subject: Removed some dead code in Uncurry. --- .../scala/tools/nsc/transform/UnCurry.scala | 25 ++++------------------ 1 file changed, 4 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala index e2ce2743f7..ca123f8782 100644 --- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala +++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala @@ -64,7 +64,6 @@ abstract class UnCurry extends InfoTransform class UnCurryTransformer(unit: CompilationUnit) extends TypingTransformer(unit) { private var needTryLift = false - private var inPattern = false private var inConstructorFlag = 0L private val byNameArgs = mutable.HashSet[Tree]() private val noApply = mutable.HashSet[Tree]() @@ -79,12 +78,6 @@ abstract class UnCurry extends InfoTransform @inline private def useNewMembers[T](owner: Symbol)(f: List[Tree] => T): T = f(newMembers.remove(owner).getOrElse(Nil).toList) - @inline private def withInPattern[T](value: Boolean)(body: => T): T = { - inPattern = value - try body - finally inPattern = !value - } - private def newFunction0(body: Tree): Tree = { val result = localTyper.typedPos(body.pos)(Function(Nil, body)).asInstanceOf[Function] log("Change owner from %s to %s in %s".format(currentOwner, result.symbol, result.body)) @@ -119,16 +112,6 @@ abstract class UnCurry extends InfoTransform && (isByName(tree.symbol)) ) - /** Uncurry a type of a tree node. - * This function is sensitive to whether or not we are in a pattern -- when in a pattern - * additional parameter sections of a case class are skipped. - */ - def uncurryTreeType(tp: Type): Type = tp match { - case MethodType(params, MethodType(params1, restpe)) if inPattern => - uncurryTreeType(MethodType(params, restpe)) - case _ => - uncurry(tp) - } // ------- Handling non-local returns ------------------------------------------------- @@ -327,7 +310,7 @@ abstract class UnCurry extends InfoTransform } else { def mkArray = mkArrayValue(args drop (formals.length - 1), varargsElemType) - if (isJava || inPattern) mkArray + if (isJava) mkArray else if (args.isEmpty) gen.mkNil // avoid needlessly double-wrapping an empty argument list else arrayToSequence(mkArray, varargsElemType) } @@ -474,7 +457,7 @@ abstract class UnCurry extends InfoTransform else super.transform(tree) case UnApply(fn, args) => - val fn1 = withInPattern(value = false)(transform(fn)) + val fn1 = transform(fn) val args1 = transformTrees(fn.symbol.name match { case nme.unapply => args case nme.unapplySeq => transformArgs(tree.pos, fn.symbol, args, analyzer.unapplyTypeList(fn.pos, fn.symbol, fn.tpe, args)) @@ -510,7 +493,7 @@ abstract class UnCurry extends InfoTransform else super.transform(tree) case CaseDef(pat, guard, body) => - val pat1 = withInPattern(value = true)(transform(pat)) + val pat1 = transform(pat) treeCopy.CaseDef(tree, pat1, transform(guard), transform(body)) case fun @ Function(_, _) => @@ -532,7 +515,7 @@ abstract class UnCurry extends InfoTransform } ) assert(result.tpe != null, result.shortClass + " tpe is null:\n" + result) - result setType uncurryTreeType(result.tpe) + result modifyType uncurry } def postTransform(tree: Tree): Tree = exitingUncurry { -- cgit v1.2.3 From 87d80ff0618e5bd964b37201371b91683e57df5d Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Thu, 15 Aug 2013 15:02:18 -0700 Subject: Positioned variations of inform/warning/globalError. Because who doesn't want a little positioning in their life. --- src/compiler/scala/tools/nsc/Global.scala | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index a6c69091c5..bfc4aefe06 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -220,12 +220,15 @@ class Global(var currentSettings: Settings, var reporter: Reporter) // not deprecated yet, but a method called "error" imported into // nearly every trait really must go. For now using globalError. - def error(msg: String) = globalError(msg) - override def inform(msg: String) = reporter.echo(msg) - override def globalError(msg: String) = reporter.error(NoPosition, msg) - override def warning(msg: String) = - if (settings.fatalWarnings) globalError(msg) - else reporter.warning(NoPosition, msg) + def error(msg: String) = globalError(msg) + + override def inform(msg: String) = inform(NoPosition, msg) + override def globalError(msg: String) = globalError(NoPosition, msg) + override def warning(msg: String) = warning(NoPosition, msg) + + def globalError(pos: Position, msg: String) = reporter.error(pos, msg) + def warning(pos: Position, msg: String) = if (settings.fatalWarnings) globalError(pos, msg) else reporter.warning(pos, msg) + def inform(pos: Position, msg: String) = reporter.echo(pos, msg) // Getting in front of Predef's asserts to supplement with more info. // This has the happy side effect of masking the one argument forms @@ -264,11 +267,13 @@ class Global(var currentSettings: Settings, var reporter: Reporter) * logging mechanism. !!! is prefixed to all messages issued via this route * to make them visually distinct. */ - @inline final override def devWarning(msg: => String) { + @inline final override def devWarning(msg: => String): Unit = devWarning(NoPosition, msg) + @inline final def devWarning(pos: Position, msg: => String) { + def pos_s = if (pos eq NoPosition) "" else s" [@ $pos]" if (settings.developer || settings.debug) - warning("!!! " + msg) + warning(pos, "!!! " + msg) else - log("!!! " + msg) // such warnings always at least logged + log(s"!!!$pos_s $msg") // such warnings always at least logged } def informComplete(msg: String): Unit = reporter.withoutTruncating(inform(msg)) -- cgit v1.2.3 From b1d72f1ac7b4b2026c4fad3453195db8bb2918bc Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Thu, 15 Aug 2013 15:02:18 -0700 Subject: Cosmetic cleanup in the matcher. --- .../scala/tools/nsc/transform/patmat/Logic.scala | 7 ++-- .../tools/nsc/transform/patmat/MatchAnalysis.scala | 41 ++++++++++------------ .../nsc/transform/patmat/PatternMatching.scala | 4 +++ 3 files changed, 25 insertions(+), 27 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/transform/patmat/Logic.scala b/src/compiler/scala/tools/nsc/transform/patmat/Logic.scala index 069484ff65..e49dd22948 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/Logic.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/Logic.scala @@ -13,7 +13,6 @@ import scala.reflect.internal.util.Statistics import scala.reflect.internal.util.Position import scala.reflect.internal.util.HashSet - trait Logic extends Debugging { import PatternMatchingStats._ @@ -494,7 +493,7 @@ trait ScalaLogic extends Interface with Logic with TreeAndTypeAnalysis { import global.{ConstantType, Constant, SingletonType, Literal, Ident, singleType} - import global.definitions.{AnyClass, UnitClass} + import global.definitions._ // all our variables range over types @@ -549,7 +548,7 @@ trait ScalaLogic extends Interface with Logic with TreeAndTypeAnalysis { def tp: Type def wideTp: Type - def isAny = wideTp.typeSymbol == AnyClass + def isAny = wideTp =:= AnyTpe def isValue: Boolean //= tp.isStable // note: use reference equality on Const since they're hash-consed (doing type equality all the time is too expensive) @@ -606,7 +605,7 @@ trait ScalaLogic extends Interface with Logic with TreeAndTypeAnalysis { if (tp.isInstanceOf[SingletonType]) tp else p match { case Literal(c) => - if (c.tpe.typeSymbol == UnitClass) c.tpe + if (c.tpe =:= UnitTpe) c.tpe else ConstantType(c) case Ident(_) if p.symbol.isStable => // for Idents, can encode uniqueness of symbol as uniqueness of the corresponding singleton type diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala index f089c8f5a5..dec92be017 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala @@ -21,7 +21,7 @@ trait TreeAndTypeAnalysis extends Debugging { // unfortunately this is not true in general: // SI-6022 expects instanceOfTpImplies(ProductClass.tpe, AnyRefTpe) def instanceOfTpImplies(tp: Type, tpImplied: Type) = { - val tpValue = tp.typeSymbol.isPrimitiveValueClass + val tpValue = isPrimitiveValueType(tp) // pretend we're comparing to Any when we're actually comparing to AnyVal or AnyRef // (and the subtype is respectively a value type or not a value type) @@ -59,17 +59,20 @@ trait TreeAndTypeAnalysis extends Debugging { debug.patmat("enum unsealed "+ ((tp, sym, sym.isSealed, isPrimitiveValueClass(sym)))) None case sym => - val subclasses = ( - sym.sealedDescendants.toList sortBy (_.sealedSortName) + val subclasses = debug.patmatResult(s"enum $sym sealed, subclasses")( // symbols which are both sealed and abstract need not be covered themselves, because // all of their children must be and they cannot otherwise be created. - filterNot (x => x.isSealed && x.isAbstractClass && !isPrimitiveValueClass(x))) - debug.patmat("enum sealed -- subclasses: "+ ((sym, subclasses))) + sym.sealedDescendants.toList + sortBy (_.sealedSortName) + filterNot (x => x.isSealed && x.isAbstractClass && !isPrimitiveValueClass(x)) + ) val tpApprox = typer.infer.approximateAbstracts(tp) val pre = tpApprox.prefix + + Some(debug.patmatResult(s"enum sealed tp=$tp, tpApprox=$tpApprox as") { // valid subtypes are turned into checkable types, as we are entering the realm of the dynamic - val validSubTypes = (subclasses flatMap {sym => + subclasses flatMap { sym => // have to filter out children which cannot match: see ticket #3683 for an example // compare to the fully known type `tp` (modulo abstract types), // so that we can rule out stuff like: sealed trait X[T]; class XInt extends X[Int] --> XInt not valid when enumerating X[String] @@ -81,9 +84,8 @@ trait TreeAndTypeAnalysis extends Debugging { // debug.patmat("subtp"+(subTpApprox <:< tpApprox, subTpApprox, tpApprox)) if (subTpApprox <:< tpApprox) Some(checkableType(subTp)) else None - }) - debug.patmat("enum sealed "+ ((tp, tpApprox)) + " as "+ validSubTypes) - Some(validSubTypes) + } + }) } // approximate a type to the static type that is fully checkable at run time, @@ -104,10 +106,7 @@ trait TreeAndTypeAnalysis extends Debugging { mapOver(tp) } } - - val res = typeArgsToWildcardsExceptArray(tp) - debug.patmat("checkable "+((tp, res))) - res + debug.patmatResult(s"checkableType($tp)")(typeArgsToWildcardsExceptArray(tp)) } // a type is "uncheckable" (for exhaustivity) if we don't statically know its subtypes (i.e., it's unsealed) @@ -136,20 +135,17 @@ trait MatchApproximation extends TreeAndTypeAnalysis with ScalaLogic with MatchT var currId = 0 } case class Test(prop: Prop, treeMaker: TreeMaker) { - // private val reusedBy = new scala.collection.mutable.HashSet[Test] + // private val reusedBy = new mutable.HashSet[Test] var reuses: Option[Test] = None def registerReuseBy(later: Test): Unit = { assert(later.reuses.isEmpty, later.reuses) // reusedBy += later later.reuses = Some(this) } - val id = { Test.currId += 1; Test.currId} - override def toString = - "T"+ id + "C("+ prop +")" //+ (reuses map ("== T"+_.id) getOrElse (if(reusedBy.isEmpty) treeMaker else reusedBy mkString (treeMaker+ " -->(", ", ",")"))) + override def toString = s"T${id}C($prop)" } - class TreeMakersToPropsIgnoreNullChecks(root: Symbol) extends TreeMakersToProps(root) { override def uniqueNonNullProp(p: Tree): Prop = True } @@ -158,9 +154,9 @@ trait MatchApproximation extends TreeAndTypeAnalysis with ScalaLogic with MatchT class TreeMakersToProps(val root: Symbol) { prepareNewAnalysis() // reset hash consing for Var and Const - private[this] val uniqueEqualityProps = new scala.collection.mutable.HashMap[(Tree, Tree), Eq] - private[this] val uniqueNonNullProps = new scala.collection.mutable.HashMap[Tree, Not] - private[this] val uniqueTypeProps = new scala.collection.mutable.HashMap[(Tree, Type), Eq] + private[this] val uniqueEqualityProps = new mutable.HashMap[(Tree, Tree), Eq] + private[this] val uniqueNonNullProps = new mutable.HashMap[Tree, Not] + private[this] val uniqueTypeProps = new mutable.HashMap[(Tree, Type), Eq] def uniqueEqualityProp(testedPath: Tree, rhs: Tree): Prop = uniqueEqualityProps getOrElseUpdate((testedPath, rhs), Eq(Var(testedPath), ValueConst(rhs))) @@ -686,8 +682,7 @@ trait MatchAnalysis extends MatchApproximation { // TODO: improve reasoning -- in the mean time, a false negative is better than an annoying false positive case _ => NoExample } - debug.patmat("described as: "+ res) - res + debug.patmatResult("described as")(res) } override def toString = toCounterExample().toString diff --git a/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala b/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala index 63834ae51e..b37b1d8550 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala @@ -95,6 +95,10 @@ trait Debugging { object debug { val printPatmat = global.settings.Ypatmatdebug.value @inline final def patmat(s: => String) = if (printPatmat) println(s) + @inline final def patmatResult[T](s: => String)(result: T): T = { + if (printPatmat) Console.err.println(s + ": " + result) + result + } } } -- cgit v1.2.3 From b38c9289308334af940aea2ee0ab1d2e65a88ccb Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Thu, 15 Aug 2013 15:02:18 -0700 Subject: Cleanups in Unapplies. --- .../tools/nsc/typechecker/MethodSynthesis.scala | 4 +- .../scala/tools/nsc/typechecker/Unapplies.scala | 48 ++++++++-------------- src/reflect/scala/reflect/internal/Trees.scala | 8 ++++ 3 files changed, 27 insertions(+), 33 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala b/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala index 546186479f..3a5845c8ca 100644 --- a/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala +++ b/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala @@ -25,7 +25,7 @@ trait MethodSynthesis { type TT[T] = ru.TypeTag[T] type CT[T] = ClassTag[T] - def ValOrDefDef(sym: Symbol, body: Tree) = + def newValOrDefDef(sym: Symbol, body: Tree) = if (sym.isLazy) ValDef(sym, body) else DefDef(sym, body) @@ -67,7 +67,7 @@ trait MethodSynthesis { } private def finishMethod(method: Symbol, f: Symbol => Tree): Tree = - localTyper typed ValOrDefDef(method, f(method)) + localTyper typed newValOrDefDef(method, f(method)) private def createInternal(name: Name, f: Symbol => Tree, info: Type): Tree = { val name1 = name.toTermName diff --git a/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala b/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala index 47c859bb5c..0e2c836860 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala @@ -12,8 +12,7 @@ import symtab.Flags._ * @author Martin Odersky * @version 1.0 */ -trait Unapplies extends ast.TreeDSL -{ +trait Unapplies extends ast.TreeDSL { self: Analyzer => import global._ @@ -21,7 +20,8 @@ trait Unapplies extends ast.TreeDSL import CODE.{ CASE => _, _ } import treeInfo.{ isRepeatedParamType, isByNameParamType } - private val unapplyParamName = nme.x_0 + private def unapplyParamName = nme.x_0 + private def caseMods = Modifiers(SYNTHETIC | CASE) // In the typeCompleter (templateSig) of a case class (resp it's module), // synthetic `copy` (reps `apply`, `unapply`) methods are added. To compute @@ -47,16 +47,10 @@ trait Unapplies extends ast.TreeDSL } /** returns unapply or unapplySeq if available */ - def unapplyMember(tp: Type): Symbol = (tp member nme.unapply) match { - case NoSymbol => tp member nme.unapplySeq - case unapp => unapp - } + def unapplyMember(tp: Type): Symbol = (tp member nme.unapply) orElse (tp member nme.unapplySeq) object ExtractorType { - def unapply(tp: Type): Option[Symbol] = { - val member = unapplyMember(tp) - if (member.exists) Some(member) else None - } + def unapply(tp: Type): Option[Symbol] = unapplyMember(tp).toOption } /** returns unapply member's parameter type. */ @@ -93,25 +87,19 @@ trait Unapplies extends ast.TreeDSL */ private def caseClassUnapplyReturnValue(param: Name, caseclazz: ClassDef) = { def caseFieldAccessorValue(selector: ValDef): Tree = { - val accessorName = selector.name - val privateLocalParamAccessor = caseclazz.impl.body.collectFirst { - case dd: ValOrDefDef if dd.name == accessorName && dd.mods.isPrivateLocal => dd.symbol - } - privateLocalParamAccessor match { - case None => - // Selecting by name seems to be the most straight forward way here to - // avoid forcing the symbol of the case class in order to list the accessors. - val maybeRenamedAccessorName = caseAccessorName(caseclazz.symbol, accessorName) - Ident(param) DOT maybeRenamedAccessorName - case Some(sym) => - // But, that gives a misleading error message in neg/t1422.scala, where a case - // class has an illegal private[this] parameter. We can detect this by checking - // the modifiers on the param accessors. - // - // We just generate a call to that param accessor here, which gives us an inaccessible - // symbol error, as before. - Ident(param) DOT sym + // Selecting by name seems to be the most straight forward way here to + // avoid forcing the symbol of the case class in order to list the accessors. + def selectByName = Ident(param) DOT caseAccessorName(caseclazz.symbol, selector.name) + // But, that gives a misleading error message in neg/t1422.scala, where a case + // class has an illegal private[this] parameter. We can detect this by checking + // the modifiers on the param accessors. + // We just generate a call to that param accessor here, which gives us an inaccessible + // symbol error, as before. + def localAccessor = caseclazz.impl.body find { + case t @ ValOrDefDef(mods, selector.name, _, _) => mods.isPrivateLocal + case _ => false } + localAccessor.fold(selectByName)(Ident(param) DOT _.symbol) } // Working with trees, rather than symbols, to avoid cycles like SI-5082 @@ -153,8 +141,6 @@ trait Unapplies extends ast.TreeDSL gen.mkTemplate(parents, emptyValDef, NoMods, Nil, body, cdef.impl.pos.focus)) } - private val caseMods = Modifiers(SYNTHETIC | CASE) - /** The apply method corresponding to a case class */ def factoryMeth(mods: Modifiers, name: TermName, cdef: ClassDef): DefDef = { diff --git a/src/reflect/scala/reflect/internal/Trees.scala b/src/reflect/scala/reflect/internal/Trees.scala index 02bee5e369..2d01840602 100644 --- a/src/reflect/scala/reflect/internal/Trees.scala +++ b/src/reflect/scala/reflect/internal/Trees.scala @@ -309,6 +309,14 @@ trait Trees extends api.Trees { self: SymbolTable => def rhs: Tree } + object ValOrDefDef { + def unapply(tree: Tree): Option[(Modifiers, TermName, Tree, Tree)] = tree match { + case ValDef(mods, name, tpt, rhs) => Some((mods, name, tpt, rhs)) + case DefDef(mods, name, _, _, tpt, rhs) => Some((mods, name, tpt, rhs)) + case _ => None + } + } + case class ValDef(mods: Modifiers, name: TermName, tpt: Tree, rhs: Tree) extends ValOrDefDef with ValDefApi object ValDef extends ValDefExtractor -- cgit v1.2.3 From 9672a80d08148e9f3223077bf96aaa4ddf17c599 Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Thu, 15 Aug 2013 15:02:18 -0700 Subject: Add checkability condition. All parents of an intersection type must be checkable for the type to be checkable. --- src/compiler/scala/tools/nsc/typechecker/Checkable.scala | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/typechecker/Checkable.scala b/src/compiler/scala/tools/nsc/typechecker/Checkable.scala index 31a31df764..67c5666f66 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Checkable.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Checkable.scala @@ -246,8 +246,8 @@ trait Checkable { uncheckedOk(P0) || (P0.widen match { case TypeRef(_, NothingClass | NullClass | AnyValClass, _) => false case RefinedType(_, decls) if !decls.isEmpty => false - case p => - new CheckabilityChecker(AnyTpe, p) isCheckable + case RefinedType(parents, _) => parents forall isCheckable + case p => new CheckabilityChecker(AnyTpe, p) isCheckable }) ) @@ -273,6 +273,8 @@ trait Checkable { // Matching on types like case _: AnyRef { def bippy: Int } => doesn't work -- yet. case RefinedType(_, decls) if !decls.isEmpty => getContext.unit.warning(tree.pos, s"a pattern match on a refinement type is unchecked") + case RefinedType(parents, _) => + parents foreach (p => checkCheckable(tree, p, X, inPattern, canRemedy)) case _ => val checker = new CheckabilityChecker(X, P) log(checker.summaryString) -- cgit v1.2.3 From 35122d6cda84bb2df69ca51c6b1b80e61693bf6f Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Thu, 15 Aug 2013 15:02:18 -0700 Subject: Minor improvement in pattern typer inference. This exploits the infrastructure developed for checking the checkability of type patterns to improve pattern type inference, which suffered from a similar deficit. There was a hack for SI-2486, the best I could manage at the time I wrote it; that is replaced with the principled approach. --- .../scala/tools/nsc/typechecker/Infer.scala | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/typechecker/Infer.scala b/src/compiler/scala/tools/nsc/typechecker/Infer.scala index 8ca0d82e93..2f86e23415 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Infer.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Infer.scala @@ -1227,7 +1227,6 @@ trait Infer extends Checkable { val tpparams = freeTypeParamsOfTerms(pattp) def ptMatchesPattp = pt matchesPattern pattp.widen - def pattpMatchesPt = pattp matchesPattern pt /* If we can absolutely rule out a match we can fail early. * This is the case if the scrutinee has no unresolved type arguments @@ -1237,9 +1236,15 @@ trait Infer extends Checkable { IncompatibleScrutineeTypeError(tree0, pattp, pt) return ErrorType } + // This performs the "reverse" propagation of type information already used + // in pattern matcher checkability testing. See pos/t2486.scala for sample + // code which would not compile without such propagation. + def propagated = propagateKnownTypes(pt, pattp.widen.typeSymbol) - checkCheckable(tree0, pattp, pt, inPattern = true, canRemedy) + checkCheckable(tree0, pattp, pt0, inPattern = true, canRemedy) if (pattp <:< pt) () + else if (pattp <:< propagated) + log(s"!($pattp <:< $pt), but after propagateKnownTypes we find ($pattp <:< $propagated) - pattern inference improved") else { debuglog("free type params (1) = " + tpparams) @@ -1256,9 +1261,7 @@ trait Infer extends Checkable { val ptvars = ptparams map freshVar val pt1 = pt.instantiateTypeParams(ptparams, ptvars) - // See ticket #2486 for an example of code which would incorrectly - // fail if we didn't allow for pattpMatchesPt. - if (isPopulated(tp, pt1) && isInstantiatable(tvars ++ ptvars) || pattpMatchesPt) + if (isPopulated(tp, pt1) && isInstantiatable(tvars ++ ptvars)) ptvars foreach instantiateTypeVar else { PatternTypeIncompatibleWithPtError1(tree0, pattp, pt) @@ -1311,10 +1314,10 @@ trait Infer extends Checkable { // properly, we can avoid it by ignoring type parameters which // have type constructors amongst their bounds. See SI-4070. def isFreeTypeParamOfTerm(sym: Symbol) = ( - sym.isAbstractType - && sym.owner.isTerm - && !sym.info.bounds.exists(_.typeParams.nonEmpty) - ) + sym.isAbstractType + && sym.owner.isTerm + && !sym.info.bounds.exists(_.typeParams.nonEmpty) + ) // Intentionally *not* using `Type#typeSymbol` here, which would normalize `tp` // and collect symbols from the result type of any resulting `PolyType`s, which -- cgit v1.2.3 From e76507f8793cd025b56ba2a0c3b0cc112bcc8aea Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Thu, 15 Aug 2013 15:02:19 -0700 Subject: An Unapplied extractor. This makes it a lot less error prone and redundant to find the part you need when unwrapping an UnApply tree. --- src/compiler/scala/reflect/reify/phases/Reshape.scala | 18 +++--------------- src/reflect/scala/reflect/internal/TreeInfo.scala | 11 +++++++++++ 2 files changed, 14 insertions(+), 15 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/reflect/reify/phases/Reshape.scala b/src/compiler/scala/reflect/reify/phases/Reshape.scala index b6f27f71ce..7610df67dc 100644 --- a/src/compiler/scala/reflect/reify/phases/Reshape.scala +++ b/src/compiler/scala/reflect/reify/phases/Reshape.scala @@ -8,6 +8,7 @@ trait Reshape { import global._ import definitions._ + import treeInfo.Unapplied /** * Rolls back certain changes that were introduced during typechecking of the reifee. @@ -65,22 +66,9 @@ trait Reshape { case block @ Block(stats, expr) => val stats1 = reshapeLazyVals(trimSyntheticCaseClassCompanions(stats)) Block(stats1, expr).copyAttrs(block) - case unapply @ UnApply(fun, args) => - def extractExtractor(tree: Tree): Tree = { - val Apply(fun, args) = tree - args match { - case List(Ident(special)) if special == nme.SELECTOR_DUMMY => - val Select(extractor, flavor) = fun - assert(flavor == nme.unapply || flavor == nme.unapplySeq) - extractor - case _ => - extractExtractor(fun) - } - } - + case unapply @ UnApply(Unapplied(Select(fun, nme.unapply | nme.unapplySeq)), args) => if (reifyDebug) println("unapplying unapply: " + tree) - val fun1 = extractExtractor(fun) - Apply(fun1, args).copyAttrs(unapply) + Apply(fun, args).copyAttrs(unapply) case _ => tree } diff --git a/src/reflect/scala/reflect/internal/TreeInfo.scala b/src/reflect/scala/reflect/internal/TreeInfo.scala index d01f1ce681..34fe0afb1a 100644 --- a/src/reflect/scala/reflect/internal/TreeInfo.scala +++ b/src/reflect/scala/reflect/internal/TreeInfo.scala @@ -773,6 +773,17 @@ abstract class TreeInfo { unapply(dissectApplied(tree)) } + /** Locates the synthetic Apply node corresponding to an extractor's call to + * unapply (unwrapping nested Applies) and returns the fun part of that Apply. + */ + object Unapplied { + def unapply(tree: Tree): Option[Tree] = tree match { + case Apply(fun, Ident(nme.SELECTOR_DUMMY) :: Nil) => Some(fun) + case Apply(fun, _) => unapply(fun) + case _ => None + } + } + /** Is this file the body of a compilation unit which should not * have Predef imported? This is the case iff the first import in the * unit explicitly refers to Predef. -- cgit v1.2.3 From 0be0b99414ae54f1f4fbc7c7bc0e36e3ada20289 Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Sat, 17 Aug 2013 07:35:37 -0700 Subject: Remedied glaring omission in error output. Catching an assert and providing beautifully formatted contextual information is a questionable service if you forget to provide the error message from the assert. --- src/compiler/scala/tools/nsc/Global.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index bfc4aefe06..9123733d49 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -1112,7 +1112,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter) "symbol owners" -> ownerChainString(sym), "call site" -> (site.fullLocationString + " in " + site.enclosingPackage) ) - ("\n" + info1) :: info2 :: context_s :: Nil mkString "\n\n" + ("\n " + errorMessage + "\n" + info1) :: info2 :: context_s :: Nil mkString "\n\n" } catch { case _: Exception | _: TypeError => errorMessage } } -- cgit v1.2.3 From d351a1f79b5bd5f3be7d5e292f046495a9a0e629 Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Sat, 17 Aug 2013 07:39:06 -0700 Subject: Segreated CPS hacks in pattern matcher. --- .../nsc/transform/patmat/MatchTranslation.scala | 55 +++++++++++++--------- 1 file changed, 34 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala index fcee142932..2de9dac54f 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala @@ -10,9 +10,40 @@ import scala.language.postfixOps import scala.collection.mutable import scala.reflect.internal.util.Statistics + +/** Segregating this super hacky code. */ +trait CpsPatternHacks { + self: PatternMatching => + + import global._ + + // duplicated from CPSUtils (avoid dependency from compiler -> cps plugin...) + private object CpsSymbols { + val MarkerCPSAdaptPlus = rootMirror.getClassIfDefined("scala.util.continuations.cpsPlus") + val MarkerCPSAdaptMinus = rootMirror.getClassIfDefined("scala.util.continuations.cpsMinus") + val MarkerCPSSynth = rootMirror.getClassIfDefined("scala.util.continuations.cpsSynth") + val MarkerCPSTypes = rootMirror.getClassIfDefined("scala.util.continuations.cpsParam") + val stripTriggerCPSAnns = Set[Symbol](MarkerCPSSynth, MarkerCPSAdaptMinus, MarkerCPSAdaptPlus) + val strippedCPSAnns = stripTriggerCPSAnns + MarkerCPSTypes + + // when one of the internal cps-type-state annotations is present, strip all CPS annotations + // a cps-type-state-annotated type makes no sense as an expected type (matchX.tpe is used as pt in translateMatch) + // (only test availability of MarkerCPSAdaptPlus assuming they are either all available or none of them are) + def removeCPSFromPt(pt: Type): Type = ( + if (MarkerCPSAdaptPlus.exists && (stripTriggerCPSAnns exists pt.hasAnnotation)) + pt filterAnnotations (ann => !(strippedCPSAnns exists ann.matches)) + else + pt + ) + } + def removeCPSFromPt(pt: Type): Type = CpsSymbols removeCPSFromPt pt +} + /** Translate typed Trees that represent pattern matches into the patternmatching IR, defined by TreeMakers. */ -trait MatchTranslation { self: PatternMatching => +trait MatchTranslation extends CpsPatternHacks { + self: PatternMatching => + import PatternMatchingStats._ import global._ import definitions._ @@ -84,15 +115,6 @@ trait MatchTranslation { self: PatternMatching => } } - // duplicated from CPSUtils (avoid dependency from compiler -> cps plugin...) - private lazy val MarkerCPSAdaptPlus = rootMirror.getClassIfDefined("scala.util.continuations.cpsPlus") - private lazy val MarkerCPSAdaptMinus = rootMirror.getClassIfDefined("scala.util.continuations.cpsMinus") - private lazy val MarkerCPSSynth = rootMirror.getClassIfDefined("scala.util.continuations.cpsSynth") - private lazy val stripTriggerCPSAnns = List(MarkerCPSSynth, MarkerCPSAdaptMinus, MarkerCPSAdaptPlus) - private lazy val MarkerCPSTypes = rootMirror.getClassIfDefined("scala.util.continuations.cpsParam") - private lazy val strippedCPSAnns = MarkerCPSTypes :: stripTriggerCPSAnns - private def removeCPSAdaptAnnotations(tp: Type) = tp filterAnnotations (ann => !(strippedCPSAnns exists (ann matches _))) - /** Implement a pattern match by turning its cases (including the implicit failure case) * into the corresponding (monadic) extractors, and combining them with the `orElse` combinator. * @@ -127,18 +149,11 @@ trait MatchTranslation { self: PatternMatching => val selectorTp = repeatedToSeq(elimAnonymousClass(selector.tpe.widen.withoutAnnotations)) - val origPt = match_.tpe // when one of the internal cps-type-state annotations is present, strip all CPS annotations - // a cps-type-state-annotated type makes no sense as an expected type (matchX.tpe is used as pt in translateMatch) - // (only test availability of MarkerCPSAdaptPlus assuming they are either all available or none of them are) - val ptUnCPS = - if (MarkerCPSAdaptPlus != NoSymbol && (stripTriggerCPSAnns exists origPt.hasAnnotation)) - removeCPSAdaptAnnotations(origPt) - else origPt - + val origPt = removeCPSFromPt(match_.tpe) // relevant test cases: pos/existentials-harmful.scala, pos/gadt-gilles.scala, pos/t2683.scala, pos/virtpatmat_exist4.scala // pt is the skolemized version - val pt = repeatedToSeq(ptUnCPS) + val pt = repeatedToSeq(origPt) // val packedPt = repeatedToSeq(typer.packedType(match_, context.owner)) val selectorSym = freshSym(selector.pos, pureType(selectorTp)) setFlag treeInfo.SYNTH_CASE_FLAGS @@ -191,8 +206,6 @@ trait MatchTranslation { self: PatternMatching => typer.typedCases(catches, ThrowableTpe, WildcardType) } - - /** The translation of `pat if guard => body` has two aspects: * 1) the substitution due to the variables bound by patterns * 2) the combination of the extractor calls using `flatMap`. -- cgit v1.2.3 From 0cf47bdb5b1b57589883544933af56a4af848492 Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Sat, 17 Aug 2013 07:44:50 -0700 Subject: Simplified the MaybeBoundTyped extractor a bit. --- .../nsc/transform/patmat/MatchTranslation.scala | 20 ++++++++++++++------ test/files/run/patmat-bind-typed.check | 1 + test/files/run/patmat-bind-typed.scala | 8 ++++++++ 3 files changed, 23 insertions(+), 6 deletions(-) create mode 100644 test/files/run/patmat-bind-typed.check create mode 100644 test/files/run/patmat-bind-typed.scala (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala index 2de9dac54f..41bdfe1076 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala @@ -293,18 +293,26 @@ trait MatchTranslation extends CpsPatternHacks { object MaybeBoundTyped { + object NonNullTyped { + // the Ident subpattern can be ignored, subpatBinder or patBinder tell us all we need to know about it + def unapply(tree: Typed): Option[Type] = tree match { + case Typed(Ident(_), _) if tree.tpe != null => Some((tree.tpe)) + case _ => None + } + } + /** Decompose the pattern in `tree`, of shape C(p_1, ..., p_N), into a list of N symbols, and a list of its N sub-trees * The list of N symbols contains symbols for every bound name as well as the un-named sub-patterns (fresh symbols are generated here for these). * The returned type is the one inferred by inferTypedPattern (`owntype`) * - * @arg patBinder symbol used to refer to the result of the previous pattern's extractor (will later be replaced by the outer tree with the correct tree to refer to that patterns result) + * @arg patBinder symbol used to refer to the result of the previous pattern's extractor + * (will later be replaced by the outer tree with the correct tree to refer to that patterns result) */ def unapply(tree: Tree): Option[(Symbol, Type)] = tree match { - // the Ident subpattern can be ignored, subpatBinder or patBinder tell us all we need to know about it - case Bound(subpatBinder, typed@Typed(Ident(_), tpt)) if typed.tpe ne null => Some((subpatBinder, typed.tpe)) - case Bind(_, typed@Typed(Ident(_), tpt)) if typed.tpe ne null => Some((patBinder, typed.tpe)) - case Typed(Ident(_), tpt) if tree.tpe ne null => Some((patBinder, tree.tpe)) - case _ => None + case Bound(binder, MaybeBoundTyped(_, tpe)) => Some((binder, tpe)) // possible nested bindings - use the outermost + case NonNullTyped(tpe) => Some((patBinder, tpe)) // patBinder used if no local bindings + case Bind(_, expr) => unapply(expr) + case _ => None } } diff --git a/test/files/run/patmat-bind-typed.check b/test/files/run/patmat-bind-typed.check new file mode 100644 index 0000000000..8baef1b4ab --- /dev/null +++ b/test/files/run/patmat-bind-typed.check @@ -0,0 +1 @@ +abc diff --git a/test/files/run/patmat-bind-typed.scala b/test/files/run/patmat-bind-typed.scala new file mode 100644 index 0000000000..10de921c51 --- /dev/null +++ b/test/files/run/patmat-bind-typed.scala @@ -0,0 +1,8 @@ +object Test { + def f(xs: List[Any]) = for (key @ (dummy: String) <- xs) yield key + + def main(args: Array[String]): Unit = { + f("abc" :: Nil) foreach println + f(5 :: Nil) foreach println + } +} -- cgit v1.2.3 From b084cab0aa474e9f650c391fa64464734c276597 Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Sat, 17 Aug 2013 07:53:42 -0700 Subject: Deduplicate mkZero in pattern matcher. --- .../tools/nsc/transform/patmat/MatchCodeGen.scala | 29 ++++++++-------------- 1 file changed, 10 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchCodeGen.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchCodeGen.scala index 1e4c56529c..8eab776f3d 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/MatchCodeGen.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchCodeGen.scala @@ -62,30 +62,21 @@ trait MatchCodeGen extends Interface { def codegen: AbsCodegen abstract class CommonCodegen extends AbsCodegen { import CODE._ - def fun(arg: Symbol, body: Tree): Tree = Function(List(ValDef(arg)), body) - def tupleSel(binder: Symbol)(i: Int): Tree = (REF(binder) DOT nme.productAccessorName(i)) // make tree that accesses the i'th component of the tuple referenced by binder - def index(tgt: Tree)(i: Int): Tree = tgt APPLY (LIT(i)) - def drop(tgt: Tree)(n: Int): Tree = (tgt DOT vpmName.drop) (LIT(n)) - def _equals(checker: Tree, binder: Symbol): Tree = checker MEMBER_== REF(binder) // NOTE: checker must be the target of the ==, that's the patmat semantics for ya + def fun(arg: Symbol, body: Tree): Tree = Function(List(ValDef(arg)), body) + def tupleSel(binder: Symbol)(i: Int): Tree = (REF(binder) DOT nme.productAccessorName(i)) // make tree that accesses the i'th component of the tuple referenced by binder + def index(tgt: Tree)(i: Int): Tree = tgt APPLY (LIT(i)) + def drop(tgt: Tree)(n: Int): Tree = gen.mkMethodCall(traversableDropMethod, tgt :: LIT(n) :: Nil) + + // NOTE: checker must be the target of the ==, that's the patmat semantics for ya + def _equals(checker: Tree, binder: Symbol): Tree = checker MEMBER_== REF(binder) // the force is needed mainly to deal with the GADT typing hack (we can't detect it otherwise as tp nor pt need contain an abstract type, we're just casting wildly) def _asInstanceOf(b: Symbol, tp: Type): Tree = if (b.info <:< tp) REF(b) else gen.mkCastPreservingAnnotations(REF(b), tp) def _isInstanceOf(b: Symbol, tp: Type): Tree = gen.mkIsInstanceOf(REF(b), tp.withoutAnnotations, any = true, wrapInApply = false) - // duplicated out of frustration with cast generation - def mkZero(tp: Type): Tree = { - tp.typeSymbol match { - case UnitClass => Literal(Constant(())) - case BooleanClass => Literal(Constant(false)) - case FloatClass => Literal(Constant(0.0f)) - case DoubleClass => Literal(Constant(0.0d)) - case ByteClass => Literal(Constant(0.toByte)) - case ShortClass => Literal(Constant(0.toShort)) - case IntClass => Literal(Constant(0)) - case LongClass => Literal(Constant(0L)) - case CharClass => Literal(Constant(0.toChar)) - case _ => gen.mkAsInstanceOf(Literal(Constant(null)), tp, any = true, wrapInApply = false) // the magic incantation is true/false here - } + def mkZero(tp: Type): Tree = gen.mkConstantZero(tp) match { + case Constant(null) => gen.mkAsInstanceOf(Literal(Constant(null)), tp, any = true, wrapInApply = false) // the magic incantation is true/false here + case const => Literal(const) } } } -- cgit v1.2.3 From dc872cd1fda84d04dc4de7789a21517902322d68 Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Sat, 17 Aug 2013 07:56:01 -0700 Subject: Broke up typed1's giant pattern match. Another level of dispatch based on what trees on can expect to see during what mode. This should be beneficial for both performance (smaller methods, fewer type tests) and correctness (prevent trees from reaching inappropriate typing methods by construction rather than via ad hoc checks.) This work also revealed that UnApply trees never reach here, so I removed typedUnApply as dead code. --- .../scala/tools/nsc/typechecker/Typers.scala | 111 ++++++++++++--------- 1 file changed, 66 insertions(+), 45 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index d2ff47626d..dd92657de8 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -4993,13 +4993,6 @@ trait Typers extends Adaptations with Tags with TypersTracking { treeCopy.Star(tree, typed(tree.elem, mode, pt)) setType makeFullyDefined(pt) } - def typedUnApply(tree: UnApply) = { - val fun1 = typed(tree.fun) - val tpes = formalTypes(unapplyTypeList(tree.fun.pos, tree.fun.symbol, fun1.tpe, tree.args), tree.args.length) - val args1 = map2(tree.args, tpes)(typedPattern) - treeCopy.UnApply(tree, fun1, args1) setType pt - } - def issueTryWarnings(tree: Try): Try = { def checkForCatchAll(cdef: CaseDef) { def unbound(t: Tree) = t.symbol == null || t.symbol == NoSymbol @@ -5254,52 +5247,80 @@ trait Typers extends Adaptations with Tags with TypersTracking { typerWithLocalContext(context.makeNewScope(fun, fun.symbol))(_.typedFunction(fun, mode, pt)) } - // begin typed1 - //if (settings.debug.value && tree.isDef) log("typing definition of "+sym);//DEBUG - tree match { - case tree: Ident => typedIdentOrWildcard(tree) - case tree: Select => typedSelectOrSuperCall(tree) - case tree: Apply => typedApply(tree) + // Trees only allowed during pattern mode. + def typedInPatternMode(tree: Tree): Tree = tree match { + case tree: Alternative => typedAlternative(tree) + case tree: Star => typedStar(tree) + case _ => abort(s"unexpected tree in pattern mode: ${tree.getClass}\n$tree") + } + + def typedTypTree(tree: TypTree): Tree = tree match { case tree: TypeTree => typedTypeTree(tree) - case tree: Literal => typedLiteral(tree) - case tree: This => typedThis(tree) - case tree: ValDef => typedValDef(tree) - case tree: DefDef => defDefTyper(tree).typedDefDef(tree) - case tree: Block => typerWithLocalContext(context.makeNewScope(tree, context.owner))(_.typedBlock(tree, mode, pt)) - case tree: If => typedIf(tree) - case tree: TypeApply => typedTypeApply(tree) case tree: AppliedTypeTree => typedAppliedTypeTree(tree) - case tree: Bind => typedBind(tree) - case tree: Function => typedFunction(tree) - case tree: Match => typedVirtualizedMatch(tree) - case tree: New => typedNew(tree) - case tree: Assign => typedAssign(tree.lhs, tree.rhs) - case tree: AssignOrNamedArg => typedAssign(tree.lhs, tree.rhs) // called by NamesDefaults in silent typecheck - case tree: Super => typedSuper(tree) case tree: TypeBoundsTree => typedTypeBoundsTree(tree) - case tree: Typed => typedTyped(tree) - case tree: ClassDef => newTyper(context.makeNewScope(tree, sym)).typedClassDef(tree) - case tree: ModuleDef => newTyper(context.makeNewScope(tree, sym.moduleClass)).typedModuleDef(tree) - case tree: TypeDef => typedTypeDef(tree) - case tree: LabelDef => labelTyper(tree).typedLabelDef(tree) - case tree: PackageDef => typedPackageDef(tree) - case tree: DocDef => typedDocDef(tree, mode, pt) - case tree: Annotated => typedAnnotated(tree) case tree: SingletonTypeTree => typedSingletonTypeTree(tree) case tree: SelectFromTypeTree => typedSelectFromTypeTree(tree) case tree: CompoundTypeTree => typedCompoundTypeTree(tree) case tree: ExistentialTypeTree => typedExistentialTypeTree(tree) - case tree: Return => typedReturn(tree) - case tree: Try => typedTry(tree) - case tree: Throw => typedThrow(tree) - case tree: Alternative => typedAlternative(tree) - case tree: Star => typedStar(tree) - case tree: UnApply => typedUnApply(tree) - case tree: ArrayValue => typedArrayValue(tree) - case tree: ApplyDynamic => typedApplyDynamic(tree) - case tree: ReferenceToBoxed => typedReferenceToBoxed(tree) case tree: TypeTreeWithDeferredRefCheck => tree // TODO: retype the wrapped tree? TTWDRC would have to change to hold the wrapped tree (not a closure) - case _ => abort(s"unexpected tree: ${tree.getClass}\n$tree") + case _ => abort(s"unexpected type-representing tree: ${tree.getClass}\n$tree") + } + + def typedMemberDef(tree: MemberDef): Tree = tree match { + case tree: ValDef => typedValDef(tree) + case tree: DefDef => defDefTyper(tree).typedDefDef(tree) + case tree: ClassDef => newTyper(context.makeNewScope(tree, sym)).typedClassDef(tree) + case tree: ModuleDef => newTyper(context.makeNewScope(tree, sym.moduleClass)).typedModuleDef(tree) + case tree: TypeDef => typedTypeDef(tree) + case tree: PackageDef => typedPackageDef(tree) + case _ => abort(s"unexpected member def: ${tree.getClass}\n$tree") + } + + // Trees not allowed during pattern mode. + def typedOutsidePatternMode(tree: Tree): Tree = tree match { + case tree: Block => typerWithLocalContext(context.makeNewScope(tree, context.owner))(_.typedBlock(tree, mode, pt)) + case tree: If => typedIf(tree) + case tree: TypeApply => typedTypeApply(tree) + case tree: Function => typedFunction(tree) + case tree: Match => typedVirtualizedMatch(tree) + case tree: New => typedNew(tree) + case tree: Assign => typedAssign(tree.lhs, tree.rhs) + case tree: AssignOrNamedArg => typedAssign(tree.lhs, tree.rhs) // called by NamesDefaults in silent typecheck + case tree: Super => typedSuper(tree) + case tree: Annotated => typedAnnotated(tree) + case tree: Return => typedReturn(tree) + case tree: Try => typedTry(tree) + case tree: Throw => typedThrow(tree) + case tree: ArrayValue => typedArrayValue(tree) + case tree: ApplyDynamic => typedApplyDynamic(tree) + case tree: ReferenceToBoxed => typedReferenceToBoxed(tree) + case tree: LabelDef => labelTyper(tree).typedLabelDef(tree) + case tree: DocDef => typedDocDef(tree, mode, pt) + case _ => abort(s"unexpected tree: ${tree.getClass}\n$tree") + } + + // Trees allowed in or out of pattern mode. + def typedInAnyMode(tree: Tree): Tree = tree match { + case tree: Ident => typedIdentOrWildcard(tree) + case tree: Bind => typedBind(tree) + case tree: Apply => typedApply(tree) + case tree: Select => typedSelectOrSuperCall(tree) + case tree: Literal => typedLiteral(tree) + case tree: Typed => typedTyped(tree) + case tree: This => typedThis(tree) // SI-6104 + case tree: UnApply => abort(s"unexpected UnApply $tree") // turns out UnApply never reaches here + case _ => + if (mode.inPatternMode) + typedInPatternMode(tree) + else + typedOutsidePatternMode(tree) + } + + // begin typed1 + tree match { + case tree: TypTree => typedTypTree(tree) + case tree: MemberDef => typedMemberDef(tree) + case _ => typedInAnyMode(tree) } } -- cgit v1.2.3 From 3349d5a0b376b80df1816d4065b02cfb0c463906 Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Sat, 17 Aug 2013 08:15:04 -0700 Subject: Pulled pattern typing methods from Typers. To the extent possible this commit is purely the extraction of those methods into the newly created PatternTypers trait. The slicing and dicing of those methods will follow shortly. --- .../tools/nsc/typechecker/PatternTypers.scala | 319 +++++++++++++++++++++ .../scala/tools/nsc/typechecker/Typers.scala | 277 +----------------- 2 files changed, 322 insertions(+), 274 deletions(-) create mode 100644 src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala b/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala new file mode 100644 index 0000000000..f09c142aef --- /dev/null +++ b/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala @@ -0,0 +1,319 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2013 LAMP/EPFL + * @author Paul Phillips + */ + +package scala +package tools +package nsc +package typechecker + +import scala.collection.mutable +import symtab.Flags +import Mode._ + + /** + * + * A pattern match such as + * + * x match { case Foo(a, b) => ...} + * + * Might match an instance of any of the following definitions of Foo. + * Note the analogous treatment between case classes and unapplies. + * + * case class Foo(xs: Int*) + * case class Foo(a: Int, xs: Int*) + * case class Foo(a: Int, b: Int) + * case class Foo(a: Int, b: Int, xs: Int*) + * + * object Foo { def unapplySeq(x: Any): Option[Seq[Int]] } + * object Foo { def unapplySeq(x: Any): Option[(Int, Seq[Int])] } + * object Foo { def unapply(x: Any): Option[(Int, Int)] } + * object Foo { def unapplySeq(x: Any): Option[(Int, Int, Seq[Int])] } + */ + +trait PatternTypers { + self: Analyzer => + + import global._ + import definitions._ + + // when true: + // - we may virtualize matches (if -Xexperimental and there's a suitable __match in scope) + // - we synthesize PartialFunction implementations for `x => x match {...}` and `match {...}` when the expected type is PartialFunction + // this is disabled by: interactive compilation (we run it for scaladoc due to SI-5933) + protected def newPatternMatching = true // presently overridden in the presentation compiler + + trait PatternTyper { + self: Typer => + + import TyperErrorGen._ + import infer._ + + private def unit = context.unit + + /** Type trees in `args0` against corresponding expected type in `adapted0`. + * + * The mode in which each argument is typed is derived from `mode` and + * whether the arg was originally by-name or var-arg (need `formals0` for that) + * the default is by-val, of course. + * + * (docs reverse-engineered -- AM) + */ + def typedArgs(args0: List[Tree], mode: Mode, formals0: List[Type], adapted0: List[Type]): List[Tree] = { + def loop(args: List[Tree], formals: List[Type], adapted: List[Type]): List[Tree] = { + if (args.isEmpty || adapted.isEmpty) Nil + else { + // No formals left or * indicates varargs. + val isVarArgs = formals.isEmpty || formals.tail.isEmpty && isRepeatedParamType(formals.head) + val isByName = formals.nonEmpty && isByNameParamType(formals.head) + def typedMode = if (isByName) mode.onlySticky else mode.onlySticky | BYVALmode + def body = typedArg(args.head, mode, typedMode, adapted.head) + def arg1 = if (isVarArgs) context.withinStarPatterns(body) else body + + // formals may be empty, so don't call tail + arg1 :: loop(args.tail, formals drop 1, adapted.tail) + } + } + loop(args0, formals0, adapted0) + } + + /* + * To deal with the type slack between actual (run-time) types and statically known types, for each abstract type T, + * reflect its variance as a skolem that is upper-bounded by T (covariant position), or lower-bounded by T (contravariant). + * + * Consider the following example: + * + * class AbsWrapperCov[+A] + * case class Wrapper[B](x: Wrapped[B]) extends AbsWrapperCov[B] + * + * def unwrap[T](x: AbsWrapperCov[T]): Wrapped[T] = x match { + * case Wrapper(wrapped) => // Wrapper's type parameter must not be assumed to be equal to T, it's *upper-bounded* by it + * wrapped // : Wrapped[_ <: T] + * } + * + * this method should type check if and only if Wrapped is covariant in its type parameter + * + * when inferring Wrapper's type parameter B from x's type AbsWrapperCov[T], + * we must take into account that x's actual type is AbsWrapperCov[Tactual] forSome {type Tactual <: T} + * as AbsWrapperCov is covariant in A -- in other words, we must not assume we know T exactly, all we know is its upper bound + * + * since method application is the only way to generate this slack between run-time and compile-time types (TODO: right!?), + * we can simply replace skolems that represent method type parameters as seen from the method's body + * by other skolems that are (upper/lower)-bounded by that type-parameter skolem + * (depending on the variance position of the skolem in the statically assumed type of the scrutinee, pt) + * + * see test/files/../t5189*.scala + */ + def adaptConstrPattern(tree: Tree, pt: Type): Tree = { // (5) + def hasUnapplyMember(tp: Type) = reallyExists(unapplyMember(tp)) + val overloadedExtractorOfObject = tree.symbol filter (sym => hasUnapplyMember(sym.tpe)) + // if the tree's symbol's type does not define an extractor, maybe the tree's type does. + // this is the case when we encounter an arbitrary tree as the target of an unapply call + // (rather than something that looks like a constructor call.) (for now, this only happens + // due to wrapClassTagUnapply, but when we support parameterized extractors, it will become + // more common place) + val extractor = overloadedExtractorOfObject orElse unapplyMember(tree.tpe) + def convertToCaseConstructor(clazz: Symbol): TypeTree = { + // convert synthetic unapply of case class to case class constructor + val prefix = tree.tpe.prefix + val tree1 = TypeTree(clazz.primaryConstructor.tpe.asSeenFrom(prefix, clazz.owner)) + .setOriginal(tree) + + val skolems = new mutable.ListBuffer[TypeSymbol] + object variantToSkolem extends TypeMap(trackVariance = true) { + def apply(tp: Type) = mapOver(tp) match { + // !!! FIXME - skipping this when variance.isInvariant allows unsoundness, see SI-5189 + case TypeRef(NoPrefix, tpSym, Nil) if !variance.isInvariant && tpSym.isTypeParameterOrSkolem && tpSym.owner.isTerm => + // must initialize or tpSym.tpe might see random type params!! + // without this, we'll get very weird types inferred in test/scaladoc/run/SI-5933.scala + // TODO: why is that?? + tpSym.initialize + val bounds = if (variance.isPositive) TypeBounds.upper(tpSym.tpe) else TypeBounds.lower(tpSym.tpe) + // origin must be the type param so we can deskolemize + val skolem = context.owner.newGADTSkolem(unit.freshTypeName("?"+tpSym.name), tpSym, bounds) + // println("mapping "+ tpSym +" to "+ skolem + " : "+ bounds +" -- pt= "+ pt +" in "+ context.owner +" at "+ context.tree ) + skolems += skolem + skolem.tpe + case tp1 => tp1 + } + } + + // have to open up the existential and put the skolems in scope + // can't simply package up pt in an ExistentialType, because that takes us back to square one (List[_ <: T] == List[T] due to covariance) + val ptSafe = variantToSkolem(pt) // TODO: pt.skolemizeExistential(context.owner, tree) ? + val freeVars = skolems.toList + + // use "tree" for the context, not context.tree: don't make another CaseDef context, + // as instantiateTypeVar's bounds would end up there + val ctorContext = context.makeNewScope(tree, context.owner) + freeVars foreach ctorContext.scope.enter + newTyper(ctorContext).infer.inferConstructorInstance(tree1, clazz.typeParams, ptSafe) + + // simplify types without losing safety, + // so that we get rid of unnecessary type slack, and so that error messages don't unnecessarily refer to skolems + val extrapolate = new ExistentialExtrapolation(freeVars) extrapolate (_: Type) + val extrapolated = tree1.tpe match { + case MethodType(ctorArgs, res) => // ctorArgs are actually in a covariant position, since this is the type of the subpatterns of the pattern represented by this Apply node + ctorArgs foreach (p => p.info = extrapolate(p.info)) // no need to clone, this is OUR method type + copyMethodType(tree1.tpe, ctorArgs, extrapolate(res)) + case tp => tp + } + + // once the containing CaseDef has been type checked (see typedCase), + // tree1's remaining type-slack skolems will be deskolemized (to the method type parameter skolems) + tree1 setType extrapolated + } + + if (extractor != NoSymbol) { + // if we did some ad-hoc overloading resolution, update the tree's symbol + // do not update the symbol if the tree's symbol's type does not define an unapply member + // (e.g. since it's some method that returns an object with an unapply member) + if (overloadedExtractorOfObject != NoSymbol) + tree setSymbol overloadedExtractorOfObject + + tree.tpe match { + case OverloadedType(pre, alts) => tree setType overloadedType(pre, alts filter (alt => hasUnapplyMember(alt.tpe))) + case _ => + } + val unapply = unapplyMember(extractor.tpe) + val clazz = unapplyParameterType(unapply) + + if (unapply.isCase && clazz.isCase) { + convertToCaseConstructor(clazz) + } else { + tree + } + } else { + val clazz = tree.tpe.typeSymbol.linkedClassOfClass + if (clazz.isCase) + convertToCaseConstructor(clazz) + else + CaseClassConstructorError(tree) + } + } + + def doTypedUnapply(tree: Tree, fun0: Tree, fun: Tree, args: List[Tree], mode: Mode, pt: Type): Tree = { + def duplErrTree = setError(treeCopy.Apply(tree, fun0, args)) + def duplErrorTree(err: AbsTypeError) = { issue(err); duplErrTree } + + val otpe = fun.tpe + + if (args.length > MaxTupleArity) + return duplErrorTree(TooManyArgsPatternError(fun)) + + // + def freshArgType(tp: Type): (List[Symbol], Type) = tp match { + case MethodType(param :: _, _) => + (Nil, param.tpe) + case PolyType(tparams, restpe) => + createFromClonedSymbols(tparams, freshArgType(restpe)._2)((ps, t) => ((ps, t))) + // No longer used, see test case neg/t960.scala (#960 has nothing to do with it) + case OverloadedType(_, _) => + OverloadedUnapplyError(fun) + (Nil, ErrorType) + case _ => + UnapplyWithSingleArgError(fun) + (Nil, ErrorType) + } + + val unapp = unapplyMember(otpe) + val unappType = otpe.memberType(unapp) + val argDummy = context.owner.newValue(nme.SELECTOR_DUMMY, fun.pos, Flags.SYNTHETIC) setInfo pt + val arg = Ident(argDummy) setType pt + + val uncheckedTypeExtractor = + if (unappType.paramTypes.nonEmpty) + extractorForUncheckedType(tree.pos, unappType.paramTypes.head) + else None + + if (!isApplicableSafe(Nil, unappType, List(pt), WildcardType)) { + //Console.println(s"UNAPP: need to typetest, arg: ${arg.tpe} unappType: $unappType") + val (freeVars, unappFormal) = freshArgType(unappType.skolemizeExistential(context.owner, tree)) + val unapplyContext = context.makeNewScope(context.tree, context.owner) + freeVars foreach unapplyContext.scope.enter + + val typer1 = newTyper(unapplyContext) + val pattp = typer1.infer.inferTypedPattern(tree, unappFormal, arg.tpe, canRemedy = uncheckedTypeExtractor.nonEmpty) + + // turn any unresolved type variables in freevars into existential skolems + val skolems = freeVars map (fv => unapplyContext.owner.newExistentialSkolem(fv, fv)) + arg setType pattp.substSym(freeVars, skolems) + argDummy setInfo arg.tpe + } + + // clearing the type is necessary so that ref will be stabilized; see bug 881 + val fun1 = typedPos(fun.pos)(Apply(Select(fun.clearType(), unapp), List(arg))) + + if (fun1.tpe.isErroneous) duplErrTree + else { + val resTp = fun1.tpe.finalResultType.dealiasWiden + val nbSubPats = args.length + val (formals, formalsExpanded) = + extractorFormalTypes(fun0.pos, resTp, nbSubPats, fun1.symbol, treeInfo.effectivePatternArity(args)) + if (formals == null) duplErrorTree(WrongNumberOfArgsError(tree, fun)) + else { + val args1 = typedArgs(args, mode, formals, formalsExpanded) + val pt1 = ensureFullyDefined(pt) // SI-1048 + val itype = glb(List(pt1, arg.tpe)) + arg setType pt1 // restore type (arg is a dummy tree, just needs to pass typechecking) + val unapply = UnApply(fun1, args1) setPos tree.pos setType itype + + // if the type that the unapply method expects for its argument is uncheckable, wrap in classtag extractor + // skip if the unapply's type is not a method type with (at least, but really it should be exactly) one argument + // also skip if we already wrapped a classtag extractor (so we don't keep doing that forever) + if (uncheckedTypeExtractor.isEmpty || fun1.symbol.owner.isNonBottomSubClass(ClassTagClass)) unapply + else wrapClassTagUnapply(unapply, uncheckedTypeExtractor.get, unappType.paramTypes.head) + } + } + } + + def wrapClassTagUnapply(uncheckedPattern: Tree, classTagExtractor: Tree, pt: Type): Tree = { + // TODO: disable when in unchecked match + // we don't create a new Context for a Match, so find the CaseDef, then go out one level and navigate back to the match that has this case + // val thisCase = context.nextEnclosing(_.tree.isInstanceOf[CaseDef]) + // val unchecked = thisCase.outer.tree.collect{case Match(selector, cases) if cases contains thisCase => selector} match { + // case List(Typed(_, tpt)) if tpt.tpe hasAnnotation UncheckedClass => true + // case t => println("outer tree: "+ (t, thisCase, thisCase.outer.tree)); false + // } + // println("wrapClassTagUnapply"+ (!isPastTyper && infer.containsUnchecked(pt), pt, uncheckedPattern)) + // println("wrapClassTagUnapply: "+ extractor) + // println(util.Position.formatMessage(uncheckedPattern.pos, "made unchecked type test into a checked one", true)) + + val args = List(uncheckedPattern) + val app = atPos(uncheckedPattern.pos)(Apply(classTagExtractor, args)) + // must call doTypedUnapply directly, as otherwise we get undesirable rewrites + // and re-typechecks of the target of the unapply call in PATTERNmode, + // this breaks down when the classTagExtractor (which defineds the unapply member) is not a simple reference to an object, + // but an arbitrary tree as is the case here + doTypedUnapply(app, classTagExtractor, classTagExtractor, args, PATTERNmode, pt) + } + + // if there's a ClassTag that allows us to turn the unchecked type test for `pt` into a checked type test + // return the corresponding extractor (an instance of ClassTag[`pt`]) + def extractorForUncheckedType(pos: Position, pt: Type): Option[Tree] = if (isPastTyper) None else { + // only look at top-level type, can't (reliably) do anything about unchecked type args (in general) + // but at least make a proper type before passing it elsewhere + val pt1 = pt.dealiasWiden match { + case tr @ TypeRef(pre, sym, args) if args.nonEmpty => copyTypeRef(tr, pre, sym, sym.typeParams map (_.tpeHK)) // replace actual type args with dummies + case pt1 => pt1 + } + pt1 match { + // if at least one of the types in an intersection is checkable, use the checkable ones + // this avoids problems as in run/matchonseq.scala, where the expected type is `Coll with scala.collection.SeqLike` + // Coll is an abstract type, but SeqLike of course is not + case RefinedType(ps, _) if ps.length > 1 && (ps exists infer.isCheckable) => + None + + case ptCheckable if infer isUncheckable ptCheckable => + val classTagExtractor = resolveClassTag(pos, ptCheckable) + + if (classTagExtractor != EmptyTree && unapplyMember(classTagExtractor.tpe) != NoSymbol) + Some(classTagExtractor) + else None + + case _ => None + } + } + } +} \ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index dd92657de8..522ad97036 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -26,7 +26,7 @@ import Mode._ * @author Martin Odersky * @version 1.0 */ -trait Typers extends Adaptations with Tags with TypersTracking { +trait Typers extends Adaptations with Tags with TypersTracking with PatternTypers { self: Analyzer => import global._ @@ -90,13 +90,7 @@ trait Typers extends Adaptations with Tags with TypersTracking { private final val InterpolatorCodeRegex = """\$\{.*?\}""".r private final val InterpolatorIdentRegex = """\$\w+""".r - // when true: - // - we may virtualize matches (if -Xexperimental and there's a suitable __match in scope) - // - we synthesize PartialFunction implementations for `x => x match {...}` and `match {...}` when the expected type is PartialFunction - // this is disabled by: interactive compilation (we run it for scaladoc due to SI-5933) - protected def newPatternMatching = true // presently overridden in the presentation compiler - - abstract class Typer(context0: Context) extends TyperDiagnostics with Adaptation with Tag with TyperContextErrors { + abstract class Typer(context0: Context) extends TyperDiagnostics with Adaptation with Tag with TyperContextErrors with PatternTyper { import context0.unit import typeDebug.{ ptTree, ptBlock, ptLine, inGreen, inRed } import TyperErrorGen._ @@ -911,122 +905,6 @@ trait Typers extends Adaptations with Tags with TypersTracking { case _ => TypeTree(tree.tpe) setOriginal tree } } - - /* - * To deal with the type slack between actual (run-time) types and statically known types, for each abstract type T, - * reflect its variance as a skolem that is upper-bounded by T (covariant position), or lower-bounded by T (contravariant). - * - * Consider the following example: - * - * class AbsWrapperCov[+A] - * case class Wrapper[B](x: Wrapped[B]) extends AbsWrapperCov[B] - * - * def unwrap[T](x: AbsWrapperCov[T]): Wrapped[T] = x match { - * case Wrapper(wrapped) => // Wrapper's type parameter must not be assumed to be equal to T, it's *upper-bounded* by it - * wrapped // : Wrapped[_ <: T] - * } - * - * this method should type check if and only if Wrapped is covariant in its type parameter - * - * when inferring Wrapper's type parameter B from x's type AbsWrapperCov[T], - * we must take into account that x's actual type is AbsWrapperCov[Tactual] forSome {type Tactual <: T} - * as AbsWrapperCov is covariant in A -- in other words, we must not assume we know T exactly, all we know is its upper bound - * - * since method application is the only way to generate this slack between run-time and compile-time types (TODO: right!?), - * we can simply replace skolems that represent method type parameters as seen from the method's body - * by other skolems that are (upper/lower)-bounded by that type-parameter skolem - * (depending on the variance position of the skolem in the statically assumed type of the scrutinee, pt) - * - * see test/files/../t5189*.scala - */ - def adaptConstrPattern(): Tree = { // (5) - def hasUnapplyMember(tp: Type) = reallyExists(unapplyMember(tp)) - val overloadedExtractorOfObject = tree.symbol filter (sym => hasUnapplyMember(sym.tpe)) - // if the tree's symbol's type does not define an extractor, maybe the tree's type does. - // this is the case when we encounter an arbitrary tree as the target of an unapply call - // (rather than something that looks like a constructor call.) (for now, this only happens - // due to wrapClassTagUnapply, but when we support parameterized extractors, it will become - // more common place) - val extractor = overloadedExtractorOfObject orElse unapplyMember(tree.tpe) - def convertToCaseConstructor(clazz: Symbol): TypeTree = { - // convert synthetic unapply of case class to case class constructor - val prefix = tree.tpe.prefix - val tree1 = TypeTree(clazz.primaryConstructor.tpe.asSeenFrom(prefix, clazz.owner)) - .setOriginal(tree) - - val skolems = new mutable.ListBuffer[TypeSymbol] - object variantToSkolem extends TypeMap(trackVariance = true) { - def apply(tp: Type) = mapOver(tp) match { - // !!! FIXME - skipping this when variance.isInvariant allows unsoundness, see SI-5189 - case TypeRef(NoPrefix, tpSym, Nil) if !variance.isInvariant && tpSym.isTypeParameterOrSkolem && tpSym.owner.isTerm => - // must initialize or tpSym.tpe might see random type params!! - // without this, we'll get very weird types inferred in test/scaladoc/run/SI-5933.scala - // TODO: why is that?? - tpSym.initialize - val bounds = if (variance.isPositive) TypeBounds.upper(tpSym.tpe) else TypeBounds.lower(tpSym.tpe) - // origin must be the type param so we can deskolemize - val skolem = context.owner.newGADTSkolem(unit.freshTypeName("?"+tpSym.name), tpSym, bounds) - // println("mapping "+ tpSym +" to "+ skolem + " : "+ bounds +" -- pt= "+ pt +" in "+ context.owner +" at "+ context.tree ) - skolems += skolem - skolem.tpe - case tp1 => tp1 - } - } - - // have to open up the existential and put the skolems in scope - // can't simply package up pt in an ExistentialType, because that takes us back to square one (List[_ <: T] == List[T] due to covariance) - val ptSafe = variantToSkolem(pt) // TODO: pt.skolemizeExistential(context.owner, tree) ? - val freeVars = skolems.toList - - // use "tree" for the context, not context.tree: don't make another CaseDef context, - // as instantiateTypeVar's bounds would end up there - val ctorContext = context.makeNewScope(tree, context.owner) - freeVars foreach ctorContext.scope.enter - newTyper(ctorContext).infer.inferConstructorInstance(tree1, clazz.typeParams, ptSafe) - - // simplify types without losing safety, - // so that we get rid of unnecessary type slack, and so that error messages don't unnecessarily refer to skolems - val extrapolate = new ExistentialExtrapolation(freeVars) extrapolate (_: Type) - val extrapolated = tree1.tpe match { - case MethodType(ctorArgs, res) => // ctorArgs are actually in a covariant position, since this is the type of the subpatterns of the pattern represented by this Apply node - ctorArgs foreach (p => p.info = extrapolate(p.info)) // no need to clone, this is OUR method type - copyMethodType(tree1.tpe, ctorArgs, extrapolate(res)) - case tp => tp - } - - // once the containing CaseDef has been type checked (see typedCase), - // tree1's remaining type-slack skolems will be deskolemized (to the method type parameter skolems) - tree1 setType extrapolated - } - - if (extractor != NoSymbol) { - // if we did some ad-hoc overloading resolution, update the tree's symbol - // do not update the symbol if the tree's symbol's type does not define an unapply member - // (e.g. since it's some method that returns an object with an unapply member) - if (overloadedExtractorOfObject != NoSymbol) - tree setSymbol overloadedExtractorOfObject - - tree.tpe match { - case OverloadedType(pre, alts) => tree setType overloadedType(pre, alts filter (alt => hasUnapplyMember(alt.tpe))) - case _ => - } - val unapply = unapplyMember(extractor.tpe) - val clazz = unapplyParameterType(unapply) - - if (unapply.isCase && clazz.isCase) { - convertToCaseConstructor(clazz) - } else { - tree - } - } else { - val clazz = tree.tpe.typeSymbol.linkedClassOfClass - if (clazz.isCase) - convertToCaseConstructor(clazz) - else - CaseClassConstructorError(tree) - } - } - def insertApply(): Tree = { assert(!context.inTypeConstructorAllowed, mode) //@M val adapted = adaptToName(tree, nme.apply) @@ -1213,7 +1091,7 @@ trait Typers extends Adaptations with Tags with TypersTracking { else if (mode.typingExprNotFun && treeInfo.isMacroApplication(tree)) macroExpandApply(this, tree, mode, pt) else if (mode.typingConstructorPattern) - adaptConstrPattern() + adaptConstrPattern(tree, pt) else if (shouldInsertApply(tree)) insertApply() else if (hasUndetsInMonoMode) { // (9) @@ -3026,32 +2904,6 @@ trait Typers extends Adaptations with Tags with TypersTracking { def typedArgs(args: List[Tree], mode: Mode) = args mapConserve (arg => typedArg(arg, mode, NOmode, WildcardType)) - /** Type trees in `args0` against corresponding expected type in `adapted0`. - * - * The mode in which each argument is typed is derived from `mode` and - * whether the arg was originally by-name or var-arg (need `formals0` for that) - * the default is by-val, of course. - * - * (docs reverse-engineered -- AM) - */ - def typedArgs(args0: List[Tree], mode: Mode, formals0: List[Type], adapted0: List[Type]): List[Tree] = { - def loop(args: List[Tree], formals: List[Type], adapted: List[Type]): List[Tree] = { - if (args.isEmpty || adapted.isEmpty) Nil - else { - // No formals left or * indicates varargs. - val isVarArgs = formals.isEmpty || formals.tail.isEmpty && isRepeatedParamType(formals.head) - val isByName = formals.nonEmpty && isByNameParamType(formals.head) - def typedMode = if (isByName) mode.onlySticky else mode.onlySticky | BYVALmode - def body = typedArg(args.head, mode, typedMode, adapted.head) - def arg1 = if (isVarArgs) context.withinStarPatterns(body) else body - - // formals may be empty, so don't call tail - arg1 :: loop(args.tail, formals drop 1, adapted.tail) - } - } - loop(args0, formals0, adapted0) - } - /** Does function need to be instantiated, because a missing parameter * in an argument closure overlaps with an uninstantiated formal? */ @@ -3387,129 +3239,6 @@ trait Typers extends Adaptations with Tags with TypersTracking { } } - def doTypedUnapply(tree: Tree, fun0: Tree, fun: Tree, args: List[Tree], mode: Mode, pt: Type): Tree = { - def duplErrTree = setError(treeCopy.Apply(tree, fun0, args)) - def duplErrorTree(err: AbsTypeError) = { issue(err); duplErrTree } - - val otpe = fun.tpe - - if (args.length > MaxTupleArity) - return duplErrorTree(TooManyArgsPatternError(fun)) - - // - def freshArgType(tp: Type): (List[Symbol], Type) = tp match { - case MethodType(param :: _, _) => - (Nil, param.tpe) - case PolyType(tparams, restpe) => - createFromClonedSymbols(tparams, freshArgType(restpe)._2)((ps, t) => ((ps, t))) - // No longer used, see test case neg/t960.scala (#960 has nothing to do with it) - case OverloadedType(_, _) => - OverloadedUnapplyError(fun) - (Nil, ErrorType) - case _ => - UnapplyWithSingleArgError(fun) - (Nil, ErrorType) - } - - val unapp = unapplyMember(otpe) - val unappType = otpe.memberType(unapp) - val argDummy = context.owner.newValue(nme.SELECTOR_DUMMY, fun.pos, SYNTHETIC) setInfo pt - val arg = Ident(argDummy) setType pt - - val uncheckedTypeExtractor = - if (unappType.paramTypes.nonEmpty) - extractorForUncheckedType(tree.pos, unappType.paramTypes.head) - else None - - if (!isApplicableSafe(Nil, unappType, List(pt), WildcardType)) { - //Console.println(s"UNAPP: need to typetest, arg: ${arg.tpe} unappType: $unappType") - val (freeVars, unappFormal) = freshArgType(unappType.skolemizeExistential(context.owner, tree)) - val unapplyContext = context.makeNewScope(context.tree, context.owner) - freeVars foreach unapplyContext.scope.enter - - val typer1 = newTyper(unapplyContext) - val pattp = typer1.infer.inferTypedPattern(tree, unappFormal, arg.tpe, canRemedy = uncheckedTypeExtractor.nonEmpty) - - // turn any unresolved type variables in freevars into existential skolems - val skolems = freeVars map (fv => unapplyContext.owner.newExistentialSkolem(fv, fv)) - arg setType pattp.substSym(freeVars, skolems) - argDummy setInfo arg.tpe - } - - // clearing the type is necessary so that ref will be stabilized; see bug 881 - val fun1 = typedPos(fun.pos)(Apply(Select(fun.clearType(), unapp), List(arg))) - - if (fun1.tpe.isErroneous) duplErrTree - else { - val resTp = fun1.tpe.finalResultType.dealiasWiden - val nbSubPats = args.length - val (formals, formalsExpanded) = - extractorFormalTypes(fun0.pos, resTp, nbSubPats, fun1.symbol, treeInfo.effectivePatternArity(args)) - if (formals == null) duplErrorTree(WrongNumberOfArgsError(tree, fun)) - else { - val args1 = typedArgs(args, mode, formals, formalsExpanded) - val pt1 = ensureFullyDefined(pt) // SI-1048 - val itype = glb(List(pt1, arg.tpe)) - arg setType pt1 // restore type (arg is a dummy tree, just needs to pass typechecking) - val unapply = UnApply(fun1, args1) setPos tree.pos setType itype - - // if the type that the unapply method expects for its argument is uncheckable, wrap in classtag extractor - // skip if the unapply's type is not a method type with (at least, but really it should be exactly) one argument - // also skip if we already wrapped a classtag extractor (so we don't keep doing that forever) - if (uncheckedTypeExtractor.isEmpty || fun1.symbol.owner.isNonBottomSubClass(ClassTagClass)) unapply - else wrapClassTagUnapply(unapply, uncheckedTypeExtractor.get, unappType.paramTypes.head) - } - } - } - - def wrapClassTagUnapply(uncheckedPattern: Tree, classTagExtractor: Tree, pt: Type): Tree = { - // TODO: disable when in unchecked match - // we don't create a new Context for a Match, so find the CaseDef, then go out one level and navigate back to the match that has this case - // val thisCase = context.nextEnclosing(_.tree.isInstanceOf[CaseDef]) - // val unchecked = thisCase.outer.tree.collect{case Match(selector, cases) if cases contains thisCase => selector} match { - // case List(Typed(_, tpt)) if tpt.tpe hasAnnotation UncheckedClass => true - // case t => println("outer tree: "+ (t, thisCase, thisCase.outer.tree)); false - // } - // println("wrapClassTagUnapply"+ (!isPastTyper && infer.containsUnchecked(pt), pt, uncheckedPattern)) - // println("wrapClassTagUnapply: "+ extractor) - // println(util.Position.formatMessage(uncheckedPattern.pos, "made unchecked type test into a checked one", true)) - - val args = List(uncheckedPattern) - val app = atPos(uncheckedPattern.pos)(Apply(classTagExtractor, args)) - // must call doTypedUnapply directly, as otherwise we get undesirable rewrites - // and re-typechecks of the target of the unapply call in PATTERNmode, - // this breaks down when the classTagExtractor (which defineds the unapply member) is not a simple reference to an object, - // but an arbitrary tree as is the case here - doTypedUnapply(app, classTagExtractor, classTagExtractor, args, PATTERNmode, pt) - } - - // if there's a ClassTag that allows us to turn the unchecked type test for `pt` into a checked type test - // return the corresponding extractor (an instance of ClassTag[`pt`]) - def extractorForUncheckedType(pos: Position, pt: Type): Option[Tree] = if (isPastTyper) None else { - // only look at top-level type, can't (reliably) do anything about unchecked type args (in general) - // but at least make a proper type before passing it elsewhere - val pt1 = pt.dealiasWiden match { - case tr @ TypeRef(pre, sym, args) if args.nonEmpty => copyTypeRef(tr, pre, sym, sym.typeParams map (_.tpeHK)) // replace actual type args with dummies - case pt1 => pt1 - } - pt1 match { - // if at least one of the types in an intersection is checkable, use the checkable ones - // this avoids problems as in run/matchonseq.scala, where the expected type is `Coll with scala.collection.SeqLike` - // Coll is an abstract type, but SeqLike of course is not - case RefinedType(ps, _) if ps.length > 1 && (ps exists infer.isCheckable) => - None - - case ptCheckable if infer isUncheckable ptCheckable => - val classTagExtractor = resolveClassTag(pos, ptCheckable) - - if (classTagExtractor != EmptyTree && unapplyMember(classTagExtractor.tpe) != NoSymbol) - Some(classTagExtractor) - else None - - case _ => None - } - } - /** * Convert an annotation constructor call into an AnnotationInfo. */ -- cgit v1.2.3 From 13ad734393dc088a3ea30fd57da339247f779cb8 Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Sat, 17 Aug 2013 08:24:55 -0700 Subject: Compressed central TreeMaker pattern match. It's easier to follow the logic when it can all be seen at once. I moved the specification excerpts down below for continued reference. --- .../nsc/transform/patmat/MatchTranslation.scala | 152 ++++++++++----------- 1 file changed, 73 insertions(+), 79 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala index 41bdfe1076..682631b70d 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala @@ -316,86 +316,39 @@ trait MatchTranslation extends CpsPatternHacks { } } + def unsupportedPatternMsg = sm""" + |unsupported pattern: ${patTree.shortClass} $patTree (this is a scalac bug.) + |Tree diagnostics: + | ${asCompactDebugString(patTree)} + |""".trim + + def one(maker: TreeMaker) = noFurtherSubPats(maker) + def none() = noFurtherSubPats() + + // Summary of translation cases. I moved the excerpts from the specification further below so all + // the logic can be seen at once. + // + // [1] skip wildcard trees -- no point in checking them + // [2] extractor and constructor patterns + // [3] replace subpatBinder by patBinder, as if the Bind was not there. + // It must be patBinder, as subpatBinder has the wrong info: even if the bind assumes a better type, + // this is not guaranteed until we cast + // [4] typed patterns - a typed pattern never has any subtrees + // must treat Typed and Bind together -- we need to know the patBinder of the Bind pattern to get at the actual type + // [5] literal and stable id patterns + // [6] pattern alternatives + // [7] symbol-less bind patterns - this happens in certain ill-formed programs, there'll be an error later + // don't fail here though (or should we?) val (treeMakers, subpats) = patTree match { - // skip wildcard trees -- no point in checking them - case WildcardPattern() => noFurtherSubPats() - case UnApply(unfun, args) => - // TODO: check unargs == args - // debug.patmat("unfun: "+ (unfun.tpe, unfun.symbol.ownerChain, unfun.symbol.info, patBinder.info)) - translateExtractorPattern(ExtractorCall(unfun, args)) - - /* A constructor pattern is of the form c(p1, ..., pn) where n ≥ 0. - It consists of a stable identifier c, followed by element patterns p1, ..., pn. - The constructor c is a simple or qualified name which denotes a case class (§5.3.2). - - If the case class is monomorphic, then it must conform to the expected type of the pattern, - and the formal parameter types of x’s primary constructor (§5.3) are taken as the expected types of the element patterns p1, ..., pn. - - If the case class is polymorphic, then its type parameters are instantiated so that the instantiation of c conforms to the expected type of the pattern. - The instantiated formal parameter types of c’s primary constructor are then taken as the expected types of the component patterns p1, ..., pn. - - The pattern matches all objects created from constructor invocations c(v1, ..., vn) where each element pattern pi matches the corresponding value vi . - A special case arises when c’s formal parameter types end in a repeated parameter. This is further discussed in (§8.1.9). - **/ - case Apply(fun, args) => - ExtractorCall.fromCaseClass(fun, args) map translateExtractorPattern getOrElse { - ErrorUtils.issueNormalTypeError(patTree, "Could not find unapply member for "+ fun +" with args "+ args)(context) - noFurtherSubPats() - } - - /* A typed pattern x : T consists of a pattern variable x and a type pattern T. - The type of x is the type pattern T, where each type variable and wildcard is replaced by a fresh, unknown type. - This pattern matches any value matched by the type pattern T (§8.2); it binds the variable name to that value. - */ - // must treat Typed and Bind together -- we need to know the patBinder of the Bind pattern to get at the actual type - case MaybeBoundTyped(subPatBinder, pt) => - val next = glb(List(dealiasWiden(patBinder.info), pt)).normalize - // a typed pattern never has any subtrees - noFurtherSubPats(TypeTestTreeMaker(subPatBinder, patBinder, pt, next)(pos)) - - /* A pattern binder x@p consists of a pattern variable x and a pattern p. - The type of the variable x is the static type T of the pattern p. - This pattern matches any value v matched by the pattern p, - provided the run-time type of v is also an instance of T, <-- TODO! https://issues.scala-lang.org/browse/SI-1503 - and it binds the variable name to that value. - */ - case Bound(subpatBinder, p) => - // replace subpatBinder by patBinder (as if the Bind was not there) - withSubPats(List(SubstOnlyTreeMaker(subpatBinder, patBinder)), - // must be patBinder, as subpatBinder has the wrong info: even if the bind assumes a better type, this is not guaranteed until we cast - (patBinder, p) - ) - - /* 8.1.4 Literal Patterns - A literal pattern L matches any value that is equal (in terms of ==) to the literal L. - The type of L must conform to the expected type of the pattern. - - 8.1.5 Stable Identifier Patterns (a stable identifier r (see §3.1)) - The pattern matches any value v such that r == v (§12.1). - The type of r must conform to the expected type of the pattern. - */ - case Literal(Constant(_)) | Ident(_) | Select(_, _) | This(_) => - noFurtherSubPats(EqualityTestTreeMaker(patBinder, patTree, pos)) - - case Alternative(alts) => - noFurtherSubPats(AlternativesTreeMaker(patBinder, alts map (translatePattern(patBinder, _)), alts.head.pos)) - - /* TODO: Paul says about future version: I think this should work, and always intended to implement if I can get away with it. - case class Foo(x: Int, y: String) - case class Bar(z: Int) - - def f(x: Any) = x match { case Foo(x, _) | Bar(x) => x } // x is lub of course. - */ - - case Bind(n, p) => // this happens in certain ill-formed programs, there'll be an error later - debug.patmat("WARNING: Bind tree with unbound symbol "+ patTree) - noFurtherSubPats() // there's no symbol -- something's wrong... don't fail here though (or should we?) - - // case Star(_) | ArrayValue => error("stone age pattern relics encountered!") - - case _ => - typer.context.unit.error(patTree.pos, s"unsupported pattern: $patTree (a ${patTree.getClass}).\n This is a scalac bug. Tree diagnostics: ${asCompactDebugString(patTree)}.") - noFurtherSubPats() + case WildcardPattern() => none() + case UnApply(unfun, args) => translateExtractorPattern(ExtractorCall(unfun, args)) + case Apply(fun, args) => ExtractorCall.fromCaseClass(fun, args) map translateExtractorPattern getOrElse noFurtherSubPats() + case MaybeBoundTyped(subPatBinder, pt) => one(TypeTestTreeMaker(subPatBinder, patBinder, pt, glb(List(dealiasWiden(patBinder.info), pt)).normalize)(pos)) + case Bound(subpatBinder, p) => withSubPats(List(SubstOnlyTreeMaker(subpatBinder, patBinder)), (patBinder, p)) + case Literal(Constant(_)) | Ident(_) | Select(_, _) | This(_) => one(EqualityTestTreeMaker(patBinder, patTree, pos)) + case Alternative(alts) => one(AlternativesTreeMaker(patBinder, alts map (translatePattern(patBinder, _)), alts.head.pos)) + case Bind(_, _) => devWarning(s"Bind tree with unbound symbol $patTree") ; none() + case _ => context.unit.error(patTree.pos, unsupportedPatternMsg) ; none() } treeMakers ++ subpats.flatMap { case (binder, pat) => @@ -416,6 +369,47 @@ trait MatchTranslation extends CpsPatternHacks { def translateBody(body: Tree, matchPt: Type): TreeMaker = BodyTreeMaker(body, matchPt) + // Some notes from the specification + + /*A constructor pattern is of the form c(p1, ..., pn) where n ≥ 0. + It consists of a stable identifier c, followed by element patterns p1, ..., pn. + The constructor c is a simple or qualified name which denotes a case class (§5.3.2). + + If the case class is monomorphic, then it must conform to the expected type of the pattern, + and the formal parameter types of x’s primary constructor (§5.3) are taken as the expected + types of the element patterns p1, ..., pn. + + If the case class is polymorphic, then its type parameters are instantiated so that the + instantiation of c conforms to the expected type of the pattern. + The instantiated formal parameter types of c’s primary constructor are then taken as the + expected types of the component patterns p1, ..., pn. + + The pattern matches all objects created from constructor invocations c(v1, ..., vn) + where each element pattern pi matches the corresponding value vi . + A special case arises when c’s formal parameter types end in a repeated parameter. + This is further discussed in (§8.1.9). + **/ + + /* A typed pattern x : T consists of a pattern variable x and a type pattern T. + The type of x is the type pattern T, where each type variable and wildcard is replaced by a fresh, unknown type. + This pattern matches any value matched by the type pattern T (§8.2); it binds the variable name to that value. + */ + + /* A pattern binder x@p consists of a pattern variable x and a pattern p. + The type of the variable x is the static type T of the pattern p. + This pattern matches any value v matched by the pattern p, + provided the run-time type of v is also an instance of T, <-- TODO! https://issues.scala-lang.org/browse/SI-1503 + and it binds the variable name to that value. + */ + + /* 8.1.4 Literal Patterns + A literal pattern L matches any value that is equal (in terms of ==) to the literal L. + The type of L must conform to the expected type of the pattern. + + 8.1.5 Stable Identifier Patterns (a stable identifier r (see §3.1)) + The pattern matches any value v such that r == v (§12.1). + The type of r must conform to the expected type of the pattern. + */ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // helper methods: they analyze types and trees in isolation, but they are not (directly) concerned with the structure of the overall translation -- cgit v1.2.3 From c5f7aaca72591ebb8e892e8781aa51b144201e36 Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Sat, 17 Aug 2013 08:34:10 -0700 Subject: Turned TreeMaker into case class. Type aliases are better than naked tuples, but only barely. --- .../nsc/transform/patmat/MatchTranslation.scala | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala index 682631b70d..ae406ad8dd 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala @@ -52,6 +52,11 @@ trait MatchTranslation extends CpsPatternHacks { trait MatchTranslator extends TreeMakers { import typer.context + // a list of TreeMakers that encode `patTree`, and a list of arguments for recursive invocations of `translatePattern` to encode its subpatterns + private case class TranslationStep(makers: List[TreeMaker], subpatterns: List[(Symbol, Tree)]) { + def merge(f: (Symbol, Tree) => List[TreeMaker]): List[TreeMaker] = makers ::: (subpatterns flatMap f.tupled) + } + // Why is it so difficult to say "here's a name and a context, give me any // matching symbol in scope" ? I am sure this code is wrong, but attempts to // use the scopes of the contexts in the enclosing context chain discover @@ -234,15 +239,14 @@ trait MatchTranslation extends CpsPatternHacks { * a function that will take care of binding and substitution of the next ast (to the right). * */ - def translateCase(scrutSym: Symbol, pt: Type)(caseDef: CaseDef) = caseDef match { case CaseDef(pattern, guard, body) => + def translateCase(scrutSym: Symbol, pt: Type)(caseDef: CaseDef) = { + val CaseDef(pattern, guard, body) = caseDef translatePattern(scrutSym, pattern) ++ translateGuard(guard) :+ translateBody(body, pt) } def translatePattern(patBinder: Symbol, patTree: Tree): List[TreeMaker] = { - // a list of TreeMakers that encode `patTree`, and a list of arguments for recursive invocations of `translatePattern` to encode its subpatterns - type TranslationStep = (List[TreeMaker], List[(Symbol, Tree)]) - def withSubPats(treeMakers: List[TreeMaker], subpats: (Symbol, Tree)*): TranslationStep = (treeMakers, subpats.toList) - def noFurtherSubPats(treeMakers: TreeMaker*): TranslationStep = (treeMakers.toList, Nil) + def withSubPats(treeMakers: List[TreeMaker], subpats: (Symbol, Tree)*): TranslationStep = TranslationStep(treeMakers, subpats.toList) + def noFurtherSubPats(treeMakers: TreeMaker*): TranslationStep = TranslationStep(treeMakers.toList, Nil) val pos = patTree.pos @@ -339,7 +343,7 @@ trait MatchTranslation extends CpsPatternHacks { // [6] pattern alternatives // [7] symbol-less bind patterns - this happens in certain ill-formed programs, there'll be an error later // don't fail here though (or should we?) - val (treeMakers, subpats) = patTree match { + val translationStep = patTree match { case WildcardPattern() => none() case UnApply(unfun, args) => translateExtractorPattern(ExtractorCall(unfun, args)) case Apply(fun, args) => ExtractorCall.fromCaseClass(fun, args) map translateExtractorPattern getOrElse noFurtherSubPats() @@ -351,9 +355,7 @@ trait MatchTranslation extends CpsPatternHacks { case _ => context.unit.error(patTree.pos, unsupportedPatternMsg) ; none() } - treeMakers ++ subpats.flatMap { case (binder, pat) => - translatePattern(binder, pat) // recurse on subpatterns - } + translationStep merge translatePattern } def translateGuard(guard: Tree): List[TreeMaker] = -- cgit v1.2.3 From e611eeaf4573db8ec16a38cb00705391496ebe0e Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Sat, 17 Aug 2013 08:46:57 -0700 Subject: Move most of Typers#Typer#typedTyped into PatternTypers. Again moving pattern-typing logic out of Typers. You can tell I like writing Typers#Typer#typedTyped. --- .../tools/nsc/typechecker/PatternTypers.scala | 43 +++++++++++++ .../scala/tools/nsc/typechecker/Typers.scala | 75 ++++++---------------- 2 files changed, 64 insertions(+), 54 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala b/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala index f09c142aef..6b4098761a 100644 --- a/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala @@ -78,6 +78,49 @@ trait PatternTypers { loop(args0, formals0, adapted0) } + protected def typedStarInPattern(tree: Tree, mode: Mode, pt: Type) = { + val Typed(expr, tpt) = tree + val exprTyped = typed(expr, mode.onlySticky) + def subArrayType(pt: Type) = + if (isPrimitiveValueClass(pt.typeSymbol) || !isFullyDefined(pt)) arrayType(pt) + else { + val tparam = context.owner freshExistential "" setInfo TypeBounds.upper(pt) + newExistentialType(List(tparam), arrayType(tparam.tpe)) + } + + val (exprAdapted, baseClass) = exprTyped.tpe.typeSymbol match { + case ArrayClass => (adapt(exprTyped, mode.onlySticky, subArrayType(pt)), ArrayClass) + case _ => (adapt(exprTyped, mode.onlySticky, seqType(pt)), SeqClass) + } + exprAdapted.tpe.baseType(baseClass) match { + case TypeRef(_, _, List(elemtp)) => + treeCopy.Typed(tree, exprAdapted, tpt setType elemtp) setType elemtp + case _ => + setError(tree) + } + } + + protected def typedInPattern(tree: Typed, mode: Mode, pt: Type) = { + val Typed(expr, tpt) = tree + val tptTyped = typedType(tpt, mode) + val exprTyped = typed(expr, mode.onlySticky, tptTyped.tpe.deconst) + val treeTyped = treeCopy.Typed(tree, exprTyped, tptTyped) + + if (mode.inPatternMode) { + val uncheckedTypeExtractor = extractorForUncheckedType(tpt.pos, tptTyped.tpe) + // make fully defined to avoid bounded wildcard types that may be in pt from calling dropExistential (SI-2038) + val ptDefined = ensureFullyDefined(pt) // FIXME this is probably redundant now that we don't dropExistenial in pattern mode. + val ownType = inferTypedPattern(tptTyped, tptTyped.tpe, ptDefined, canRemedy = uncheckedTypeExtractor.nonEmpty) + treeTyped setType ownType + + uncheckedTypeExtractor match { + case None => treeTyped + case Some(extractor) => wrapClassTagUnapply(treeTyped, extractor, tptTyped.tpe) + } + } else + treeTyped setType tptTyped.tpe + } + /* * To deal with the type slack between actual (run-time) types and statically known types, for each abstract type T, * reflect its variance as a skolem that is upper-bounded by T (covariant position), or lower-bounded by T (contravariant). diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 522ad97036..a2f98b04ae 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -4773,61 +4773,28 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper } def typedTyped(tree: Typed) = { - val expr = tree.expr - val tpt = tree.tpt - tpt match { - case Function(List(), EmptyTree) => - // find out whether the programmer is trying to eta-expand a macro def - // to do that we need to typecheck the tree first (we need a symbol of the eta-expandee) - // that typecheck must not trigger macro expansions, so we explicitly prohibit them - // however we cannot do `context.withMacrosDisabled` - // because `expr` might contain nested macro calls (see SI-6673) - val exprTyped = typed1(suppressMacroExpansion(expr), mode, pt) - exprTyped match { - case macroDef if treeInfo.isMacroApplication(macroDef) => - MacroEtaError(exprTyped) - case _ => - typedEta(checkDead(exprTyped)) - } - - case t if treeInfo isWildcardStarType t => - val exprTyped = typed(expr, mode.onlySticky) - def subArrayType(pt: Type) = - if (isPrimitiveValueClass(pt.typeSymbol) || !isFullyDefined(pt)) arrayType(pt) - else { - val tparam = context.owner freshExistential "" setInfo TypeBounds.upper(pt) - newExistentialType(List(tparam), arrayType(tparam.tpe)) - } - - val (exprAdapted, baseClass) = exprTyped.tpe.typeSymbol match { - case ArrayClass => (adapt(exprTyped, mode.onlySticky, subArrayType(pt)), ArrayClass) - case _ => (adapt(exprTyped, mode.onlySticky, seqType(pt)), SeqClass) - } - exprAdapted.tpe.baseType(baseClass) match { - case TypeRef(_, _, List(elemtp)) => - treeCopy.Typed(tree, exprAdapted, tpt setType elemtp) setType elemtp - case _ => - setError(tree) + if (treeInfo isWildcardStarType tree.tpt) + typedStarInPattern(tree, mode.onlySticky, pt) + else if (mode.inPatternMode) + typedInPattern(tree, mode.onlySticky, pt) + else tree match { + // find out whether the programmer is trying to eta-expand a macro def + // to do that we need to typecheck the tree first (we need a symbol of the eta-expandee) + // that typecheck must not trigger macro expansions, so we explicitly prohibit them + // however we cannot do `context.withMacrosDisabled` + // because `expr` might contain nested macro calls (see SI-6673) + // + // Note: apparently `Function(Nil, EmptyTree)` is the secret parser marker + // which means trailing underscore. + case Typed(expr, Function(Nil, EmptyTree)) => + typed1(suppressMacroExpansion(expr), mode, pt) match { + case macroDef if treeInfo.isMacroApplication(macroDef) => MacroEtaError(macroDef) + case exprTyped => typedEta(checkDead(exprTyped)) } - - case _ => - val tptTyped = typedType(tpt, mode) - val exprTyped = typed(expr, mode.onlySticky, tptTyped.tpe.deconst) - val treeTyped = treeCopy.Typed(tree, exprTyped, tptTyped) - - if (mode.inPatternMode) { - val uncheckedTypeExtractor = extractorForUncheckedType(tpt.pos, tptTyped.tpe) - // make fully defined to avoid bounded wildcard types that may be in pt from calling dropExistential (SI-2038) - val ptDefined = ensureFullyDefined(pt) // FIXME this is probably redundant now that we don't dropExistenial in pattern mode. - val ownType = inferTypedPattern(tptTyped, tptTyped.tpe, ptDefined, canRemedy = uncheckedTypeExtractor.nonEmpty) - treeTyped setType ownType - - uncheckedTypeExtractor match { - case None => treeTyped - case Some(extractor) => wrapClassTagUnapply(treeTyped, extractor, tptTyped.tpe) - } - } else - treeTyped setType tptTyped.tpe + case Typed(expr, tpt) => + val tpt1 = typedType(tpt, mode) // type the ascribed type first + val expr1 = typed(expr, mode.onlySticky, tpt1.tpe.deconst) // then type the expression with tpt1 as the expected type + treeCopy.Typed(tree, expr1, tpt1) setType tpt1.tpe } } -- cgit v1.2.3 From 35775a8fc87e9f3538e13e8d26f9b151138efe8c Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Sat, 17 Aug 2013 08:57:53 -0700 Subject: SI-4425 do some validity checking on unapplies. Filter out unapplies which can't be called (such as those with a second non-implicit parameter list) and report the error in a meaningful fashion. --- .../tools/nsc/typechecker/ContextErrors.scala | 7 ++- .../scala/tools/nsc/typechecker/Unapplies.scala | 10 +++- test/files/neg/t4425.check | 3 +- test/files/neg/t4425b.check | 55 ++++++++++++++++++++++ test/files/neg/t4425b.scala | 38 +++++++++++++++ 5 files changed, 109 insertions(+), 4 deletions(-) create mode 100644 test/files/neg/t4425b.check create mode 100644 test/files/neg/t4425b.scala (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala index 81f5545695..7f4bf0dfbc 100644 --- a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala +++ b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala @@ -593,7 +593,12 @@ trait ContextErrors { } def CaseClassConstructorError(tree: Tree) = { - issueNormalTypeError(tree, tree.symbol + " is not a case class constructor, nor does it have an unapply/unapplySeq method") + val baseMessage = tree.symbol + " is not a case class constructor, nor does it have an unapply/unapplySeq method" + val addendum = directUnapplyMember(tree.symbol.info) match { + case sym if hasMultipleNonImplicitParamLists(sym) => s"\nNote: ${sym.defString} exists in ${tree.symbol}, but it cannot be used as an extractor due to its second non-implicit parameter list" + case _ => "" + } + issueNormalTypeError(tree, baseMessage + addendum) setError(tree) } diff --git a/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala b/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala index 0e2c836860..18b8f8a9ce 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala @@ -46,8 +46,14 @@ trait Unapplies extends ast.TreeDSL { } } - /** returns unapply or unapplySeq if available */ - def unapplyMember(tp: Type): Symbol = (tp member nme.unapply) orElse (tp member nme.unapplySeq) + /** Returns unapply or unapplySeq if available, without further checks. + */ + def directUnapplyMember(tp: Type): Symbol = (tp member nme.unapply) orElse (tp member nme.unapplySeq) + + /** Filters out unapplies with multiple (non-implicit) parameter lists, + * as they cannot be used as extractors + */ + def unapplyMember(tp: Type): Symbol = directUnapplyMember(tp) filter (sym => !hasMultipleNonImplicitParamLists(sym)) object ExtractorType { def unapply(tp: Type): Option[Symbol] = unapplyMember(tp).toOption diff --git a/test/files/neg/t4425.check b/test/files/neg/t4425.check index 0f2fe6f2d1..cb5da6e7dc 100644 --- a/test/files/neg/t4425.check +++ b/test/files/neg/t4425.check @@ -1,4 +1,5 @@ -t4425.scala:3: error: isInstanceOf cannot test if value types are references. +t4425.scala:3: error: object X is not a case class constructor, nor does it have an unapply/unapplySeq method +Note: def unapply(x: Int)(y: Option[Int]): None.type exists in object X, but it cannot be used as an extractor due to its second non-implicit parameter list 42 match { case _ X _ => () } ^ one error found diff --git a/test/files/neg/t4425b.check b/test/files/neg/t4425b.check new file mode 100644 index 0000000000..e43c489586 --- /dev/null +++ b/test/files/neg/t4425b.check @@ -0,0 +1,55 @@ +t4425b.scala:5: error: object X is not a case class constructor, nor does it have an unapply/unapplySeq method +Note: def unapply(x: String)(y: String): Nothing exists in object X, but it cannot be used as an extractor due to its second non-implicit parameter list + println( "" match { case _ X _ => "ok" ; case _ => "fail" }) + ^ +t4425b.scala:6: error: object X is not a case class constructor, nor does it have an unapply/unapplySeq method +Note: def unapply(x: String)(y: String): Nothing exists in object X, but it cannot be used as an extractor due to its second non-implicit parameter list + println((X: Any) match { case _ X _ => "ok" ; case _ => "fail" }) + ^ +t4425b.scala:7: error: object X is not a case class constructor, nor does it have an unapply/unapplySeq method +Note: def unapply(x: String)(y: String): Nothing exists in object X, but it cannot be used as an extractor due to its second non-implicit parameter list + println( "" match { case X(_) => "ok" ; case _ => "fail" }) + ^ +t4425b.scala:8: error: object X is not a case class constructor, nor does it have an unapply/unapplySeq method +Note: def unapply(x: String)(y: String): Nothing exists in object X, but it cannot be used as an extractor due to its second non-implicit parameter list + println((X: Any) match { case X(_) => "ok" ; case _ => "fail" }) + ^ +t4425b.scala:9: error: object X is not a case class constructor, nor does it have an unapply/unapplySeq method +Note: def unapply(x: String)(y: String): Nothing exists in object X, but it cannot be used as an extractor due to its second non-implicit parameter list + println( "" match { case X(_, _) => "ok" ; case _ => "fail" }) + ^ +t4425b.scala:10: error: object X is not a case class constructor, nor does it have an unapply/unapplySeq method +Note: def unapply(x: String)(y: String): Nothing exists in object X, but it cannot be used as an extractor due to its second non-implicit parameter list + println((X: Any) match { case X(_, _) => "ok" ; case _ => "fail" }) + ^ +t4425b.scala:18: error: result type Nothing of unapply defined in method unapply in object X does not conform to Option[_] or Boolean + println( "" match { case _ X _ => "ok" ; case _ => "fail" }) + ^ +t4425b.scala:19: error: result type Nothing of unapply defined in method unapply in object X does not conform to Option[_] or Boolean + println((X: Any) match { case _ X _ => "ok" ; case _ => "fail" }) + ^ +t4425b.scala:20: error: result type Nothing of unapply defined in method unapply in object X does not conform to Option[_] or Boolean + println( "" match { case X(_) => "ok" ; case _ => "fail" }) + ^ +t4425b.scala:21: error: result type Nothing of unapply defined in method unapply in object X does not conform to Option[_] or Boolean + println((X: Any) match { case X(_) => "ok" ; case _ => "fail" }) + ^ +t4425b.scala:22: error: result type Nothing of unapply defined in method unapply in object X does not conform to Option[_] or Boolean + println( "" match { case X(_, _) => "ok" ; case _ => "fail" }) + ^ +t4425b.scala:23: error: result type Nothing of unapply defined in method unapply in object X does not conform to Option[_] or Boolean + println((X: Any) match { case X(_, _) => "ok" ; case _ => "fail" }) + ^ +t4425b.scala:31: error: wrong number of arguments for object X + println( "" match { case _ X _ => "ok" ; case _ => "fail" }) + ^ +t4425b.scala:32: error: wrong number of arguments for object X + println((X: Any) match { case _ X _ => "ok" ; case _ => "fail" }) + ^ +t4425b.scala:35: error: wrong number of arguments for object X + println( "" match { case X(_, _) => "ok" ; case _ => "fail" }) + ^ +t4425b.scala:36: error: wrong number of arguments for object X + println((X: Any) match { case X(_, _) => "ok" ; case _ => "fail" }) + ^ +16 errors found diff --git a/test/files/neg/t4425b.scala b/test/files/neg/t4425b.scala new file mode 100644 index 0000000000..861e9521f6 --- /dev/null +++ b/test/files/neg/t4425b.scala @@ -0,0 +1,38 @@ +object Test1 { + object X { def unapply(x : String)(y: String) = throw new Exception } + + def f1() { + println( "" match { case _ X _ => "ok" ; case _ => "fail" }) + println((X: Any) match { case _ X _ => "ok" ; case _ => "fail" }) + println( "" match { case X(_) => "ok" ; case _ => "fail" }) + println((X: Any) match { case X(_) => "ok" ; case _ => "fail" }) + println( "" match { case X(_, _) => "ok" ; case _ => "fail" }) + println((X: Any) match { case X(_, _) => "ok" ; case _ => "fail" }) + } +} + +object Test2 { + object X { def unapply(x : String) = throw new Exception } + + def f1() { + println( "" match { case _ X _ => "ok" ; case _ => "fail" }) + println((X: Any) match { case _ X _ => "ok" ; case _ => "fail" }) + println( "" match { case X(_) => "ok" ; case _ => "fail" }) + println((X: Any) match { case X(_) => "ok" ; case _ => "fail" }) + println( "" match { case X(_, _) => "ok" ; case _ => "fail" }) + println((X: Any) match { case X(_, _) => "ok" ; case _ => "fail" }) + } +} + +object Test3 { + object X { def unapply(x : String) = None } + + def f1() { + println( "" match { case _ X _ => "ok" ; case _ => "fail" }) + println((X: Any) match { case _ X _ => "ok" ; case _ => "fail" }) + println( "" match { case X(_) => "ok" ; case _ => "fail" }) + println((X: Any) match { case X(_) => "ok" ; case _ => "fail" }) + println( "" match { case X(_, _) => "ok" ; case _ => "fail" }) + println((X: Any) match { case X(_, _) => "ok" ; case _ => "fail" }) + } +} -- cgit v1.2.3 From a2189f48084b71a25725fedb7808aba2e71715ad Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Sat, 17 Aug 2013 09:18:27 -0700 Subject: Expanded logic in formalTypes. Made the super unusual move (for me) of making a method longer instead of shorter, because I had quite a time modifying the logic as it was. Ideally we wouldn't use this method because it is much less efficient than necessary. If one is typing a call like this: def f(xs: Int*) f(p1, p2, p3, ..., p500) Then it is not necessary to create a list with 500 IntTpes. Once you run out of non-varargs types, every argument which remains can be typed against the vararg type directly. --- .../scala/tools/nsc/typechecker/Infer.scala | 25 +++++++++++++++------- 1 file changed, 17 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/typechecker/Infer.scala b/src/compiler/scala/tools/nsc/typechecker/Infer.scala index 2f86e23415..c8377fde95 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Infer.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Infer.scala @@ -25,18 +25,27 @@ trait Infer extends Checkable { /** The formal parameter types corresponding to `formals`. * If `formals` has a repeated last parameter, a list of - * (nargs - params.length + 1) copies of its type is returned. - * By-name types are replaced with their underlying type. + * (numArgs - numFormals + 1) copies of its type is appended + * to the other formals. By-name types are replaced with their + * underlying type. * * @param removeByName allows keeping ByName parameters. Used in NamesDefaults. * @param removeRepeated allows keeping repeated parameter (if there's one argument). Used in NamesDefaults. */ - def formalTypes(formals: List[Type], nargs: Int, removeByName: Boolean = true, removeRepeated: Boolean = true): List[Type] = { - val formals1 = if (removeByName) formals mapConserve dropByName else formals - if (isVarArgTypes(formals1) && (removeRepeated || formals.length != nargs)) { - val ft = formals1.last.dealiasWiden.typeArgs.head - formals1.init ::: (for (i <- List.range(formals1.length - 1, nargs)) yield ft) - } else formals1 + def formalTypes(formals: List[Type], numArgs: Int, removeByName: Boolean = true, removeRepeated: Boolean = true): List[Type] = { + val numFormals = formals.length + val formals1 = if (removeByName) formals mapConserve dropByName else formals + val expandLast = ( + (removeRepeated || numFormals != numArgs) + && isVarArgTypes(formals1) + ) + def lastType = formals1.last.dealiasWiden.typeArgs.head + def expanded(n: Int) = (1 to n).toList map (_ => lastType) + + if (expandLast) + formals1.init ::: expanded(numArgs - numFormals + 1) + else + formals1 } /** Sorts the alternatives according to the given comparison function. -- cgit v1.2.3 From 8c94e9cefce1a4c7f8d35c566770aab6908c993d Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Sat, 17 Aug 2013 09:20:47 -0700 Subject: Add some logging to instantiateTypeVar. This method is right at the center of ALL KINDS of bad. "this is quite nasty" begins the comment, and I credit its deadpan understatement. A little logging is the least we can offer the next guy. --- src/compiler/scala/tools/nsc/typechecker/Infer.scala | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/typechecker/Infer.scala b/src/compiler/scala/tools/nsc/typechecker/Infer.scala index c8377fde95..a7c43361fa 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Infer.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Infer.scala @@ -1199,6 +1199,17 @@ trait Infer extends Checkable { val tparam = tvar.origin.typeSymbol val TypeBounds(lo0, hi0) = tparam.info.bounds val tb @ TypeBounds(lo1, hi1) = instBounds(tvar) + val enclCase = context.enclosingCaseDef + + log("\n" + sm""" + |----- + | enclCase: ${enclCase.tree} + | saved: ${enclCase.savedTypeBounds} + | tparam: ${tparam.shortSymbolClass} + | def_s: ${tparam.defString} + | seen_s: ${tparam.defStringSeenAs(tb)} + |----- + """.trim) if (lo1 <:< hi1) { if (lo1 <:< lo0 && hi0 <:< hi1) // bounds unimproved @@ -1206,7 +1217,7 @@ trait Infer extends Checkable { else if (tparam == lo1.typeSymbolDirect || tparam == hi1.typeSymbolDirect) log(s"cyclical bounds: discarding TypeBounds($lo1, $hi1) for $tparam because $tparam appears as bounds") else { - context.enclosingCaseDef pushTypeBounds tparam + enclCase pushTypeBounds tparam tparam setInfo logResult(s"updated bounds: $tparam from ${tparam.info} to")(tb) } } -- cgit v1.2.3 From a31f1f0e42f6f0a5ffdff4294d04b055816e63e7 Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Sat, 17 Aug 2013 09:24:58 -0700 Subject: Cleanups in Typers. Should be behaviorally neutral. A little more logging and a little better style. --- .../scala/tools/nsc/typechecker/Typers.scala | 53 ++++++++++++---------- 1 file changed, 29 insertions(+), 24 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index a2f98b04ae..a1fb5816b9 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -36,7 +36,6 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper final def forArgMode(fun: Tree, mode: Mode) = if (treeInfo.isSelfOrSuperConstrCall(fun)) mode | SCCmode else mode - // printResult(s"forArgMode($fun, $mode) gets SCCmode")(mode | SCCmode) // namer calls typer.computeType(rhs) on DefDef / ValDef when tpt is empty. the result // is cached here and re-used in typedDefDef / typedValDef // Also used to cache imports type-checked by namer. @@ -90,7 +89,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper private final val InterpolatorCodeRegex = """\$\{.*?\}""".r private final val InterpolatorIdentRegex = """\$\w+""".r - abstract class Typer(context0: Context) extends TyperDiagnostics with Adaptation with Tag with TyperContextErrors with PatternTyper { + abstract class Typer(context0: Context) extends TyperDiagnostics with Adaptation with Tag with PatternTyper with TyperContextErrors { import context0.unit import typeDebug.{ ptTree, ptBlock, ptLine, inGreen, inRed } import TyperErrorGen._ @@ -905,6 +904,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper case _ => TypeTree(tree.tpe) setOriginal tree } } + def insertApply(): Tree = { assert(!context.inTypeConstructorAllowed, mode) //@M val adapted = adaptToName(tree, nme.apply) @@ -2373,7 +2373,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper // list, so substitute the final result type of the method, i.e. the type // of the case class. if (pat1.tpe.paramSectionCount > 0) - pat1 setType pat1.tpe.finalResultType + pat1 modifyType (_.finalResultType) for (bind @ Bind(name, _) <- cdef.pat) if (name.toTermName != nme.WILDCARD && bind.symbol != null && bind.symbol != NoSymbol) @@ -2388,8 +2388,10 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper // insert a cast if something typechecked under the GADT constraints, // but not in real life (i.e., now that's we've reset the method's type skolems' // infos back to their pre-GADT-constraint state) - if (isFullyDefined(pt) && !(body1.tpe <:< pt)) + if (isFullyDefined(pt) && !(body1.tpe <:< pt)) { + log(s"Adding cast to pattern because ${body1.tpe} does not conform to expected type $pt") body1 = typedPos(body1.pos)(gen.mkCast(body1, pt.dealiasWiden)) + } } // body1 = checkNoEscaping.locals(context.scope, pt, body1) @@ -3140,22 +3142,20 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper val tparams = context.extractUndetparams() if (tparams.isEmpty) { // all type params are defined def handleMonomorphicCall: Tree = { - // In order for checkDead not to be misled by the unfortunate special - // case of AnyRef#synchronized (which is implemented with signature T => T - // but behaves as if it were (=> T) => T) we need to know what is the actual - // target of a call. Since this information is no longer available from - // typedArg, it is recorded here. - val args1 = - // no expected type when jumping to a match label -- anything goes (this is ok since we're typing the translation of well-typed code) - // ... except during erasure: we must take the expected type into account as it drives the insertion of casts! - // I've exhausted all other semi-clean approaches I could think of in balancing GADT magic, SI-6145, CPS type-driven transforms and other existential trickiness - // (the right thing to do -- packing existential types -- runs into limitations in subtyping existential types, - // casting breaks SI-6145, - // not casting breaks GADT typing as it requires sneaking ill-typed trees past typer) - if (!phase.erasedTypes && fun.symbol.isLabel && treeInfo.isSynthCaseSymbol(fun.symbol)) + // no expected type when jumping to a match label -- anything goes (this is ok since we're typing the translation of well-typed code) + // ... except during erasure: we must take the expected type into account as it drives the insertion of casts! + // I've exhausted all other semi-clean approaches I could think of in balancing GADT magic, SI-6145, CPS type-driven transforms and other existential trickiness + // (the right thing to do -- packing existential types -- runs into limitations in subtyping existential types, + // casting breaks SI-6145, + // not casting breaks GADT typing as it requires sneaking ill-typed trees past typer) + def noExpectedType = !phase.erasedTypes && fun.symbol.isLabel && treeInfo.isSynthCaseSymbol(fun.symbol) + + val args1 = ( + if (noExpectedType) typedArgs(args, forArgMode(fun, mode)) else typedArgs(args, forArgMode(fun, mode), paramTypes, formals) + ) // instantiate dependent method types, must preserve singleton types where possible (stableTypeFor) -- example use case: // val foo = "foo"; def precise(x: String)(y: x.type): x.type = {...}; val bar : foo.type = precise(foo)(foo) @@ -3486,11 +3486,15 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper /** convert local symbols and skolems to existentials */ def packedType(tree: Tree, owner: Symbol): Type = { - def defines(tree: Tree, sym: Symbol) = - sym.isExistentialSkolem && sym.unpackLocation == tree || - tree.isDef && tree.symbol == sym - def isVisibleParameter(sym: Symbol) = - sym.isParameter && (sym.owner == owner) && (sym.isType || !owner.isAnonymousFunction) + def defines(tree: Tree, sym: Symbol) = ( + sym.isExistentialSkolem && sym.unpackLocation == tree + || tree.isDef && tree.symbol == sym + ) + def isVisibleParameter(sym: Symbol) = ( + sym.isParameter + && (sym.owner == owner) + && (sym.isType || !owner.isAnonymousFunction) + ) def containsDef(owner: Symbol, sym: Symbol): Boolean = (!sym.hasPackageFlag) && { var o = sym.owner @@ -4721,7 +4725,6 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper treeCopy.Star(tree, typed(tree.elem, mode, pt)) setType makeFullyDefined(pt) } - def issueTryWarnings(tree: Try): Try = { def checkForCatchAll(cdef: CaseDef) { def unbound(t: Tree) = t.symbol == null || t.symbol == NoSymbol @@ -4930,11 +4933,13 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper case _ => tree } } - else + else { // we should get here only when something before failed // and we try again (@see tryTypedApply). In that case we can assign // whatever type to tree; we just have to survive until a real error message is issued. + devWarning(tree.pos, s"Assigning Any type to TypeTree because tree.original == null") tree setType AnyTpe + } } def typedFunction(fun: Function) = { if (fun.symbol == NoSymbol) -- cgit v1.2.3 From e60e837db608c1531d96366a073ae8eb51ce78ae Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Sat, 17 Aug 2013 09:35:41 -0700 Subject: Simplify management of pattern vars. Always set their info the same way, and when doing so always translate repeated types into sequence types. --- .../nsc/transform/patmat/MatchTranslation.scala | 84 ++++++++++++---------- 1 file changed, 48 insertions(+), 36 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala index ae406ad8dd..4f89287bf5 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala @@ -48,6 +48,11 @@ trait MatchTranslation extends CpsPatternHacks { import global._ import definitions._ import global.analyzer.{ErrorUtils, formalTypes} + import treeInfo.{ Unapplied, isStar } + + // Always map repeated params to sequences + private def setVarInfo(sym: Symbol, info: Type) = + sym setInfo debug.patmatResult(s"changing ${sym.defString} to")(repeatedToSeq(info)) trait MatchTranslator extends TreeMakers { import typer.context @@ -134,10 +139,8 @@ trait MatchTranslation extends CpsPatternHacks { val Match(selector, cases) = match_ val (nonSyntheticCases, defaultOverride) = cases match { - case init :+ last if treeInfo isSyntheticDefaultCase last => - (init, Some(((scrut: Tree) => last.body))) - case _ => - (cases, None) + case init :+ last if treeInfo isSyntheticDefaultCase last => (init, Some(((scrut: Tree) => last.body))) + case _ => (cases, None) } checkMatchVariablePatterns(nonSyntheticCases) @@ -245,50 +248,49 @@ trait MatchTranslation extends CpsPatternHacks { } def translatePattern(patBinder: Symbol, patTree: Tree): List[TreeMaker] = { + val pos = patTree.pos + def patType = patBinder.info.dealiasWiden // the type of the variable bound to the pattern + + def glbWithBinder(other: Type) = glb(patType :: other :: Nil).normalize + def withSubPats(treeMakers: List[TreeMaker], subpats: (Symbol, Tree)*): TranslationStep = TranslationStep(treeMakers, subpats.toList) def noFurtherSubPats(treeMakers: TreeMaker*): TranslationStep = TranslationStep(treeMakers.toList, Nil) - val pos = patTree.pos - def translateExtractorPattern(extractor: ExtractorCall): TranslationStep = { - if (!extractor.isTyped) ErrorUtils.issueNormalTypeError(patTree, "Could not typecheck extractor call: "+ extractor)(context) - // if (extractor.resultInMonad == ErrorType) throw new TypeError(pos, "Unsupported extractor type: "+ extractor.tpe) - - debug.patmat("translateExtractorPattern checking parameter type: "+ ((patBinder, patBinder.info.widen, extractor.paramType, patBinder.info.widen <:< extractor.paramType))) - - // must use type `tp`, which is provided by extractor's result, not the type expected by binder, - // as b.info may be based on a Typed type ascription, which has not been taken into account yet by the translation - // (it will later result in a type test when `tp` is not a subtype of `b.info`) - // TODO: can we simplify this, together with the Bound case? - (extractor.subPatBinders, extractor.subPatTypes).zipped foreach { case (b, tp) => - debug.patmat("changing "+ b +" : "+ b.info +" -> "+ tp) - b setInfo tp - } + import extractor.paramType // the type expected by the unapply + + def patConforms = patType <:< paramType + def patEquiv = patType =:= paramType + + if (!extractor.isTyped) + ErrorUtils.issueNormalTypeError(patTree, "Could not typecheck extractor call: "+ extractor)(context) + + debug.patmat("translateExtractorPattern checking parameter type: " + ((patBinder, patType, paramType, patConforms))) // example check: List[Int] <:< ::[Int] // TODO: extractor.paramType may contain unbound type params (run/t2800, run/t3530) // `patBinderOrCasted` is assigned the result of casting `patBinder` to `extractor.paramType` val (typeTestTreeMaker, patBinderOrCasted, binderKnownNonNull) = - if (patBinder.info.widen <:< extractor.paramType) { + if (patConforms) { // no type test needed, but the tree maker relies on `patBinderOrCasted` having type `extractor.paramType` (and not just some type compatible with it) // SI-6624 shows this is necessary because apparently patBinder may have an unfortunate type (.decls don't have the case field accessors) // TODO: get to the bottom of this -- I assume it happens when type checking infers a weird type for an unapply call // by going back to the parameterType for the extractor call we get a saner type, so let's just do that for now - /* TODO: uncomment when `settings.developer` and `devWarning` become available - if (settings.developer.value && !(patBinder.info =:= extractor.paramType)) - devWarning(s"resetting info of $patBinder: ${patBinder.info} to ${extractor.paramType}") - */ - (Nil, patBinder setInfo extractor.paramType, false) - } else { + if (!patEquiv) + devWarning(s"resetting info of $patBinder: $patType to $paramType") + + (Nil, setVarInfo(patBinder, paramType), false) + } + else { // chain a type-testing extractor before the actual extractor call // it tests the type, checks the outer pointer and casts to the expected type // TODO: the outer check is mandated by the spec for case classes, but we do it for user-defined unapplies as well [SPEC] // (the prefix of the argument passed to the unapply must equal the prefix of the type of the binder) - val treeMaker = TypeTestTreeMaker(patBinder, patBinder, extractor.paramType, extractor.paramType)(pos, extractorArgTypeTest = true) + val treeMaker = TypeTestTreeMaker(patBinder, patBinder, paramType, paramType)(pos, extractorArgTypeTest = true) // check whether typetest implies patBinder is not null, // even though the eventual null check will be on patBinderOrCasted - // it'll be equal to patBinder casted to extractor.paramType anyway (and the type test is on patBinder) + // it'll be equal to patBinder casted to paramType anyway (and the type test is on patBinder) (List(treeMaker), treeMaker.nextBinder, treeMaker.impliesBinderNonNull(patBinder)) } @@ -413,6 +415,7 @@ trait MatchTranslation extends CpsPatternHacks { The type of r must conform to the expected type of the pattern. */ + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // helper methods: they analyze types and trees in isolation, but they are not (directly) concerned with the structure of the overall translation /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -444,14 +447,23 @@ trait MatchTranslation extends CpsPatternHacks { // `subPatBinders` are the variables bound by this pattern in the following patterns // subPatBinders are replaced by references to the relevant part of the extractor's result (tuple component, seq element, the result as-is) - lazy val subPatBinders = args map { - case Bound(b, p) => b - case p => freshSym(p.pos, prefix = "p") + // must set infos to `subPatTypes`, which are provided by extractor's result, + // as b.info may be based on a Typed type ascription, which has not been taken into account yet by the translation + // (it will later result in a type test when `tp` is not a subtype of `b.info`) + // TODO: can we simplify this, together with the Bound case? + lazy val subPatBinders = { + val binders = args map { case Bound(b, _) => b ; case p => freshSym(p.pos, prefix = "p") } + if (binders.length != subPatTypes.length) + devWarning(s"Mismatched binders and subpatterns: binders=$binders, subPatTypes=$subPatTypes") + + (binders, subPatTypes).zipped map setVarInfo } + lazy val subBindersAndPatterns: List[(Symbol, Tree)] = subPatBinders zip unboundArgs - lazy val subBindersAndPatterns: List[(Symbol, Tree)] = (subPatBinders zip args) map { - case (b, Bound(_, p)) => (b, p) - case bp => bp + private def unboundArgs = args map unBound + private def unBound(t: Tree): Tree = t match { + case Bound(_, p) => p + case _ => t } // never store these in local variables (for PreserveSubPatBinders) @@ -480,8 +492,8 @@ trait MatchTranslation extends CpsPatternHacks { protected def seqTree(binder: Symbol) = tupleSel(binder)(firstIndexingBinder+1) protected def tupleSel(binder: Symbol)(i: Int): Tree = codegen.tupleSel(binder)(i) - // the trees that select the subpatterns on the extractor's result, referenced by `binder` - // require isSeq + // the trees that select the subpatterns on the extractor's result, + // referenced by `binder` protected def subPatRefsSeq(binder: Symbol): List[Tree] = { val indexingIndices = (0 to (lastIndexingBinder-firstIndexingBinder)) val nbIndexingIndices = indexingIndices.length -- cgit v1.2.3 From db2e756e0e12a09f162266e7be13ec03a133a44c Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Sat, 17 Aug 2013 09:46:12 -0700 Subject: Stylistic cleanups in patmat. Should be behaviorally neutral. --- .../tools/nsc/transform/patmat/MatchCodeGen.scala | 10 ++--- .../nsc/transform/patmat/MatchTranslation.scala | 46 ++++++++++++---------- .../nsc/transform/patmat/MatchTreeMaking.scala | 12 +++++- .../nsc/transform/patmat/PatternMatching.scala | 5 ++- .../tools/nsc/typechecker/PatternTypers.scala | 24 +++++------ 5 files changed, 57 insertions(+), 40 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchCodeGen.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchCodeGen.scala index 8eab776f3d..77a6b3940c 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/MatchCodeGen.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchCodeGen.scala @@ -145,9 +145,8 @@ trait MatchCodeGen extends Interface { * if keepGoing is false, the result Some(x) of the naive translation is encoded as matchRes == x */ def matcher(scrut: Tree, scrutSym: Symbol, restpe: Type)(cases: List[Casegen => Tree], matchFailGen: Option[Tree => Tree]): Tree = { - val matchEnd = newSynthCaseLabel("matchEnd") val matchRes = NoSymbol.newValueParameter(newTermName("x"), NoPosition, newFlags = SYNTHETIC) setInfo restpe.withoutAnnotations - matchEnd setInfo MethodType(List(matchRes), restpe) + val matchEnd = newSynthCaseLabel("matchEnd") setInfo MethodType(List(matchRes), restpe) def newCaseSym = newSynthCaseLabel("case") setInfo MethodType(Nil, restpe) var _currCase = newCaseSym @@ -159,7 +158,6 @@ trait MatchCodeGen extends Interface { LabelDef(currCase, Nil, mkCase(new OptimizedCasegen(matchEnd, nextCase))) } - // must compute catchAll after caseLabels (side-effects nextCase) // catchAll.isEmpty iff no synthetic default case needed (the (last) user-defined case is a default) // if the last user-defined case is a default, it will never jump to the next case; it will go immediately to matchEnd @@ -173,9 +171,9 @@ trait MatchCodeGen extends Interface { val scrutDef = if(scrutSym ne NoSymbol) List(VAL(scrutSym) === scrut) else Nil // for alternatives // the generated block is taken apart in TailCalls under the following assumptions - // the assumption is once we encounter a case, the remainder of the block will consist of cases - // the prologue may be empty, usually it is the valdef that stores the scrut - // val (prologue, cases) = stats span (s => !s.isInstanceOf[LabelDef]) + // the assumption is once we encounter a case, the remainder of the block will consist of cases + // the prologue may be empty, usually it is the valdef that stores the scrut + // val (prologue, cases) = stats span (s => !s.isInstanceOf[LabelDef]) Block( scrutDef ++ caseDefs ++ catchAllDef, LabelDef(matchEnd, List(matchRes), REF(matchRes)) diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala index 4f89287bf5..c14b8919dd 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala @@ -538,23 +538,17 @@ trait MatchTranslation extends CpsPatternHacks { // no need to check unless it's an unapplySeq and the minimal length is non-trivially satisfied if (!isSeq || (expectedLength < minLenToCheck)) None else Some(expectedLength) - } // TODO: to be called when there's a def unapplyProd(x: T): U // U must have N members _1,..., _N -- the _i are type checked, call their type Ti, - // // for now only used for case classes -- pretending there's an unapplyProd that's the identity (and don't call it) class ExtractorCallProd(fun: Tree, args: List[Tree]) extends ExtractorCall(args) { // TODO: fix the illegal type bound in pos/t602 -- type inference messes up before we get here: /*override def equals(x$1: Any): Boolean = ... val o5: Option[com.mosol.sl.Span[Any]] = // Span[Any] --> Any is not a legal type argument for Span! */ - // private val orig = fun match {case tpt: TypeTree => tpt.original case _ => fun} - // private val origExtractorTp = unapplyMember(orig.symbol.filter(sym => reallyExists(unapplyMember(sym.tpe))).tpe).tpe - // private val extractorTp = if (wellKinded(fun.tpe)) fun.tpe else existentialAbstraction(origExtractorTp.typeParams, origExtractorTp.resultType) - // debug.patmat("ExtractorCallProd: "+ (fun.tpe, existentialAbstraction(origExtractorTp.typeParams, origExtractorTp.resultType))) - // debug.patmat("ExtractorCallProd: "+ (fun.tpe, args map (_.tpe))) + private def constructorTp = fun.tpe def isTyped = fun.isTyped @@ -562,7 +556,8 @@ trait MatchTranslation extends CpsPatternHacks { // to which type should the previous binder be casted? def paramType = constructorTp.finalResultType - def isSeq: Boolean = rawSubPatTypes.nonEmpty && isRepeatedParamType(rawSubPatTypes.last) + def isSeq = isVarArgTypes(rawSubPatTypes) + protected def rawSubPatTypes = constructorTp.paramTypes /** Create the TreeMaker that embodies this extractor call @@ -604,6 +599,7 @@ trait MatchTranslation extends CpsPatternHacks { def paramType = tpe.paramTypes.head def resultType = tpe.finalResultType def isSeq = extractorCall.symbol.name == nme.unapplySeq + def isBool = resultType =:= BooleanTpe /** Create the TreeMaker that embodies this extractor call * @@ -616,10 +612,22 @@ trait MatchTranslation extends CpsPatternHacks { * Perhaps it hasn't reached critical mass, but it would already clean things up a touch. */ def treeMaker(patBinderOrCasted: Symbol, binderKnownNonNull: Boolean, pos: Position): TreeMaker = { - // the extractor call (applied to the binder bound by the flatMap corresponding to the previous (i.e., enclosing/outer) pattern) + // the extractor call (applied to the binder bound by the flatMap corresponding + // to the previous (i.e., enclosing/outer) pattern) val extractorApply = atPos(pos)(spliceApply(patBinderOrCasted)) - val binder = freshSym(pos, pureType(resultInMonad)) // can't simplify this when subPatBinders.isEmpty, since UnitTpe is definitely wrong when isSeq, and resultInMonad should always be correct since it comes directly from the extractor's result type - ExtractorTreeMaker(extractorApply, lengthGuard(binder), binder)(subPatBinders, subPatRefs(binder), resultType.typeSymbol == BooleanClass, checkedLength, patBinderOrCasted, ignoredSubPatBinders) + // can't simplify this when subPatBinders.isEmpty, since UnitTpe is definitely + // wrong when isSeq, and resultInMonad should always be correct since it comes + // directly from the extractor's result type + val binder = freshSym(pos, pureType(resultInMonad)) + + ExtractorTreeMaker(extractorApply, lengthGuard(binder), binder)( + subPatBinders, + subPatRefs(binder), + isBool, + checkedLength, + patBinderOrCasted, + ignoredSubPatBinders + ) } override protected def seqTree(binder: Symbol): Tree = @@ -636,19 +644,17 @@ trait MatchTranslation extends CpsPatternHacks { object splice extends Transformer { override def transform(t: Tree) = t match { case Apply(x, List(i @ Ident(nme.SELECTOR_DUMMY))) => - treeCopy.Apply(t, x, List(CODE.REF(binder).setPos(i.pos))) - case _ => super.transform(t) + treeCopy.Apply(t, x, List(CODE.REF(binder) setPos i.pos)) + case _ => + super.transform(t) } } - splice.transform(extractorCallIncludingDummy) + splice transform extractorCallIncludingDummy } // what's the extractor's result type in the monad? // turn an extractor's result type into something `monadTypeToSubPatTypesAndRefs` understands - protected lazy val resultInMonad: Type = if(!hasLength(tpe.paramTypes, 1)) ErrorType else { - if (resultType.typeSymbol == BooleanClass) UnitTpe - else matchMonadResult(resultType) - } + protected lazy val resultInMonad: Type = if (isBool) UnitTpe else matchMonadResult(resultType) // the type of "get" protected lazy val rawSubPatTypes = if (resultInMonad.typeSymbol eq UnitClass) Nil @@ -670,7 +676,7 @@ trait MatchTranslation extends CpsPatternHacks { case Ident(nme.WILDCARD) => true case Star(WildcardPattern()) => true case x: Ident => treeInfo.isVarPattern(x) - case Alternative(ps) => ps forall (WildcardPattern.unapply(_)) + case Alternative(ps) => ps forall unapply case EmptyTree => true case _ => false } @@ -680,7 +686,7 @@ trait MatchTranslation extends CpsPatternHacks { def unapply(pat: Tree): Boolean = pat match { case Bind(nme.WILDCARD, _) => true // don't skip when binding an interesting symbol! case Ident(nme.WILDCARD) => true - case Alternative(ps) => ps forall (PatternBoundToUnderscore.unapply(_)) + case Alternative(ps) => ps forall unapply case Typed(PatternBoundToUnderscore(), _) => true case _ => false } diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala index baccdcf544..921c3ca1b5 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala @@ -201,6 +201,16 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging { def extraStoredBinders: Set[Symbol] = Set() + debug.patmat(s""" + |ExtractorTreeMaker($extractor, $extraCond, $nextBinder) { + | $subPatBinders + | $subPatRefs + | $extractorReturnsBoolean + | $checkedLength + | $prevBinder + | $ignoredSubPatBinders + |}""".stripMargin) + def chainBefore(next: Tree)(casegen: Casegen): Tree = { val condAndNext = extraCond match { case Some(cond) => @@ -426,7 +436,7 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging { case _ if testedBinder.info.widen <:< expectedTp => // if the expected type is a primitive value type, it cannot be null and it cannot have an outer pointer // since the types conform, no further checking is required - if (expectedTp.typeSymbol.isPrimitiveValueClass) tru + if (isPrimitiveValueType(expectedTp)) tru // have to test outer and non-null only when it's a reference type else if (expectedTp <:< AnyRefTpe) { // do non-null check first to ensure we won't select outer on null diff --git a/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala b/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala index b37b1d8550..aa923b1059 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala @@ -94,7 +94,7 @@ trait Debugging { // TODO: the inliner fails to inline the closures to debug.patmat unless the method is nested in an object object debug { val printPatmat = global.settings.Ypatmatdebug.value - @inline final def patmat(s: => String) = if (printPatmat) println(s) + @inline final def patmat(s: => String) = if (printPatmat) Console.err.println(s) @inline final def patmatResult[T](s: => String)(result: T): T = { if (printPatmat) Console.err.println(s + ": " + result) result @@ -103,7 +103,8 @@ trait Debugging { } trait Interface extends ast.TreeDSL { - import global.{newTermName, analyzer, Type, ErrorType, Symbol, Tree} + import global._ + import definitions._ import analyzer.Typer // 2.10/2.11 compatibility diff --git a/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala b/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala index 6b4098761a..9416fcb651 100644 --- a/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala @@ -313,23 +313,25 @@ trait PatternTypers { def wrapClassTagUnapply(uncheckedPattern: Tree, classTagExtractor: Tree, pt: Type): Tree = { // TODO: disable when in unchecked match - // we don't create a new Context for a Match, so find the CaseDef, then go out one level and navigate back to the match that has this case - // val thisCase = context.nextEnclosing(_.tree.isInstanceOf[CaseDef]) - // val unchecked = thisCase.outer.tree.collect{case Match(selector, cases) if cases contains thisCase => selector} match { - // case List(Typed(_, tpt)) if tpt.tpe hasAnnotation UncheckedClass => true - // case t => println("outer tree: "+ (t, thisCase, thisCase.outer.tree)); false - // } - // println("wrapClassTagUnapply"+ (!isPastTyper && infer.containsUnchecked(pt), pt, uncheckedPattern)) - // println("wrapClassTagUnapply: "+ extractor) - // println(util.Position.formatMessage(uncheckedPattern.pos, "made unchecked type test into a checked one", true)) - + // we don't create a new Context for a Match, so find the CaseDef, + // then go out one level and navigate back to the match that has this case val args = List(uncheckedPattern) val app = atPos(uncheckedPattern.pos)(Apply(classTagExtractor, args)) // must call doTypedUnapply directly, as otherwise we get undesirable rewrites // and re-typechecks of the target of the unapply call in PATTERNmode, // this breaks down when the classTagExtractor (which defineds the unapply member) is not a simple reference to an object, // but an arbitrary tree as is the case here - doTypedUnapply(app, classTagExtractor, classTagExtractor, args, PATTERNmode, pt) + val res = doTypedUnapply(app, classTagExtractor, classTagExtractor, args, PATTERNmode, pt) + + log(sm""" + |wrapClassTagUnapply { + | pattern: $uncheckedPattern + | extract: $classTagExtractor + | pt: $pt + | res: $res + |}""".trim) + + res } // if there's a ClassTag that allows us to turn the unchecked type test for `pt` into a checked type test -- cgit v1.2.3 From b895541396015e5e50749b3f2fdb7fc4ab230919 Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Sat, 17 Aug 2013 09:54:21 -0700 Subject: Introduced classes to encapsulate extractor info. To be put to use in a commit shortly to follow. --- .../tools/nsc/typechecker/PatternTypers.scala | 123 +++++++++++++++++++++ 1 file changed, 123 insertions(+) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala b/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala index 9416fcb651..990de0ca1f 100644 --- a/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala @@ -121,6 +121,129 @@ trait PatternTypers { treeTyped setType tptTyped.tpe } + case class CaseClassInfo(clazz: Symbol, classType: Type) { + def constructor = clazz.primaryConstructor + def constructorType = classType.prefix memberType clazz memberType constructor + def paramTypes = constructorType.paramTypes + def accessors = clazz.caseFieldAccessors + def accessorTypes = accessors map (m => (classType memberType m).finalResultType) + // def inverted = MethodType(clazz :: Nil, tupleType(accessorTypes)) + } + object NoCaseClassInfo extends CaseClassInfo(NoSymbol, NoType) { + override def toString = "NoCaseClassInfo" + } + + case class UnapplyMethodInfo(unapply: Symbol, tpe: Type) { + def name = unapply.name + def isUnapplySeq = name == nme.unapplySeq + def unapplyType = tpe memberType method + def resultType = tpe.finalResultType + def method = unapplyMember(tpe) + def paramType = firstParamType(unapplyType) + def rawGet = if (isBool) UnitTpe else resultOfMatchingMethod(resultType, "get")() + def rawTypes = if (isBool) Nil else if (rawProduct.isEmpty) rawGet :: Nil else rawProduct + def rawArity = rawTypes.size + def isBool = resultType =:= BooleanTpe // aka "Tuple0" or "Option[Unit]" + def isCase = method.isCase + + private def rawProduct = getNameBasedProductSelectorTypes(rawGet) + } + + object NoUnapplyMethodInfo extends UnapplyMethodInfo(NoSymbol, NoType) { + override def toString = "NoUnapplyMethodInfo" + } + + case class ExtractorShape(fun: Tree, args: List[Tree]) { + def pos = fun.pos + private def symbol = fun.symbol + private def tpe = fun.tpe + + val ccInfo = tpe.typeSymbol.linkedClassOfClass match { + case clazz if clazz.isCase => CaseClassInfo(clazz, tpe) + case _ => NoCaseClassInfo + } + val exInfo = UnapplyMethodInfo(symbol, tpe) + import exInfo.{ rawTypes, isUnapplySeq, rawGet } + + override def toString = s"ExtractorShape($fun, $args)" + + def unapplyMethod = exInfo.method + def unapplyType = exInfo.unapplyType + def unapplyParamType = exInfo.paramType + def caseClass = ccInfo.clazz + def enclClass = symbol.enclClass + + def formals = ( + if (isUnapplySeq) productTypes :+ varargsType + else if (elementArity == 0) productTypes + else if (patternFixedArity == 1) squishIntoOne() + else wrongArity(patternFixedArity) + ) + + def rawLast = if (rawTypes.isEmpty) rawGet else rawTypes.last + def elementType = unapplySeqElementType(rawLast) + + private def hasBogusExtractor = directUnapplyMember(tpe).exists && !unapplyMethod.exists + private def expectedArity = "" + productArity + ( if (isUnapplySeq) "+" else "") + private def wrongArityMsg(n: Int) = ( + if (hasBogusExtractor) s"$enclClass does not define a valid extractor method" + else s"wrong number of patterns for $enclClass offering $rawTypes_s: expected $expectedArity, found $n" + ) + private def rawTypes_s = rawTypes match { + case Nil => "()" + case tp :: Nil => "" + tp + case tps => tps.mkString("(", ", ", ")") + } + + private def err(msg: String) = { unit.error(pos, msg) ; throw new TypeError(msg) } + private def wrongArity(n: Int) = err(wrongArityMsg(n)) + + def squishIntoOne() = { + if (settings.lint) + unit.warning(pos, s"$enclClass expects $expectedArity patterns to hold $rawGet but crushing into $productArity-tuple to fit single pattern (SI-6675)") + + rawGet :: Nil + } + def patternFixedArity = treeInfo effectivePatternArity args + def productArity = productTypes.size + def elementArity = patternFixedArity - productArity + + def productTypes = if (isUnapplySeq) rawTypes dropRight 1 else rawTypes + def elementTypes = List.fill(elementArity)(elementType) + def varargsType = scalaRepeatedType(elementType) + + def expectedPatternTypes = elementArity match { + case 0 => productTypes + case _ if elementArity > 0 && exInfo.isUnapplySeq => productTypes ::: elementTypes + case _ if productArity > 1 && patternFixedArity == 1 => squishIntoOne() + case _ => wrongArity(patternFixedArity) + } + } + + private class VariantToSkolemMap extends TypeMap(trackVariance = true) { + private val skolemBuffer = mutable.ListBuffer[TypeSymbol]() + + def skolems = try skolemBuffer.toList finally skolemBuffer.clear() + def apply(tp: Type): Type = mapOver(tp) match { + // !!! FIXME - skipping this when variance.isInvariant allows unsoundness, see SI-5189 + case tp @ TypeRef(NoPrefix, tpSym, Nil) if tpSym.isTypeParameterOrSkolem && tpSym.owner.isTerm => + if (variance.isInvariant) { + // if (variance.isInvariant) tpSym.tpeHK.bounds + devWarning(s"variantToSkolem skipping rewrite of $tpSym due to invariance") + return tp + } + val bounds = ( + if (variance.isPositive) TypeBounds.upper(tpSym.tpeHK) + else TypeBounds.lower(tpSym.tpeHK) + ) + // origin must be the type param so we can deskolemize + val skolem = context.owner.newGADTSkolem(unit.freshTypeName("?"+tpSym.name), tpSym, bounds) + skolemBuffer += skolem + skolem.tpe_* + case tp1 => tp1 + } + } + /* * To deal with the type slack between actual (run-time) types and statically known types, for each abstract type T, * reflect its variance as a skolem that is upper-bounded by T (covariant position), or lower-bounded by T (contravariant). -- cgit v1.2.3 From 8f05647ca53da781b420be0723faf1cdbf14b2ff Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Sat, 17 Aug 2013 09:58:14 -0700 Subject: Pattern matcher: extractors become name-based. An extractor is no longer required to return Option[T], and can instead return anything which directly contains methods with these signatures: def isEmpty: Boolean def get: T If the type of get contains methods with the names of product selectors (_1, _2, etc.) then the type and arity of the extraction is inferred from the type of get. If it does not contain _1, then it is a single value extractor analogous like Option[T]. This has significant benefits and opens new territory: - an AnyVal based Option-like class can be used which leverages null as None, and no allocations are necessary - for primitive types the benefit is squared (see below) - the performance difference between case classes and extractors should now be largely eliminated - this in turn allows us to recapture great swaths of memory which are currently squandered (e.g. every TypeRef has fields for pre and args, even though these are more than half the time NoPrefix and Nil) Here is a primitive example: final class OptInt(val x: Int) extends AnyVal { def get: Int = x def isEmpty = x == Int.MinValue // or whatever is appropriate } // This boxes TWICE: Int => Integer => Some(Integer) def unapply(x: Int): Option[Int] // This boxes NONCE def unapply(x: Int): OptInt As a multi-value example, after I contribute some methods to TypeRef: def isEmpty = false def get = this def _1 = pre def _2 = sym def _3 = args Then it's extractor becomes def unapply(x: TypeRef) = x Which, it need hardly be said, involves no allocations. --- .../scala/tools/nsc/transform/UnCurry.scala | 2 +- .../tools/nsc/transform/patmat/MatchCodeGen.scala | 30 +- .../nsc/transform/patmat/MatchOptimization.scala | 2 +- .../nsc/transform/patmat/MatchTranslation.scala | 164 ++++---- .../nsc/transform/patmat/PatternMatching.scala | 46 ++- .../scala/tools/nsc/typechecker/Infer.scala | 90 ----- .../tools/nsc/typechecker/PatternTypers.scala | 418 ++++++++++----------- .../scala/tools/nsc/typechecker/Typers.scala | 6 +- .../scala/tools/nsc/typechecker/Unapplies.scala | 22 -- test/files/neg/t4425.check | 10 +- test/files/neg/t4425.scala | 10 + test/files/neg/t4425b.check | 36 +- test/files/neg/t6675.check | 2 +- test/files/neg/t997.check | 7 +- .../pos/annotated-treecopy/Impls_Macros_1.scala | 2 +- test/files/run/matchonseq.scala | 10 +- test/files/run/t7214.scala | 2 +- 17 files changed, 400 insertions(+), 459 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala index ca123f8782..16c803e2e8 100644 --- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala +++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala @@ -460,7 +460,7 @@ abstract class UnCurry extends InfoTransform val fn1 = transform(fn) val args1 = transformTrees(fn.symbol.name match { case nme.unapply => args - case nme.unapplySeq => transformArgs(tree.pos, fn.symbol, args, analyzer.unapplyTypeList(fn.pos, fn.symbol, fn.tpe, args)) + case nme.unapplySeq => transformArgs(tree.pos, fn.symbol, args, localTyper.expectedPatternTypes(fn, args)) case _ => sys.error("internal error: UnApply node has wrong symbol") }) treeCopy.UnApply(tree, fn1, args1) diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchCodeGen.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchCodeGen.scala index 77a6b3940c..52055dea85 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/MatchCodeGen.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchCodeGen.scala @@ -83,15 +83,14 @@ trait MatchCodeGen extends Interface { trait PureMatchMonadInterface extends MatchMonadInterface { val matchStrategy: Tree - - def inMatchMonad(tp: Type): Type = appliedType(oneSig, List(tp)).finalResultType - def pureType(tp: Type): Type = appliedType(oneSig, List(tp)).paramTypes.headOption getOrElse NoType // fail gracefully (otherwise we get crashes) - protected def matchMonadSym = oneSig.finalResultType.typeSymbol - import CODE._ def _match(n: Name): SelectStart = matchStrategy DOT n - private lazy val oneSig: Type = typer.typedOperator(_match(vpmName.one)).tpe // TODO: error message + // TODO: error message + private lazy val oneType = typer.typedOperator(_match(vpmName.one)).tpe + private def oneApplied(tp: Type): Type = appliedType(oneType, tp :: Nil) + override def pureType(tp: Type): Type = firstParamType(oneApplied(tp)) + override def mapResultType(prev: Type, elem: Type): Type = oneApplied(elem).finalResultType } trait PureCodegen extends CodegenCore with PureMatchMonadInterface { @@ -123,13 +122,7 @@ trait MatchCodeGen extends Interface { } } - trait OptimizedMatchMonadInterface extends MatchMonadInterface { - override def inMatchMonad(tp: Type): Type = optionType(tp) - override def pureType(tp: Type): Type = tp - override protected def matchMonadSym = OptionClass - } - - trait OptimizedCodegen extends CodegenCore with TypedSubstitution with OptimizedMatchMonadInterface { + trait OptimizedCodegen extends CodegenCore with TypedSubstitution with MatchMonadInterface { override def codegen: AbsCodegen = optimizedCodegen // when we know we're targetting Option, do some inlining the optimizer won't do @@ -195,15 +188,14 @@ trait MatchCodeGen extends Interface { // next: MatchMonad[U] // returns MatchMonad[U] def flatMap(prev: Tree, b: Symbol, next: Tree): Tree = { - val tp = inMatchMonad(b.tpe) - val prevSym = freshSym(prev.pos, tp, "o") - val isEmpty = tp member vpmName.isEmpty - val get = tp member vpmName.get - + val prevSym = freshSym(prev.pos, prev.tpe, "o") BLOCK( VAL(prevSym) === prev, // must be isEmpty and get as we don't control the target of the call (prev is an extractor call) - ifThenElseZero(NOT(prevSym DOT isEmpty), Substitution(b, prevSym DOT get)(next)) + ifThenElseZero( + NOT(prevSym DOT vpmName.isEmpty), + Substitution(b, prevSym DOT vpmName.get)(next) + ) ) } diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchOptimization.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchOptimization.scala index 9854e4ef62..ec45789687 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/MatchOptimization.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchOptimization.scala @@ -210,7 +210,7 @@ trait MatchOptimization extends MatchTreeMaking with MatchAnalysis { // } //// SWITCHES -- TODO: operate on Tests rather than TreeMakers - trait SwitchEmission extends TreeMakers with OptimizedMatchMonadInterface { + trait SwitchEmission extends TreeMakers with MatchMonadInterface { import treeInfo.isGuardedCase abstract class SwitchMaker { diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala index c14b8919dd..5ddcd3528b 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala @@ -347,9 +347,8 @@ trait MatchTranslation extends CpsPatternHacks { // don't fail here though (or should we?) val translationStep = patTree match { case WildcardPattern() => none() - case UnApply(unfun, args) => translateExtractorPattern(ExtractorCall(unfun, args)) - case Apply(fun, args) => ExtractorCall.fromCaseClass(fun, args) map translateExtractorPattern getOrElse noFurtherSubPats() - case MaybeBoundTyped(subPatBinder, pt) => one(TypeTestTreeMaker(subPatBinder, patBinder, pt, glb(List(dealiasWiden(patBinder.info), pt)).normalize)(pos)) + case _: UnApply | _: Apply => translateExtractorPattern(ExtractorCall(patTree)) + case MaybeBoundTyped(subPatBinder, pt) => one(TypeTestTreeMaker(subPatBinder, patBinder, pt, glbWithBinder(pt))(pos)) case Bound(subpatBinder, p) => withSubPats(List(SubstOnlyTreeMaker(subpatBinder, patBinder)), (patBinder, p)) case Literal(Constant(_)) | Ident(_) | Select(_, _) | This(_) => one(EqualityTestTreeMaker(patBinder, patTree, pos)) case Alternative(alts) => one(AlternativesTreeMaker(patBinder, alts map (translatePattern(patBinder, _)), alts.head.pos)) @@ -421,22 +420,37 @@ trait MatchTranslation extends CpsPatternHacks { /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// object ExtractorCall { - def apply(unfun: Tree, args: List[Tree]): ExtractorCall = new ExtractorCallRegular(unfun, args) - def fromCaseClass(fun: Tree, args: List[Tree]): Option[ExtractorCall] = Some(new ExtractorCallProd(fun, args)) + // TODO: check unargs == args + def apply(tree: Tree): ExtractorCall = tree match { + case UnApply(unfun, args) => new ExtractorCallRegular(unfun, args) // extractor + case Apply(fun, args) => new ExtractorCallProd(fun, args) // case class + } } - abstract class ExtractorCall(val args: List[Tree]) { - val nbSubPats = args.length + abstract class ExtractorCall { + import CODE._ - // everything okay, captain? - def isTyped : Boolean + def fun: Tree + def args: List[Tree] + + val nbSubPats = args.length + val starLength = if (hasStar) 1 else 0 + val nonStarLength = args.length - starLength + // everything okay, captain? + def isTyped: Boolean def isSeq: Boolean - lazy val lastIsStar = (nbSubPats > 0) && treeInfo.isStar(args.last) + + private def hasStar = nbSubPats > 0 && isStar(args.last) + private def isNonEmptySeq = nbSubPats > 0 && isSeq + + def isSingle = nbSubPats == 0 && !isSeq // to which type should the previous binder be casted? def paramType : Type + protected def rawSubPatTypes: List[Type] + /** Create the TreeMaker that embodies this extractor call * * `binder` has been casted to `paramType` if necessary @@ -467,68 +481,83 @@ trait MatchTranslation extends CpsPatternHacks { } // never store these in local variables (for PreserveSubPatBinders) - lazy val ignoredSubPatBinders = (subPatBinders zip args).collect{ - case (b, PatternBoundToUnderscore()) => b - }.toSet - - def subPatTypes: List[Type] = - if(isSeq) { - val TypeRef(pre, SeqClass, args) = seqTp - // do repeated-parameter expansion to match up with the expected number of arguments (in casu, subpatterns) - val formalsWithRepeated = rawSubPatTypes.init :+ typeRef(pre, RepeatedParamClass, args) - - if (lastIsStar) formalTypes(formalsWithRepeated, nbSubPats - 1) :+ seqTp - else formalTypes(formalsWithRepeated, nbSubPats) - } else rawSubPatTypes - - protected def rawSubPatTypes: List[Type] - - protected def seqTp = rawSubPatTypes.last baseType SeqClass - protected def seqLenCmp = rawSubPatTypes.last member nme.lengthCompare - protected lazy val firstIndexingBinder = rawSubPatTypes.length - 1 // rawSubPatTypes.last is the Seq, thus there are `rawSubPatTypes.length - 1` non-seq elements in the tuple - protected lazy val lastIndexingBinder = if(lastIsStar) nbSubPats-2 else nbSubPats-1 - protected lazy val expectedLength = lastIndexingBinder - firstIndexingBinder + 1 - protected lazy val minLenToCheck = if(lastIsStar) 1 else 0 - protected def seqTree(binder: Symbol) = tupleSel(binder)(firstIndexingBinder+1) + lazy val ignoredSubPatBinders: Set[Symbol] = subPatBinders zip args collect { case (b, PatternBoundToUnderscore()) => b } toSet + + // do repeated-parameter expansion to match up with the expected number of arguments (in casu, subpatterns) + private def nonStarSubPatTypes = formalTypes(rawInit :+ repeatedType, nonStarLength) + + def subPatTypes: List[Type] = ( + if (rawSubPatTypes.isEmpty || !isSeq) rawSubPatTypes + else if (hasStar) nonStarSubPatTypes :+ rawLast + else nonStarSubPatTypes + ) + + private def emptySub = rawSubPatTypes.isEmpty + private def rawLast = if (emptySub) NothingTpe else rawSubPatTypes.last + private def rawInit = rawSubPatTypes dropRight 1 + protected def sequenceType = if (emptySub) NothingTpe else rawLast + protected def elementType = if (emptySub) NothingTpe else unapplySeqElementType(rawLast) + protected def repeatedType = if (emptySub) NothingTpe else scalaRepeatedType(elementType) + + // rawSubPatTypes.last is the Seq, thus there are `rawSubPatTypes.length - 1` non-seq elements in the tuple + protected def firstIndexingBinder = rawSubPatTypes.length - 1 + protected def lastIndexingBinder = nbSubPats - 1 - starLength + protected def expectedLength = lastIndexingBinder - firstIndexingBinder + 1 + + private def productElemsToN(binder: Symbol, n: Int): List[Tree] = 1 to n map tupleSel(binder) toList + private def genTake(binder: Symbol, n: Int): List[Tree] = (0 until n).toList map (codegen index seqTree(binder)) + private def genDrop(binder: Symbol, n: Int): List[Tree] = codegen.drop(seqTree(binder))(expectedLength) :: Nil + + // codegen.drop(seqTree(binder))(nbIndexingIndices)))).toList + protected def seqTree(binder: Symbol) = tupleSel(binder)(firstIndexingBinder + 1) protected def tupleSel(binder: Symbol)(i: Int): Tree = codegen.tupleSel(binder)(i) // the trees that select the subpatterns on the extractor's result, // referenced by `binder` protected def subPatRefsSeq(binder: Symbol): List[Tree] = { - val indexingIndices = (0 to (lastIndexingBinder-firstIndexingBinder)) - val nbIndexingIndices = indexingIndices.length - + def lastTrees: List[Tree] = ( + if (!hasStar) Nil + else if (expectedLength == 0) seqTree(binder) :: Nil + else genDrop(binder, expectedLength) + ) // this error-condition has already been checked by checkStarPatOK: // if(isSeq) assert(firstIndexingBinder + nbIndexingIndices + (if(lastIsStar) 1 else 0) == nbSubPats, "(resultInMonad, ts, subPatTypes, subPats)= "+(resultInMonad, ts, subPatTypes, subPats)) - // there are `firstIndexingBinder` non-seq tuple elements preceding the Seq - (((1 to firstIndexingBinder) map tupleSel(binder)) ++ - // then we have to index the binder that represents the sequence for the remaining subpatterns, except for... - (indexingIndices map codegen.index(seqTree(binder))) ++ - // the last one -- if the last subpattern is a sequence wildcard: drop the prefix (indexed by the refs on the line above), return the remainder - (if(!lastIsStar) Nil else List( - if(nbIndexingIndices == 0) seqTree(binder) - else codegen.drop(seqTree(binder))(nbIndexingIndices)))).toList + + // [1] there are `firstIndexingBinder` non-seq tuple elements preceding the Seq + // [2] then we have to index the binder that represents the sequence for the remaining subpatterns, except for... + // [3] the last one -- if the last subpattern is a sequence wildcard: + // drop the prefix (indexed by the refs on the preceding line), return the remainder + ( productElemsToN(binder, firstIndexingBinder) + ++ genTake(binder, expectedLength) + ++ lastTrees + ).toList } // the trees that select the subpatterns on the extractor's result, referenced by `binder` // require (nbSubPats > 0 && (!lastIsStar || isSeq)) protected def subPatRefs(binder: Symbol): List[Tree] = - if (nbSubPats == 0) Nil - else if (isSeq) subPatRefsSeq(binder) - else ((1 to nbSubPats) map tupleSel(binder)).toList + if (isNonEmptySeq) subPatRefsSeq(binder) else productElemsToN(binder, nbSubPats) + + private def compareInts(t1: Tree, t2: Tree) = + gen.mkMethodCall(termMember(ScalaPackage, "math"), TermName("signum"), Nil, (t1 INT_- t2) :: Nil) protected def lengthGuard(binder: Symbol): Option[Tree] = // no need to check unless it's an unapplySeq and the minimal length is non-trivially satisfied - checkedLength map { expectedLength => import CODE._ + checkedLength map { expectedLength => // `binder.lengthCompare(expectedLength)` - def checkExpectedLength = (seqTree(binder) DOT seqLenCmp)(LIT(expectedLength)) + // ...if binder has a lengthCompare method, otherwise + // `scala.math.signum(binder.length - expectedLength)` + def checkExpectedLength = sequenceType member nme.lengthCompare match { + case NoSymbol => compareInts(Select(seqTree(binder), nme.length), LIT(expectedLength)) + case lencmp => (seqTree(binder) DOT lencmp)(LIT(expectedLength)) + } // the comparison to perform // when the last subpattern is a wildcard-star the expectedLength is but a lower bound // (otherwise equality is required) def compareOp: (Tree, Tree) => Tree = - if (lastIsStar) _ INT_>= _ - else _ INT_== _ + if (hasStar) _ INT_>= _ + else _ INT_== _ // `if (binder != null && $checkExpectedLength [== | >=] 0) then else zero` (seqTree(binder) ANY_!= NULL) AND compareOp(checkExpectedLength, ZERO) @@ -536,14 +565,14 @@ trait MatchTranslation extends CpsPatternHacks { def checkedLength: Option[Int] = // no need to check unless it's an unapplySeq and the minimal length is non-trivially satisfied - if (!isSeq || (expectedLength < minLenToCheck)) None + if (!isSeq || expectedLength < starLength) None else Some(expectedLength) } // TODO: to be called when there's a def unapplyProd(x: T): U // U must have N members _1,..., _N -- the _i are type checked, call their type Ti, // for now only used for case classes -- pretending there's an unapplyProd that's the identity (and don't call it) - class ExtractorCallProd(fun: Tree, args: List[Tree]) extends ExtractorCall(args) { + class ExtractorCallProd(val fun: Tree, val args: List[Tree]) extends ExtractorCall { // TODO: fix the illegal type bound in pos/t602 -- type inference messes up before we get here: /*override def equals(x$1: Any): Boolean = ... val o5: Option[com.mosol.sl.Span[Any]] = // Span[Any] --> Any is not a legal type argument for Span! @@ -588,17 +617,17 @@ trait MatchTranslation extends CpsPatternHacks { else codegen.tupleSel(binder)(i) // this won't type check for case classes, as they do not inherit ProductN } - override def toString(): String = "case class "+ (if (constructorTp eq null) fun else paramType.typeSymbol) +" with arguments "+ args + override def toString() = s"ExtractorCallProd($fun:${fun.tpe} / ${fun.symbol} / args=$args)" } - class ExtractorCallRegular(extractorCallIncludingDummy: Tree, args: List[Tree]) extends ExtractorCall(args) { - private lazy val Some(Apply(extractorCall, _)) = extractorCallIncludingDummy.find{ case Apply(_, List(Ident(nme.SELECTOR_DUMMY))) => true case _ => false } + class ExtractorCallRegular(extractorCallIncludingDummy: Tree, val args: List[Tree]) extends ExtractorCall { + val Unapplied(fun) = extractorCallIncludingDummy - def tpe = extractorCall.tpe - def isTyped = (tpe ne NoType) && extractorCall.isTyped && (resultInMonad ne ErrorType) - def paramType = tpe.paramTypes.head - def resultType = tpe.finalResultType - def isSeq = extractorCall.symbol.name == nme.unapplySeq + def tpe = fun.tpe + def paramType = firstParamType(tpe) + def resultType = fun.tpe.finalResultType + def isTyped = (tpe ne NoType) && fun.isTyped && (resultInMonad ne ErrorType) + def isSeq = fun.symbol.name == nme.unapplySeq def isBool = resultType =:= BooleanTpe /** Create the TreeMaker that embodies this extractor call @@ -656,15 +685,16 @@ trait MatchTranslation extends CpsPatternHacks { // turn an extractor's result type into something `monadTypeToSubPatTypesAndRefs` understands protected lazy val resultInMonad: Type = if (isBool) UnitTpe else matchMonadResult(resultType) // the type of "get" - protected lazy val rawSubPatTypes = - if (resultInMonad.typeSymbol eq UnitClass) Nil - else if(!isSeq && nbSubPats == 1) List(resultInMonad) - else getProductArgs(resultInMonad) match { - case Nil => List(resultInMonad) + protected lazy val rawSubPatTypes = ( + if (isBool) Nil + else if (!isSeq && nbSubPats == 1) resultInMonad :: Nil + else getNameBasedProductSelectorTypes(resultInMonad) match { + case Nil => resultInMonad :: Nil case x => x } + ) - override def toString() = extractorCall +": "+ extractorCall.tpe +" (symbol= "+ extractorCall.symbol +")." + override def toString() = s"ExtractorCallRegular($fun:${fun.tpe} / ${fun.symbol})" } /** A conservative approximation of which patterns do not discern anything. diff --git a/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala b/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala index aa923b1059..616abaaf3b 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala @@ -171,6 +171,42 @@ trait Interface extends ast.TreeDSL { trait MatchMonadInterface { val typer: Typer val matchOwner = typer.context.owner + def pureType(tp: Type): Type = tp + + // Extracting from the monad: tp == Option[T], result == T + def matchMonadResult(tp: Type) = definitions typeOfMemberNamedGet tp + + // prev == CC[T] + // elem == U + // result == CC[U] + // where "CC" here is Option or any other single-type-parameter container + // + // TODO - what if it has multiple type parameters? + // If we have access to the zero, maybe we can infer the + // type parameter by contrasting with the zero's application. + def mapResultType(prev: Type, elem: Type): Type = { + // default to Option[U] if we can't reliably infer the types + def fallback(elem: Type): Type = elem match { + case TypeRef(_, sym, _) if sym.isTypeParameterOrSkolem => fallback(sym.info.bounds.hi) + case _ => optionType(elem) + } + + // optionType(elem) //pack(elem)) + // The type of "get" in CC[T] is what settles what was wrapped. + val prevElem = matchMonadResult(prev) + if (prevElem =:= elem) prev + else prev.typeArgs match { + case targ :: Nil if targ =:= prevElem => + // the type of "get" in the result should be elem. + // If not, the type arguments are doing something nonobvious + // so fall back on Option. + val result = appliedType(prev.typeConstructor, elem :: Nil) + val newElem = matchMonadResult(result) + if (elem =:= newElem) result else fallback(newElem) + case _ => + fallback(AnyTpe) + } + } def reportUnreachable(pos: Position) = typer.context.unit.warning(pos, "unreachable code") def reportMissingCases(pos: Position, counterExamples: List[String]) = { @@ -180,16 +216,6 @@ trait Interface extends ast.TreeDSL { typer.context.unit.warning(pos, "match may not be exhaustive.\nIt would fail on the following "+ ceString) } - - def inMatchMonad(tp: Type): Type - def pureType(tp: Type): Type - final def matchMonadResult(tp: Type): Type = - tp.baseType(matchMonadSym).typeArgs match { - case arg :: Nil => arg - case _ => ErrorType - } - - protected def matchMonadSym: Symbol } diff --git a/src/compiler/scala/tools/nsc/typechecker/Infer.scala b/src/compiler/scala/tools/nsc/typechecker/Infer.scala index a7c43361fa..b199176d90 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Infer.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Infer.scala @@ -76,96 +76,6 @@ trait Infer extends Checkable { override def complete(sym: Symbol) = () } - /** Returns `(formals, formalsExpanded)` where `formalsExpanded` are the expected types - * for the `nbSubPats` sub-patterns of an extractor pattern, of which the corresponding - * unapply[Seq] call is assumed to have result type `resTp`. - * - * `formals` are the formal types before expanding a potential repeated parameter (must come last in `formals`, if at all) - * - * @param nbSubPats The number of arguments to the extractor pattern - * @param effectiveNbSubPats `nbSubPats`, unless there is one sub-pattern which, after unwrapping - * bind patterns, is a Tuple pattern, in which case it is the number of - * elements. Used to issue warnings about binding a `TupleN` to a single value. - * @throws TypeError when the unapply[Seq] definition is ill-typed - * @returns (null, null) when the expected number of sub-patterns cannot be satisfied by the given extractor - * - * This is the spec currently implemented -- TODO: update it. - * - * 8.1.8 ExtractorPatterns - * - * An extractor pattern x(p1, ..., pn) where n ≥ 0 is of the same syntactic form as a constructor pattern. - * However, instead of a case class, the stable identifier x denotes an object which has a member method named unapply or unapplySeq that matches the pattern. - * - * An `unapply` method with result type `R` in an object `x` matches the - * pattern `x(p_1, ..., p_n)` if it takes exactly one argument and, either: - * - `n = 0` and `R =:= Boolean`, or - * - `n = 1` and `R <:< Option[T]`, for some type `T`. - * The argument pattern `p1` is typed in turn with expected type `T`. - * - Or, `n > 1` and `R <:< Option[Product_n[T_1, ..., T_n]]`, for some - * types `T_1, ..., T_n`. The argument patterns `p_1, ..., p_n` are - * typed with expected types `T_1, ..., T_n`. - * - * An `unapplySeq` method in an object `x` matches the pattern `x(p_1, ..., p_n)` - * if it takes exactly one argument and its result type is of the form `Option[S]`, - * where either: - * - `S` is a subtype of `Seq[U]` for some element type `U`, (set `m = 0`) - * - or `S` is a `ProductX[T_1, ..., T_m]` and `T_m <: Seq[U]` (`m <= n`). - * - * The argument patterns `p_1, ..., p_n` are typed with expected types - * `T_1, ..., T_m, U, ..., U`. Here, `U` is repeated `n-m` times. - * - */ - def extractorFormalTypes(pos: Position, resTp: Type, nbSubPats: Int, - unappSym: Symbol, effectiveNbSubPats: Int): (List[Type], List[Type]) = { - val isUnapplySeq = unappSym.name == nme.unapplySeq - val booleanExtractor = resTp.typeSymbolDirect == BooleanClass - - def seqToRepeatedChecked(tp: Type) = { - val toRepeated = seqToRepeated(tp) - if (tp eq toRepeated) throw new TypeError("(the last tuple-component of) the result type of an unapplySeq must be a Seq[_]") - else toRepeated - } - - // empty list --> error, otherwise length == 1 - lazy val optionArgs = resTp.baseType(OptionClass).typeArgs - // empty list --> not a ProductN, otherwise product element types - def productArgs = getProductArgs(optionArgs.head) - - val formals = - // convert Seq[T] to the special repeated argument type - // so below we can use formalTypes to expand formals to correspond to the number of actuals - if (isUnapplySeq) { - if (optionArgs.nonEmpty) - productArgs match { - case Nil => List(seqToRepeatedChecked(optionArgs.head)) - case normalTps :+ seqTp => normalTps :+ seqToRepeatedChecked(seqTp) - } - else throw new TypeError(s"result type $resTp of unapplySeq defined in ${unappSym.fullLocationString} does not conform to Option[_]") - } else { - if (booleanExtractor && nbSubPats == 0) Nil - else if (optionArgs.nonEmpty) - if (nbSubPats == 1) { - val productArity = productArgs.size - if (productArity > 1 && productArity != effectiveNbSubPats && settings.lint) - global.currentUnit.warning(pos, - s"extractor pattern binds a single value to a Product${productArity} of type ${optionArgs.head}") - optionArgs - } - // TODO: update spec to reflect we allow any ProductN, not just TupleN - else productArgs - else - throw new TypeError(s"result type $resTp of unapply defined in ${unappSym.fullLocationString} does not conform to Option[_] or Boolean") - } - - // for unapplySeq, replace last vararg by as many instances as required by nbSubPats - val formalsExpanded = - if (isUnapplySeq && formals.nonEmpty) formalTypes(formals, nbSubPats) - else formals - - if (formalsExpanded.lengthCompare(nbSubPats) != 0) (null, null) - else (formals, formalsExpanded) - } - /** A fresh type variable with given type parameter as origin. */ def freshVar(tparam: Symbol): TypeVar = TypeVar(tparam) diff --git a/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala b/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala index 990de0ca1f..13926ca18b 100644 --- a/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala @@ -38,6 +38,13 @@ trait PatternTypers { import global._ import definitions._ + private object FixedAndRepeatedTypes { + def unapply(types: List[Type]) = types match { + case init :+ last if isRepeatedParamType(last) => Some((init, dropRepeated(last))) + case _ => Some((types, NoType)) + } + } + // when true: // - we may virtualize matches (if -Xexperimental and there's a suitable __match in scope) // - we synthesize PartialFunction implementations for `x => x match {...}` and `match {...}` when the expected type is PartialFunction @@ -52,75 +59,118 @@ trait PatternTypers { private def unit = context.unit - /** Type trees in `args0` against corresponding expected type in `adapted0`. - * - * The mode in which each argument is typed is derived from `mode` and - * whether the arg was originally by-name or var-arg (need `formals0` for that) - * the default is by-val, of course. - * - * (docs reverse-engineered -- AM) - */ - def typedArgs(args0: List[Tree], mode: Mode, formals0: List[Type], adapted0: List[Type]): List[Tree] = { - def loop(args: List[Tree], formals: List[Type], adapted: List[Type]): List[Tree] = { - if (args.isEmpty || adapted.isEmpty) Nil - else { - // No formals left or * indicates varargs. - val isVarArgs = formals.isEmpty || formals.tail.isEmpty && isRepeatedParamType(formals.head) - val isByName = formals.nonEmpty && isByNameParamType(formals.head) - def typedMode = if (isByName) mode.onlySticky else mode.onlySticky | BYVALmode - def body = typedArg(args.head, mode, typedMode, adapted.head) - def arg1 = if (isVarArgs) context.withinStarPatterns(body) else body - - // formals may be empty, so don't call tail - arg1 :: loop(args.tail, formals drop 1, adapted.tail) - } + // If the tree's symbol's type does not define an extractor, maybe the tree's type does. + // this is the case when we encounter an arbitrary tree as the target of an unapply call + // (rather than something that looks like a constructor call.) (for now, this only happens + // due to wrapClassTagUnapply, but when we support parameterized extractors, it will become + // more common place) + private def hasUnapplyMember(tpe: Type): Boolean = reallyExists(unapplyMember(tpe)) + private def hasUnapplyMember(sym: Symbol): Boolean = hasUnapplyMember(sym.tpe_*) + private def hasUnapplyMember(fun: Tree): Boolean = hasUnapplyMember(fun.symbol) || hasUnapplyMember(fun.tpe) + + // ad-hoc overloading resolution to deal with unapplies and case class constructors + // If some but not all alternatives survive filtering the tree's symbol with `p`, + // then update the tree's symbol and type to exclude the filtered out alternatives. + private def inPlaceAdHocOverloadingResolution(fun: Tree)(p: Symbol => Boolean): Tree = fun.symbol filter p match { + case sym if sym.exists && (sym ne fun.symbol) => fun setSymbol sym modifyType (tp => filterOverloadedAlts(tp)(p)) + case _ => fun + } + private def filterOverloadedAlts(tpe: Type)(p: Symbol => Boolean): Type = tpe match { + case OverloadedType(pre, alts) => overloadedType(pre, alts filter p) + case tp => tp + } + + def typedConstructorPattern(fun0: Tree, pt: Type) = { + // Do some ad-hoc overloading resolution and update the tree's symbol and type + // do not update the symbol if the tree's symbol's type does not define an unapply member + // (e.g. since it's some method that returns an object with an unapply member) + val fun = inPlaceAdHocOverloadingResolution(fun0)(hasUnapplyMember) + def caseClass = fun.tpe.typeSymbol.linkedClassOfClass + + // Dueling test cases: pos/overloaded-unapply.scala, run/case-class-23.scala, pos/t5022.scala + // A case class with 23+ params has no unapply method. + // A case class constructor be overloaded with unapply methods in the companion. + if (caseClass.isCase && !unapplyMember(fun.tpe).isOverloaded) + convertToCaseConstructor(fun, caseClass, pt) + else if (hasUnapplyMember(fun)) + fun + else + CaseClassConstructorError(fun) + } + + def expectedPatternTypes(fun: Tree, args: List[Tree]): List[Type] = + newExtractorShape(fun, args).expectedPatternTypes + + def typedPatternArgs(fun: Tree, args: List[Tree], mode: Mode): List[Tree] = + typedArgsForFormals(args, newExtractorShape(fun, args).formals, mode) + + def typedArgsForFormals(args: List[Tree], formals: List[Type], mode: Mode): List[Tree] = { + def typedArgWithFormal(arg: Tree, pt: Type) = { + val newMode = if (isByNameParamType(pt)) mode.onlySticky else mode.onlySticky | BYVALmode + typedArg(arg, mode, newMode, dropByName(pt)) + } + val FixedAndRepeatedTypes(fixed, elem) = formals + val front = (args, fixed).zipped map typedArgWithFormal + def rest = context withinStarPatterns (args drop front.length map (typedArgWithFormal(_, elem))) + + elem match { + case NoType => front + case _ => front ::: rest } - loop(args0, formals0, adapted0) + } + + private def boundedArrayType(bound: Type): Type = { + val tparam = context.owner freshExistential "" setInfo (TypeBounds upper bound) + newExistentialType(tparam :: Nil, arrayType(tparam.tpe_*)) } protected def typedStarInPattern(tree: Tree, mode: Mode, pt: Type) = { val Typed(expr, tpt) = tree - val exprTyped = typed(expr, mode.onlySticky) - def subArrayType(pt: Type) = - if (isPrimitiveValueClass(pt.typeSymbol) || !isFullyDefined(pt)) arrayType(pt) - else { - val tparam = context.owner freshExistential "" setInfo TypeBounds.upper(pt) - newExistentialType(List(tparam), arrayType(tparam.tpe)) - } - - val (exprAdapted, baseClass) = exprTyped.tpe.typeSymbol match { - case ArrayClass => (adapt(exprTyped, mode.onlySticky, subArrayType(pt)), ArrayClass) - case _ => (adapt(exprTyped, mode.onlySticky, seqType(pt)), SeqClass) + val exprTyped = typed(expr, mode) + val baseClass = exprTyped.tpe.typeSymbol match { + case ArrayClass => ArrayClass + case _ => SeqClass + } + val starType = baseClass match { + case ArrayClass if isPrimitiveValueType(pt) || !isFullyDefined(pt) => arrayType(pt) + case ArrayClass => boundedArrayType(pt) + case _ => seqType(pt) } - exprAdapted.tpe.baseType(baseClass) match { - case TypeRef(_, _, List(elemtp)) => - treeCopy.Typed(tree, exprAdapted, tpt setType elemtp) setType elemtp - case _ => - setError(tree) + val exprAdapted = adapt(exprTyped, mode, starType) + exprAdapted.tpe baseType baseClass match { + case TypeRef(_, _, elemtp :: Nil) => treeCopy.Typed(tree, exprAdapted, tpt setType elemtp) setType elemtp + case _ => setError(tree) } } protected def typedInPattern(tree: Typed, mode: Mode, pt: Type) = { val Typed(expr, tpt) = tree - val tptTyped = typedType(tpt, mode) - val exprTyped = typed(expr, mode.onlySticky, tptTyped.tpe.deconst) - val treeTyped = treeCopy.Typed(tree, exprTyped, tptTyped) - - if (mode.inPatternMode) { - val uncheckedTypeExtractor = extractorForUncheckedType(tpt.pos, tptTyped.tpe) - // make fully defined to avoid bounded wildcard types that may be in pt from calling dropExistential (SI-2038) - val ptDefined = ensureFullyDefined(pt) // FIXME this is probably redundant now that we don't dropExistenial in pattern mode. - val ownType = inferTypedPattern(tptTyped, tptTyped.tpe, ptDefined, canRemedy = uncheckedTypeExtractor.nonEmpty) - treeTyped setType ownType - - uncheckedTypeExtractor match { - case None => treeTyped - case Some(extractor) => wrapClassTagUnapply(treeTyped, extractor, tptTyped.tpe) - } - } else - treeTyped setType tptTyped.tpe + val tptTyped = typedType(tpt, mode) + val tpe = tptTyped.tpe + val exprTyped = typed(expr, mode, tpe.deconst) + val extractor = extractorForUncheckedType(tpt.pos, tpe) + + val canRemedy = tpe match { + case RefinedType(_, decls) if !decls.isEmpty => false + case RefinedType(parents, _) if parents exists isUncheckable => false + case _ => extractor.nonEmpty + } + + val ownType = inferTypedPattern(tptTyped, tpe, pt, canRemedy) + val treeTyped = treeCopy.Typed(tree, exprTyped, tptTyped) setType ownType + + extractor match { + case EmptyTree => treeTyped + case _ => wrapClassTagUnapply(treeTyped, extractor, tpe) + } } + def newExtractorShape(tree: Tree): ExtractorShape = tree match { + case Apply(fun, args) => ExtractorShape(fun, args) + case UnApply(fun, args) => ExtractorShape(fun, args) + } + def newExtractorShape(fun: Tree, args: List[Tree]): ExtractorShape = ExtractorShape(fun, args) + case class CaseClassInfo(clazz: Symbol, classType: Type) { def constructor = clazz.primaryConstructor def constructorType = classType.prefix memberType clazz memberType constructor @@ -243,7 +293,6 @@ trait PatternTypers { case tp1 => tp1 } } - /* * To deal with the type slack between actual (run-time) types and statically known types, for each abstract type T, * reflect its variance as a skolem that is upper-bounded by T (covariant position), or lower-bounded by T (contravariant). @@ -271,91 +320,39 @@ trait PatternTypers { * * see test/files/../t5189*.scala */ - def adaptConstrPattern(tree: Tree, pt: Type): Tree = { // (5) - def hasUnapplyMember(tp: Type) = reallyExists(unapplyMember(tp)) - val overloadedExtractorOfObject = tree.symbol filter (sym => hasUnapplyMember(sym.tpe)) - // if the tree's symbol's type does not define an extractor, maybe the tree's type does. - // this is the case when we encounter an arbitrary tree as the target of an unapply call - // (rather than something that looks like a constructor call.) (for now, this only happens - // due to wrapClassTagUnapply, but when we support parameterized extractors, it will become - // more common place) - val extractor = overloadedExtractorOfObject orElse unapplyMember(tree.tpe) - def convertToCaseConstructor(clazz: Symbol): TypeTree = { - // convert synthetic unapply of case class to case class constructor - val prefix = tree.tpe.prefix - val tree1 = TypeTree(clazz.primaryConstructor.tpe.asSeenFrom(prefix, clazz.owner)) - .setOriginal(tree) - - val skolems = new mutable.ListBuffer[TypeSymbol] - object variantToSkolem extends TypeMap(trackVariance = true) { - def apply(tp: Type) = mapOver(tp) match { - // !!! FIXME - skipping this when variance.isInvariant allows unsoundness, see SI-5189 - case TypeRef(NoPrefix, tpSym, Nil) if !variance.isInvariant && tpSym.isTypeParameterOrSkolem && tpSym.owner.isTerm => - // must initialize or tpSym.tpe might see random type params!! - // without this, we'll get very weird types inferred in test/scaladoc/run/SI-5933.scala - // TODO: why is that?? - tpSym.initialize - val bounds = if (variance.isPositive) TypeBounds.upper(tpSym.tpe) else TypeBounds.lower(tpSym.tpe) - // origin must be the type param so we can deskolemize - val skolem = context.owner.newGADTSkolem(unit.freshTypeName("?"+tpSym.name), tpSym, bounds) - // println("mapping "+ tpSym +" to "+ skolem + " : "+ bounds +" -- pt= "+ pt +" in "+ context.owner +" at "+ context.tree ) - skolems += skolem - skolem.tpe - case tp1 => tp1 - } - } - - // have to open up the existential and put the skolems in scope - // can't simply package up pt in an ExistentialType, because that takes us back to square one (List[_ <: T] == List[T] due to covariance) - val ptSafe = variantToSkolem(pt) // TODO: pt.skolemizeExistential(context.owner, tree) ? - val freeVars = skolems.toList - - // use "tree" for the context, not context.tree: don't make another CaseDef context, - // as instantiateTypeVar's bounds would end up there - val ctorContext = context.makeNewScope(tree, context.owner) - freeVars foreach ctorContext.scope.enter - newTyper(ctorContext).infer.inferConstructorInstance(tree1, clazz.typeParams, ptSafe) - - // simplify types without losing safety, - // so that we get rid of unnecessary type slack, and so that error messages don't unnecessarily refer to skolems - val extrapolate = new ExistentialExtrapolation(freeVars) extrapolate (_: Type) - val extrapolated = tree1.tpe match { - case MethodType(ctorArgs, res) => // ctorArgs are actually in a covariant position, since this is the type of the subpatterns of the pattern represented by this Apply node - ctorArgs foreach (p => p.info = extrapolate(p.info)) // no need to clone, this is OUR method type - copyMethodType(tree1.tpe, ctorArgs, extrapolate(res)) - case tp => tp - } - - // once the containing CaseDef has been type checked (see typedCase), - // tree1's remaining type-slack skolems will be deskolemized (to the method type parameter skolems) - tree1 setType extrapolated - } - - if (extractor != NoSymbol) { - // if we did some ad-hoc overloading resolution, update the tree's symbol - // do not update the symbol if the tree's symbol's type does not define an unapply member - // (e.g. since it's some method that returns an object with an unapply member) - if (overloadedExtractorOfObject != NoSymbol) - tree setSymbol overloadedExtractorOfObject - - tree.tpe match { - case OverloadedType(pre, alts) => tree setType overloadedType(pre, alts filter (alt => hasUnapplyMember(alt.tpe))) - case _ => - } - val unapply = unapplyMember(extractor.tpe) - val clazz = unapplyParameterType(unapply) - - if (unapply.isCase && clazz.isCase) { - convertToCaseConstructor(clazz) - } else { - tree - } - } else { - val clazz = tree.tpe.typeSymbol.linkedClassOfClass - if (clazz.isCase) - convertToCaseConstructor(clazz) - else - CaseClassConstructorError(tree) + private def convertToCaseConstructor(tree: Tree, caseClass: Symbol, pt: Type): Tree = { + val variantToSkolem = new VariantToSkolemMap + val caseConstructorType = tree.tpe.prefix memberType caseClass memberType caseClass.primaryConstructor + val tree1 = TypeTree(caseConstructorType) setOriginal tree + + // have to open up the existential and put the skolems in scope + // can't simply package up pt in an ExistentialType, because that takes us back to square one (List[_ <: T] == List[T] due to covariance) + val ptSafe = variantToSkolem(pt) // TODO: pt.skolemizeExistential(context.owner, tree) ? + val freeVars = variantToSkolem.skolems + + // use "tree" for the context, not context.tree: don't make another CaseDef context, + // as instantiateTypeVar's bounds would end up there + log(sm"""|convert to case constructor { + | tree: $tree: ${tree.tpe} + | ptSafe: $ptSafe + | context.tree: ${context.tree}: ${context.tree.tpe} + |}""".trim) + + val ctorContext = context.makeNewScope(tree, context.owner) + freeVars foreach ctorContext.scope.enter + newTyper(ctorContext).infer.inferConstructorInstance(tree1, caseClass.typeParams, ptSafe) + + // simplify types without losing safety, + // so that we get rid of unnecessary type slack, and so that error messages don't unnecessarily refer to skolems + val extrapolator = new ExistentialExtrapolation(freeVars) + def extrapolate(tp: Type) = extrapolator extrapolate tp + + // once the containing CaseDef has been type checked (see typedCase), + // tree1's remaining type-slack skolems will be deskolemized (to the method type parameter skolems) + tree1 modifyType { + case MethodType(ctorArgs, restpe) => // ctorArgs are actually in a covariant position, since this is the type of the subpatterns of the pattern represented by this Apply node + copyMethodType(tree1.tpe, ctorArgs map (_ modifyInfo extrapolate), extrapolate(restpe)) // no need to clone ctorArgs, this is OUR method type + case tp => tp } } @@ -363,75 +360,61 @@ trait PatternTypers { def duplErrTree = setError(treeCopy.Apply(tree, fun0, args)) def duplErrorTree(err: AbsTypeError) = { issue(err); duplErrTree } - val otpe = fun.tpe - if (args.length > MaxTupleArity) return duplErrorTree(TooManyArgsPatternError(fun)) - // - def freshArgType(tp: Type): (List[Symbol], Type) = tp match { - case MethodType(param :: _, _) => - (Nil, param.tpe) - case PolyType(tparams, restpe) => - createFromClonedSymbols(tparams, freshArgType(restpe)._2)((ps, t) => ((ps, t))) - // No longer used, see test case neg/t960.scala (#960 has nothing to do with it) - case OverloadedType(_, _) => - OverloadedUnapplyError(fun) - (Nil, ErrorType) - case _ => - UnapplyWithSingleArgError(fun) - (Nil, ErrorType) + def freshArgType(tp: Type): Type = tp match { + case MethodType(param :: _, _) => param.tpe + case PolyType(tparams, restpe) => createFromClonedSymbols(tparams, freshArgType(restpe))(polyType) + case OverloadedType(_, _) => OverloadedUnapplyError(fun) ; ErrorType + case _ => UnapplyWithSingleArgError(fun) ; ErrorType + } + val shape = newExtractorShape(fun, args) + import shape.{ unapplyParamType, unapplyType, unapplyMethod } + + def extractor = extractorForUncheckedType(shape.pos, unapplyParamType) + def canRemedy = unapplyParamType match { + case RefinedType(_, decls) if !decls.isEmpty => false + case RefinedType(parents, _) if parents exists isUncheckable => false + case _ => extractor.nonEmpty } - val unapp = unapplyMember(otpe) - val unappType = otpe.memberType(unapp) - val argDummy = context.owner.newValue(nme.SELECTOR_DUMMY, fun.pos, Flags.SYNTHETIC) setInfo pt - val arg = Ident(argDummy) setType pt - - val uncheckedTypeExtractor = - if (unappType.paramTypes.nonEmpty) - extractorForUncheckedType(tree.pos, unappType.paramTypes.head) - else None - - if (!isApplicableSafe(Nil, unappType, List(pt), WildcardType)) { - //Console.println(s"UNAPP: need to typetest, arg: ${arg.tpe} unappType: $unappType") - val (freeVars, unappFormal) = freshArgType(unappType.skolemizeExistential(context.owner, tree)) + def freshUnapplyArgType(): Type = { + val GenPolyType(freeVars, unappFormal) = freshArgType(unapplyType.skolemizeExistential(context.owner, tree)) val unapplyContext = context.makeNewScope(context.tree, context.owner) freeVars foreach unapplyContext.scope.enter - - val typer1 = newTyper(unapplyContext) - val pattp = typer1.infer.inferTypedPattern(tree, unappFormal, arg.tpe, canRemedy = uncheckedTypeExtractor.nonEmpty) - + val pattp = newTyper(unapplyContext).infer.inferTypedPattern(tree, unappFormal, pt, canRemedy) // turn any unresolved type variables in freevars into existential skolems val skolems = freeVars map (fv => unapplyContext.owner.newExistentialSkolem(fv, fv)) - arg setType pattp.substSym(freeVars, skolems) - argDummy setInfo arg.tpe + pattp.substSym(freeVars, skolems) } + val unapplyArg = ( + context.owner.newValue(nme.SELECTOR_DUMMY, fun.pos, Flags.SYNTHETIC) setInfo ( + if (isApplicableSafe(Nil, unapplyType, pt :: Nil, WildcardType)) pt + else freshUnapplyArgType() + ) + ) // clearing the type is necessary so that ref will be stabilized; see bug 881 - val fun1 = typedPos(fun.pos)(Apply(Select(fun.clearType(), unapp), List(arg))) - - if (fun1.tpe.isErroneous) duplErrTree - else { - val resTp = fun1.tpe.finalResultType.dealiasWiden - val nbSubPats = args.length - val (formals, formalsExpanded) = - extractorFormalTypes(fun0.pos, resTp, nbSubPats, fun1.symbol, treeInfo.effectivePatternArity(args)) - if (formals == null) duplErrorTree(WrongNumberOfArgsError(tree, fun)) - else { - val args1 = typedArgs(args, mode, formals, formalsExpanded) - val pt1 = ensureFullyDefined(pt) // SI-1048 - val itype = glb(List(pt1, arg.tpe)) - arg setType pt1 // restore type (arg is a dummy tree, just needs to pass typechecking) - val unapply = UnApply(fun1, args1) setPos tree.pos setType itype - - // if the type that the unapply method expects for its argument is uncheckable, wrap in classtag extractor - // skip if the unapply's type is not a method type with (at least, but really it should be exactly) one argument - // also skip if we already wrapped a classtag extractor (so we don't keep doing that forever) - if (uncheckedTypeExtractor.isEmpty || fun1.symbol.owner.isNonBottomSubClass(ClassTagClass)) unapply - else wrapClassTagUnapply(unapply, uncheckedTypeExtractor.get, unappType.paramTypes.head) - } + val fun1 = typedPos(fun.pos)(Apply(Select(fun.clearType(), unapplyMethod), Ident(unapplyArg) :: Nil)) + + def makeTypedUnApply() = { + // the union of the expected type and the inferred type of the argument to unapply + val glbType = glb(ensureFullyDefined(pt) :: unapplyArg.tpe_* :: Nil) + val wrapInTypeTest = canRemedy && !(fun1.symbol.owner isNonBottomSubClass ClassTagClass) + val args1 = typedPatternArgs(fun1, args, mode) + val result = UnApply(fun1, args1) setPos tree.pos setType glbType + + if (wrapInTypeTest) + wrapClassTagUnapply(result, extractor, glbType) + else + result } + + if (fun1.tpe.isErroneous) + duplErrTree + else + makeTypedUnApply() } def wrapClassTagUnapply(uncheckedPattern: Tree, classTagExtractor: Tree, pt: Type): Tree = { @@ -459,28 +442,23 @@ trait PatternTypers { // if there's a ClassTag that allows us to turn the unchecked type test for `pt` into a checked type test // return the corresponding extractor (an instance of ClassTag[`pt`]) - def extractorForUncheckedType(pos: Position, pt: Type): Option[Tree] = if (isPastTyper) None else { - // only look at top-level type, can't (reliably) do anything about unchecked type args (in general) - // but at least make a proper type before passing it elsewhere - val pt1 = pt.dealiasWiden match { - case tr @ TypeRef(pre, sym, args) if args.nonEmpty => copyTypeRef(tr, pre, sym, sym.typeParams map (_.tpeHK)) // replace actual type args with dummies - case pt1 => pt1 - } - pt1 match { - // if at least one of the types in an intersection is checkable, use the checkable ones - // this avoids problems as in run/matchonseq.scala, where the expected type is `Coll with scala.collection.SeqLike` - // Coll is an abstract type, but SeqLike of course is not - case RefinedType(ps, _) if ps.length > 1 && (ps exists infer.isCheckable) => - None - - case ptCheckable if infer isUncheckable ptCheckable => - val classTagExtractor = resolveClassTag(pos, ptCheckable) - - if (classTagExtractor != EmptyTree && unapplyMember(classTagExtractor.tpe) != NoSymbol) - Some(classTagExtractor) - else None - - case _ => None + def extractorForUncheckedType(pos: Position, pt: Type): Tree = { + if (isPastTyper || (pt eq NoType)) EmptyTree else { + pt match { + case RefinedType(parents, decls) if !decls.isEmpty || (parents exists isUncheckable) => return EmptyTree + case _ => + } + // only look at top-level type, can't (reliably) do anything about unchecked type args (in general) + // but at least make a proper type before passing it elsewhere + val pt1 = pt.dealiasWiden match { + case tr @ TypeRef(pre, sym, args) if args.nonEmpty => copyTypeRef(tr, pre, sym, sym.typeParams map (_.tpeHK)) // replace actual type args with dummies + case pt1 => pt1 + } + if (isCheckable(pt1)) EmptyTree + else resolveClassTag(pos, pt1) match { + case tree if unapplyMember(tree.tpe).exists => tree + case _ => devWarning(s"Cannot create runtime type test for $pt1") ; EmptyTree + } } } } diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index a1fb5816b9..fc452db737 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -1091,7 +1091,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper else if (mode.typingExprNotFun && treeInfo.isMacroApplication(tree)) macroExpandApply(this, tree, mode, pt) else if (mode.typingConstructorPattern) - adaptConstrPattern(tree, pt) + typedConstructorPattern(tree, pt) else if (shouldInsertApply(tree)) insertApply() else if (hasUndetsInMonoMode) { // (9) @@ -3154,7 +3154,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper if (noExpectedType) typedArgs(args, forArgMode(fun, mode)) else - typedArgs(args, forArgMode(fun, mode), paramTypes, formals) + typedArgsForFormals(args, paramTypes, forArgMode(fun, mode)) ) // instantiate dependent method types, must preserve singleton types where possible (stableTypeFor) -- example use case: @@ -4937,7 +4937,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper // we should get here only when something before failed // and we try again (@see tryTypedApply). In that case we can assign // whatever type to tree; we just have to survive until a real error message is issued. - devWarning(tree.pos, s"Assigning Any type to TypeTree because tree.original == null") + devWarning(tree.pos, s"Assigning Any type to TypeTree because tree.original is null: tree is $tree/${System.identityHashCode(tree)}, sym=${tree.symbol}, tpe=${tree.tpe}") tree setType AnyTpe } } diff --git a/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala b/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala index 18b8f8a9ce..5049fec65b 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala @@ -30,22 +30,6 @@ trait Unapplies extends ast.TreeDSL { // moduleClass symbol of the companion module. class ClassForCaseCompanionAttachment(val caseClass: ClassDef) - /** returns type list for return type of the extraction - * @see extractorFormalTypes - */ - def unapplyTypeList(pos: Position, ufn: Symbol, ufntpe: Type, args: List[Tree]) = { - assert(ufn.isMethod, ufn) - val nbSubPats = args.length - //Console.println("utl "+ufntpe+" "+ufntpe.typeSymbol) - ufn.name match { - case nme.unapply | nme.unapplySeq => - val (formals, _) = extractorFormalTypes(pos, unapplyUnwrap(ufntpe), nbSubPats, ufn, treeInfo.effectivePatternArity(args)) - if (formals == null) throw new TypeError(s"$ufn of type $ufntpe cannot extract $nbSubPats sub-patterns") - else formals - case _ => throw new TypeError(ufn+" is not an unapply or unapplySeq") - } - } - /** Returns unapply or unapplySeq if available, without further checks. */ def directUnapplyMember(tp: Type): Symbol = (tp member nme.unapply) orElse (tp member nme.unapplySeq) @@ -59,12 +43,6 @@ trait Unapplies extends ast.TreeDSL { def unapply(tp: Type): Option[Symbol] = unapplyMember(tp).toOption } - /** returns unapply member's parameter type. */ - def unapplyParameterType(extractor: Symbol) = extractor.tpe.params match { - case p :: Nil => p.tpe.typeSymbol - case _ => NoSymbol - } - def copyUntyped[T <: Tree](tree: T): T = returning[T](tree.duplicate)(UnTyper traverse _) diff --git a/test/files/neg/t4425.check b/test/files/neg/t4425.check index cb5da6e7dc..95b88a6b3d 100644 --- a/test/files/neg/t4425.check +++ b/test/files/neg/t4425.check @@ -2,4 +2,12 @@ t4425.scala:3: error: object X is not a case class constructor, nor does it have Note: def unapply(x: Int)(y: Option[Int]): None.type exists in object X, but it cannot be used as an extractor due to its second non-implicit parameter list 42 match { case _ X _ => () } ^ -one error found +t4425.scala:8: error: object X is not a case class constructor, nor does it have an unapply/unapplySeq method +Note: def unapply(x: Int)(y: Int): Some[(Int, Int)] exists in object X, but it cannot be used as an extractor due to its second non-implicit parameter list + 42 match { case _ X _ => () } + ^ +t4425.scala:13: error: object X is not a case class constructor, nor does it have an unapply/unapplySeq method +Note: def unapply(x: String)(y: String): Some[(Int, Int)] exists in object X, but it cannot be used as an extractor due to its second non-implicit parameter list + "" match { case _ X _ => () } + ^ +three errors found diff --git a/test/files/neg/t4425.scala b/test/files/neg/t4425.scala index d8cc6922f7..1714955c27 100644 --- a/test/files/neg/t4425.scala +++ b/test/files/neg/t4425.scala @@ -2,3 +2,13 @@ object Foo { object X { def unapply(x : Int)(y : Option[Int] = None) = None } 42 match { case _ X _ => () } } + +object Foo2 { + object X { def unapply(x : Int)(y: Int) = Some((2,2)) } + 42 match { case _ X _ => () } +} + +object Foo3 { + object X { def unapply(x : String)(y: String) = Some((2,2)) } + "" match { case _ X _ => () } +} \ No newline at end of file diff --git a/test/files/neg/t4425b.check b/test/files/neg/t4425b.check index e43c489586..3af3027da1 100644 --- a/test/files/neg/t4425b.check +++ b/test/files/neg/t4425b.check @@ -22,34 +22,40 @@ t4425b.scala:10: error: object X is not a case class constructor, nor does it ha Note: def unapply(x: String)(y: String): Nothing exists in object X, but it cannot be used as an extractor due to its second non-implicit parameter list println((X: Any) match { case X(_, _) => "ok" ; case _ => "fail" }) ^ -t4425b.scala:18: error: result type Nothing of unapply defined in method unapply in object X does not conform to Option[_] or Boolean +t4425b.scala:18: error: wrong number of patterns for object X offering : expected 1, found 2 println( "" match { case _ X _ => "ok" ; case _ => "fail" }) ^ -t4425b.scala:19: error: result type Nothing of unapply defined in method unapply in object X does not conform to Option[_] or Boolean +t4425b.scala:19: error: wrong number of patterns for object X offering : expected 1, found 2 println((X: Any) match { case _ X _ => "ok" ; case _ => "fail" }) ^ -t4425b.scala:20: error: result type Nothing of unapply defined in method unapply in object X does not conform to Option[_] or Boolean - println( "" match { case X(_) => "ok" ; case _ => "fail" }) - ^ -t4425b.scala:21: error: result type Nothing of unapply defined in method unapply in object X does not conform to Option[_] or Boolean - println((X: Any) match { case X(_) => "ok" ; case _ => "fail" }) - ^ -t4425b.scala:22: error: result type Nothing of unapply defined in method unapply in object X does not conform to Option[_] or Boolean +t4425b.scala:22: error: wrong number of patterns for object X offering : expected 1, found 2 + println( "" match { case X(_, _) => "ok" ; case _ => "fail" }) + ^ +t4425b.scala:22: error: wrong number of patterns for object X offering : expected 1, found 2 println( "" match { case X(_, _) => "ok" ; case _ => "fail" }) ^ -t4425b.scala:23: error: result type Nothing of unapply defined in method unapply in object X does not conform to Option[_] or Boolean +t4425b.scala:23: error: wrong number of patterns for object X offering : expected 1, found 2 + println((X: Any) match { case X(_, _) => "ok" ; case _ => "fail" }) + ^ +t4425b.scala:23: error: wrong number of patterns for object X offering : expected 1, found 2 println((X: Any) match { case X(_, _) => "ok" ; case _ => "fail" }) ^ -t4425b.scala:31: error: wrong number of arguments for object X +t4425b.scala:31: error: wrong number of patterns for object X offering Nothing: expected 1, found 2 println( "" match { case _ X _ => "ok" ; case _ => "fail" }) ^ -t4425b.scala:32: error: wrong number of arguments for object X +t4425b.scala:32: error: wrong number of patterns for object X offering Nothing: expected 1, found 2 println((X: Any) match { case _ X _ => "ok" ; case _ => "fail" }) ^ -t4425b.scala:35: error: wrong number of arguments for object X +t4425b.scala:35: error: wrong number of patterns for object X offering Nothing: expected 1, found 2 + println( "" match { case X(_, _) => "ok" ; case _ => "fail" }) + ^ +t4425b.scala:35: error: wrong number of patterns for object X offering Nothing: expected 1, found 2 println( "" match { case X(_, _) => "ok" ; case _ => "fail" }) ^ -t4425b.scala:36: error: wrong number of arguments for object X +t4425b.scala:36: error: wrong number of patterns for object X offering Nothing: expected 1, found 2 + println((X: Any) match { case X(_, _) => "ok" ; case _ => "fail" }) + ^ +t4425b.scala:36: error: wrong number of patterns for object X offering Nothing: expected 1, found 2 println((X: Any) match { case X(_, _) => "ok" ; case _ => "fail" }) ^ -16 errors found +18 errors found diff --git a/test/files/neg/t6675.check b/test/files/neg/t6675.check index 3a277af866..aecf04cb68 100644 --- a/test/files/neg/t6675.check +++ b/test/files/neg/t6675.check @@ -1,4 +1,4 @@ -t6675.scala:10: warning: extractor pattern binds a single value to a Product3 of type (Int, Int, Int) +t6675.scala:10: warning: object X expects 3 patterns to hold (Int, Int, Int) but crushing into 3-tuple to fit single pattern (SI-6675) "" match { case X(b) => b } // should warn under -Xlint. Not an error because of SI-6111 ^ error: No warnings can be incurred under -Xfatal-warnings. diff --git a/test/files/neg/t997.check b/test/files/neg/t997.check index 186095f44a..be1e92c369 100644 --- a/test/files/neg/t997.check +++ b/test/files/neg/t997.check @@ -1,7 +1,10 @@ -t997.scala:13: error: wrong number of arguments for object Foo +t997.scala:13: error: wrong number of patterns for object Foo offering (String, String): expected 2, found 3 +"x" match { case Foo(a, b, c) => Console.println((a,b,c)) } + ^ +t997.scala:13: error: wrong number of patterns for object Foo offering (String, String): expected 2, found 3 "x" match { case Foo(a, b, c) => Console.println((a,b,c)) } ^ t997.scala:13: error: not found: value a "x" match { case Foo(a, b, c) => Console.println((a,b,c)) } ^ -two errors found +three errors found diff --git a/test/files/pos/annotated-treecopy/Impls_Macros_1.scala b/test/files/pos/annotated-treecopy/Impls_Macros_1.scala index cf58bc3dfd..ecf8916c46 100644 --- a/test/files/pos/annotated-treecopy/Impls_Macros_1.scala +++ b/test/files/pos/annotated-treecopy/Impls_Macros_1.scala @@ -22,7 +22,7 @@ object Macros { var b1 = new Transformer { override def transform(tree: Tree): Tree = tree match { case Ident(x) if (x==n) => Ident(TermName("_arg")) - case tt @ TypeTree() if tt.original != null => TypeTree(tt.tpe) setOriginal transform(tt.original) + case tt: TypeTree if tt.original != null => TypeTree(tt.tpe) setOriginal transform(tt.original) // without the fix to LazyTreeCopier.Annotated, we would need to uncomment the line below to make the macro work // that's because the pattern match in the input expression gets expanded into Typed(, TypeTree()) // with the original of the TypeTree being Annotated(<@unchecked>, Ident()) diff --git a/test/files/run/matchonseq.scala b/test/files/run/matchonseq.scala index 49b406a6ec..f6f320245a 100644 --- a/test/files/run/matchonseq.scala +++ b/test/files/run/matchonseq.scala @@ -1,8 +1,8 @@ -object Test extends App{ - Vector(1,2,3) match { - case head +: tail => println("It worked! head=" + head) +object Test extends App { + Vector(1,2,3) match { + case head +: tail => println("It worked! head=" + head) } - Vector(1,2,3) match { - case init :+ last => println("It worked! last=" + last) + Vector(1,2,3) match { + case init :+ last => println("It worked! last=" + last) } } diff --git a/test/files/run/t7214.scala b/test/files/run/t7214.scala index ff1ea8082d..15c2c24fa0 100644 --- a/test/files/run/t7214.scala +++ b/test/files/run/t7214.scala @@ -25,7 +25,7 @@ class Crash { def unapply(a: Alias): Option[Any] = None } (t: Any) match { - case Extractor() => + case Extractor(_) => case _ => } -- cgit v1.2.3 From 4f6b16a5e931cf11a8388745738a78cec0818be4 Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Sat, 17 Aug 2013 12:40:23 -0700 Subject: Introduced case class BoundTree. The first step in improving the handling of (Symbol, Tree). --- .../tools/nsc/transform/patmat/MatchTranslation.scala | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala index 5ddcd3528b..2ebd01f091 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala @@ -57,9 +57,11 @@ trait MatchTranslation extends CpsPatternHacks { trait MatchTranslator extends TreeMakers { import typer.context + case class BoundTree(binder: Symbol, tree: Tree) + // a list of TreeMakers that encode `patTree`, and a list of arguments for recursive invocations of `translatePattern` to encode its subpatterns - private case class TranslationStep(makers: List[TreeMaker], subpatterns: List[(Symbol, Tree)]) { - def merge(f: (Symbol, Tree) => List[TreeMaker]): List[TreeMaker] = makers ::: (subpatterns flatMap f.tupled) + private case class TranslationStep(makers: List[TreeMaker], subpatterns: List[BoundTree]) { + def merge(f: BoundTree => List[TreeMaker]): List[TreeMaker] = makers ::: (subpatterns flatMap f) } // Why is it so difficult to say "here's a name and a context, give me any @@ -247,13 +249,14 @@ trait MatchTranslation extends CpsPatternHacks { translatePattern(scrutSym, pattern) ++ translateGuard(guard) :+ translateBody(body, pt) } + def translatePattern(bound: BoundTree): List[TreeMaker] = translatePattern(bound.binder, bound.tree) def translatePattern(patBinder: Symbol, patTree: Tree): List[TreeMaker] = { val pos = patTree.pos def patType = patBinder.info.dealiasWiden // the type of the variable bound to the pattern def glbWithBinder(other: Type) = glb(patType :: other :: Nil).normalize - def withSubPats(treeMakers: List[TreeMaker], subpats: (Symbol, Tree)*): TranslationStep = TranslationStep(treeMakers, subpats.toList) + def withSubPats(treeMakers: List[TreeMaker], subpats: BoundTree*): TranslationStep = TranslationStep(treeMakers, subpats.toList) def noFurtherSubPats(treeMakers: TreeMaker*): TranslationStep = TranslationStep(treeMakers.toList, Nil) def translateExtractorPattern(extractor: ExtractorCall): TranslationStep = { @@ -349,7 +352,7 @@ trait MatchTranslation extends CpsPatternHacks { case WildcardPattern() => none() case _: UnApply | _: Apply => translateExtractorPattern(ExtractorCall(patTree)) case MaybeBoundTyped(subPatBinder, pt) => one(TypeTestTreeMaker(subPatBinder, patBinder, pt, glbWithBinder(pt))(pos)) - case Bound(subpatBinder, p) => withSubPats(List(SubstOnlyTreeMaker(subpatBinder, patBinder)), (patBinder, p)) + case Bound(subpatBinder, p) => withSubPats(List(SubstOnlyTreeMaker(subpatBinder, patBinder)), BoundTree(patBinder, p)) case Literal(Constant(_)) | Ident(_) | Select(_, _) | This(_) => one(EqualityTestTreeMaker(patBinder, patTree, pos)) case Alternative(alts) => one(AlternativesTreeMaker(patBinder, alts map (translatePattern(patBinder, _)), alts.head.pos)) case Bind(_, _) => devWarning(s"Bind tree with unbound symbol $patTree") ; none() @@ -472,7 +475,7 @@ trait MatchTranslation extends CpsPatternHacks { (binders, subPatTypes).zipped map setVarInfo } - lazy val subBindersAndPatterns: List[(Symbol, Tree)] = subPatBinders zip unboundArgs + lazy val subBindersAndPatterns: List[BoundTree] = (subPatBinders, unboundArgs).zipped map BoundTree private def unboundArgs = args map unBound private def unBound(t: Tree): Tree = t match { @@ -724,9 +727,8 @@ trait MatchTranslation extends CpsPatternHacks { object Bound { def unapply(t: Tree): Option[(Symbol, Tree)] = t match { - case t@Bind(n, p) if (t.symbol ne null) && (t.symbol ne NoSymbol) => // pos/t2429 does not satisfy these conditions - Some((t.symbol, p)) - case _ => None + case t@Bind(n, p) if (t.symbol ne null) && (t.symbol ne NoSymbol) => Some((t.symbol, p)) // pos/t2429 does not satisfy these conditions + case _ => None } } } -- cgit v1.2.3 From 54bb76b898e9edf2a90c6b385196771c14acf581 Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Sat, 17 Aug 2013 13:16:12 -0700 Subject: Move more pattern logic into BoundTree. --- .../nsc/transform/patmat/MatchTranslation.scala | 123 +++++++++++++-------- 1 file changed, 75 insertions(+), 48 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala index 2ebd01f091..45003ccef1 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala @@ -48,7 +48,7 @@ trait MatchTranslation extends CpsPatternHacks { import global._ import definitions._ import global.analyzer.{ErrorUtils, formalTypes} - import treeInfo.{ Unapplied, isStar } + import treeInfo.{ WildcardStarArg, Unapplied, isStar, unbind } // Always map repeated params to sequences private def setVarInfo(sym: Symbol, info: Type) = @@ -57,11 +57,53 @@ trait MatchTranslation extends CpsPatternHacks { trait MatchTranslator extends TreeMakers { import typer.context - case class BoundTree(binder: Symbol, tree: Tree) + case class BoundTree(binder: Symbol, tree: Tree) { + def pos = tree.pos + def tpe = binder.info.dealiasWiden // the type of the variable bound to the pattern + def pt = unbound match { + case Star(tpt) => this glbWith seqType(tpt.tpe) + case Typed(_, tpt) => tpt.tpe + case tree => tree.tpe + } + def repeatedType = unbound match { + case Star(tpt) => tpt.tpe + case _ => NoType + } + def glbWith(other: Type) = glb(tpe :: other :: Nil).normalize + + private def setInfo(paramType: Type): Boolean = { + devWarning(s"resetting info of $this to $paramType") + setVarInfo(binder, paramType) + true + } + // If <:< but not =:=, no type test needed, but the tree maker relies on the binder having + // exactly paramType (and not just some type compatible with it.) SI-6624 shows this is necessary + // because apparently patBinder may have an unfortunate type (.decls don't have the case field + // accessors) TODO: get to the bottom of this -- I assume it happens when type checking + // infers a weird type for an unapply call. By going back to the parameterType for the + // extractor call we get a saner type, so let's just do that for now. + def ensureConformsTo(paramType: Type): Boolean = ( + (tpe =:= paramType) + || (tpe <:< paramType) && setInfo(paramType) + ) + + private def concreteType = tpe.bounds.hi + private def unbound = unbind(tree) + private def tpe_s = if (pt <:< concreteType) "" + pt else s"$pt (binder: $tpe)" + private def at_s = unbound match { + case WildcardPattern() => "" + case pat => s" @ $pat" + } + override def toString = s"${binder.name}: $tpe_s$at_s" + } // a list of TreeMakers that encode `patTree`, and a list of arguments for recursive invocations of `translatePattern` to encode its subpatterns private case class TranslationStep(makers: List[TreeMaker], subpatterns: List[BoundTree]) { + if ((sys.props contains "patmat") && subpatterns.nonEmpty) + Console.err.println(this) + def merge(f: BoundTree => List[TreeMaker]): List[TreeMaker] = makers ::: (subpatterns flatMap f) + override def toString = if (subpatterns.isEmpty) "" else subpatterns.mkString("(", ", ", ")") } // Why is it so difficult to say "here's a name and a context, give me any @@ -246,61 +288,45 @@ trait MatchTranslation extends CpsPatternHacks { */ def translateCase(scrutSym: Symbol, pt: Type)(caseDef: CaseDef) = { val CaseDef(pattern, guard, body) = caseDef - translatePattern(scrutSym, pattern) ++ translateGuard(guard) :+ translateBody(body, pt) + translatePattern(BoundTree(scrutSym, pattern)) ++ translateGuard(guard) :+ translateBody(body, pt) } - def translatePattern(bound: BoundTree): List[TreeMaker] = translatePattern(bound.binder, bound.tree) - def translatePattern(patBinder: Symbol, patTree: Tree): List[TreeMaker] = { - val pos = patTree.pos - def patType = patBinder.info.dealiasWiden // the type of the variable bound to the pattern - - def glbWithBinder(other: Type) = glb(patType :: other :: Nil).normalize + def translatePattern(bound: BoundTree): List[TreeMaker] = { + def patBinder = bound.binder + def patTree = bound.tree + def patType = bound.tpe + def pos = bound.pos def withSubPats(treeMakers: List[TreeMaker], subpats: BoundTree*): TranslationStep = TranslationStep(treeMakers, subpats.toList) def noFurtherSubPats(treeMakers: TreeMaker*): TranslationStep = TranslationStep(treeMakers.toList, Nil) + // example check: List[Int] <:< ::[Int] def translateExtractorPattern(extractor: ExtractorCall): TranslationStep = { - import extractor.paramType // the type expected by the unapply - - def patConforms = patType <:< paramType - def patEquiv = patType =:= paramType - + import extractor.{ paramType, treeMaker, subBindersAndPatterns } if (!extractor.isTyped) ErrorUtils.issueNormalTypeError(patTree, "Could not typecheck extractor call: "+ extractor)(context) - debug.patmat("translateExtractorPattern checking parameter type: " + ((patBinder, patType, paramType, patConforms))) - - // example check: List[Int] <:< ::[Int] - // TODO: extractor.paramType may contain unbound type params (run/t2800, run/t3530) - // `patBinderOrCasted` is assigned the result of casting `patBinder` to `extractor.paramType` - val (typeTestTreeMaker, patBinderOrCasted, binderKnownNonNull) = - if (patConforms) { - // no type test needed, but the tree maker relies on `patBinderOrCasted` having type `extractor.paramType` (and not just some type compatible with it) - // SI-6624 shows this is necessary because apparently patBinder may have an unfortunate type (.decls don't have the case field accessors) - // TODO: get to the bottom of this -- I assume it happens when type checking infers a weird type for an unapply call - // by going back to the parameterType for the extractor call we get a saner type, so let's just do that for now - if (!patEquiv) - devWarning(s"resetting info of $patBinder: $patType to $paramType") - - (Nil, setVarInfo(patBinder, paramType), false) - } - else { - // chain a type-testing extractor before the actual extractor call - // it tests the type, checks the outer pointer and casts to the expected type - // TODO: the outer check is mandated by the spec for case classes, but we do it for user-defined unapplies as well [SPEC] - // (the prefix of the argument passed to the unapply must equal the prefix of the type of the binder) - val treeMaker = TypeTestTreeMaker(patBinder, patBinder, paramType, paramType)(pos, extractorArgTypeTest = true) - - // check whether typetest implies patBinder is not null, - // even though the eventual null check will be on patBinderOrCasted - // it'll be equal to patBinder casted to paramType anyway (and the type test is on patBinder) - (List(treeMaker), treeMaker.nextBinder, treeMaker.impliesBinderNonNull(patBinder)) - } + // chain a type-testing extractor before the actual extractor call + // it tests the type, checks the outer pointer and casts to the expected type + // TODO: the outer check is mandated by the spec for case classes, but we do it for user-defined unapplies as well [SPEC] + // (the prefix of the argument passed to the unapply must equal the prefix of the type of the binder) + lazy val typeTest = TypeTestTreeMaker(patBinder, patBinder, paramType, paramType)(pos, extractorArgTypeTest = true) + // check whether typetest implies patBinder is not null, + // even though the eventual null check will be on typeTest.nextBinder + // it'll be equal to patBinder casted to paramType anyway (and the type test is on patBinder) + def extraction: TreeMaker = treeMaker(typeTest.nextBinder, typeTest impliesBinderNonNull patBinder, pos) + + // paramType = the type expected by the unapply + // TODO: paramType may contain unbound type params (run/t2800, run/t3530) + val makers = ( + // Statically conforms to paramType + if (bound ensureConformsTo paramType) treeMaker(patBinder, false, pos) :: Nil + else typeTest :: extraction :: Nil + ) - withSubPats(typeTestTreeMaker :+ extractor.treeMaker(patBinderOrCasted, binderKnownNonNull, pos), extractor.subBindersAndPatterns: _*) + withSubPats(makers, subBindersAndPatterns: _*) } - object MaybeBoundTyped { object NonNullTyped { // the Ident subpattern can be ignored, subpatBinder or patBinder tell us all we need to know about it @@ -331,8 +357,9 @@ trait MatchTranslation extends CpsPatternHacks { | ${asCompactDebugString(patTree)} |""".trim - def one(maker: TreeMaker) = noFurtherSubPats(maker) - def none() = noFurtherSubPats() + def one(maker: TreeMaker) = noFurtherSubPats(maker) + def none() = noFurtherSubPats() + def translatedAlts(alts: List[Tree]) = alts map (alt => translatePattern(BoundTree(patBinder, alt))) // Summary of translation cases. I moved the excerpts from the specification further below so all // the logic can be seen at once. @@ -351,10 +378,10 @@ trait MatchTranslation extends CpsPatternHacks { val translationStep = patTree match { case WildcardPattern() => none() case _: UnApply | _: Apply => translateExtractorPattern(ExtractorCall(patTree)) - case MaybeBoundTyped(subPatBinder, pt) => one(TypeTestTreeMaker(subPatBinder, patBinder, pt, glbWithBinder(pt))(pos)) + case MaybeBoundTyped(subPatBinder, pt) => one(TypeTestTreeMaker(subPatBinder, patBinder, pt, bound glbWith pt)(pos)) case Bound(subpatBinder, p) => withSubPats(List(SubstOnlyTreeMaker(subpatBinder, patBinder)), BoundTree(patBinder, p)) case Literal(Constant(_)) | Ident(_) | Select(_, _) | This(_) => one(EqualityTestTreeMaker(patBinder, patTree, pos)) - case Alternative(alts) => one(AlternativesTreeMaker(patBinder, alts map (translatePattern(patBinder, _)), alts.head.pos)) + case Alternative(alts) => one(AlternativesTreeMaker(patBinder, translatedAlts(alts), alts.head.pos)) case Bind(_, _) => devWarning(s"Bind tree with unbound symbol $patTree") ; none() case _ => context.unit.error(patTree.pos, unsupportedPatternMsg) ; none() } -- cgit v1.2.3 From 84a335916556cb0fe939d1c51f27d80d9cf980dc Mon Sep 17 00:00:00 2001 From: Eugene Burmako Date: Sat, 17 Aug 2013 09:29:28 +0200 Subject: SI-5903 extractor macros Establishes a pattern that can be used to implement extractor macros that give the programmer control over signatures of unapplications at compile-time. === The pattern === In a nutshell, given an unapply method (for simplicity, in this example the scrutinee is of a concrete type, but it's also possible to have the extractor be polymorphic, as demonstrated in the tests): ``` def unapply(x: SomeType) = ??? ``` One can write a macro that generates extraction signatures for unapply on per-call basis, using the target of the calls (c.prefix) and the type of the scrutinee (that comes with x), and then communicate these signatures to the typechecker. For example, here's how one can define a macro that simply passes the scrutinee back to the pattern match (for information on how to express signatures that involve multiple extractees, visit https://github.com/scala/scala/pull/2848). ``` def unapply(x: SomeType) = macro impl def impl(c: Context)(x: c.Tree) = { q""" new { class Match(x: SomeType) { def isEmpty = false def get = x } def unapply(x: SomeType) = new Match(x) }.unapply($x) """ } ``` In addition to the matcher, which implements domain-specific matching logic, there's quite a bit of boilerplate here, but every part of it looks necessary to arrange a non-frustrating dialogue with the typer. Maybe something better can be done in this department, but I can't see how, without introducing modifications to the typechecker. Even though the pattern uses structural types, somehow no reflective calls are being generated (as verified by -Xlog-reflective-calls and then by manual examination of the produced code). That's a mystery to me, but that's also good news, since that means that extractor macros aren't going to induce performance penalties. Almost. Unfortunately, I couldn't turn matchers into value classes because one can't declare value classes local. Nevertheless, I'm leaving a canary in place (neg/t5903e) that will let us know once this restriction is lifted. === Use cases === In particular, the pattern can be used to implement shapeshifting pattern matchers for string interpolators without resorting to dirty tricks. For example, quasiquote unapplications can be unhardcoded now: ``` def doTypedApply(tree: Tree, fun0: Tree, args: List[Tree], ...) = { ... fun.tpe match { case ExtractorType(unapply) if mode.inPatternMode => // this hardcode in Typers.scala is no longer necessary if (unapply == QuasiquoteClass_api_unapply) macroExpandUnapply(...) else doTypedUnapply(tree, fun0, fun, args, mode, pt) } } ``` Rough implementation strategy here would involve writing an extractor macro that destructures c.prefix, analyzes parts of StringContext and then generates an appropriate matcher as outlined above. === Implementation details === No modifications to core logic of typer or patmat are necessary, as we're just piggybacking on https://github.com/scala/scala/pull/2848. The only minor change I introduced is a guard against misbehaving extractor macros that don't conform to the pattern (e.g. expand into blocks or whatever else). Without the guard we'd crash with an NPE, with the guard we get a sane compilation error. --- .../tools/nsc/typechecker/ContextErrors.scala | 3 +++ .../tools/nsc/typechecker/PatternTypers.scala | 2 ++ test/files/neg/t5903a.check | 7 ++++++ test/files/neg/t5903a/Macros_1.scala | 28 ++++++++++++++++++++++ test/files/neg/t5903a/Test_2.scala | 6 +++++ test/files/neg/t5903b.check | 9 +++++++ test/files/neg/t5903b/Macros_1.scala | 23 ++++++++++++++++++ test/files/neg/t5903b/Test_2.scala | 6 +++++ test/files/neg/t5903c.check | 7 ++++++ test/files/neg/t5903c/Macros_1.scala | 26 ++++++++++++++++++++ test/files/neg/t5903c/Test_2.scala | 6 +++++ test/files/neg/t5903d.check | 7 ++++++ test/files/neg/t5903d/Macros_1.scala | 23 ++++++++++++++++++ test/files/neg/t5903d/Test_2.scala | 6 +++++ test/files/neg/t5903e.check | 4 ++++ test/files/neg/t5903e/Macros_1.scala | 25 +++++++++++++++++++ test/files/neg/t5903e/Test_2.scala | 6 +++++ test/files/run/t5903a.check | 1 + test/files/run/t5903a.flags | 1 + test/files/run/t5903a/Macros_1.scala | 28 ++++++++++++++++++++++ test/files/run/t5903a/Test_2.scala | 6 +++++ test/files/run/t5903b.check | 1 + test/files/run/t5903b.flags | 1 + test/files/run/t5903b/Macros_1.scala | 25 +++++++++++++++++++ test/files/run/t5903b/Test_2.scala | 6 +++++ test/files/run/t5903c.check | 1 + test/files/run/t5903c.flags | 1 + test/files/run/t5903c/Macros_1.scala | 23 ++++++++++++++++++ test/files/run/t5903c/Test_2.scala | 6 +++++ test/files/run/t5903d.check | 1 + test/files/run/t5903d.flags | 1 + test/files/run/t5903d/Macros_1.scala | 25 +++++++++++++++++++ test/files/run/t5903d/Test_2.scala | 6 +++++ 33 files changed, 327 insertions(+) create mode 100644 test/files/neg/t5903a.check create mode 100644 test/files/neg/t5903a/Macros_1.scala create mode 100644 test/files/neg/t5903a/Test_2.scala create mode 100644 test/files/neg/t5903b.check create mode 100644 test/files/neg/t5903b/Macros_1.scala create mode 100644 test/files/neg/t5903b/Test_2.scala create mode 100644 test/files/neg/t5903c.check create mode 100644 test/files/neg/t5903c/Macros_1.scala create mode 100644 test/files/neg/t5903c/Test_2.scala create mode 100644 test/files/neg/t5903d.check create mode 100644 test/files/neg/t5903d/Macros_1.scala create mode 100644 test/files/neg/t5903d/Test_2.scala create mode 100644 test/files/neg/t5903e.check create mode 100644 test/files/neg/t5903e/Macros_1.scala create mode 100644 test/files/neg/t5903e/Test_2.scala create mode 100644 test/files/run/t5903a.check create mode 100644 test/files/run/t5903a.flags create mode 100644 test/files/run/t5903a/Macros_1.scala create mode 100644 test/files/run/t5903a/Test_2.scala create mode 100644 test/files/run/t5903b.check create mode 100644 test/files/run/t5903b.flags create mode 100644 test/files/run/t5903b/Macros_1.scala create mode 100644 test/files/run/t5903b/Test_2.scala create mode 100644 test/files/run/t5903c.check create mode 100644 test/files/run/t5903c.flags create mode 100644 test/files/run/t5903c/Macros_1.scala create mode 100644 test/files/run/t5903c/Test_2.scala create mode 100644 test/files/run/t5903d.check create mode 100644 test/files/run/t5903d.flags create mode 100644 test/files/run/t5903d/Macros_1.scala create mode 100644 test/files/run/t5903d/Test_2.scala (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala index 7f4bf0dfbc..1f4d5cbac2 100644 --- a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala +++ b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala @@ -517,6 +517,9 @@ trait ContextErrors { def TooManyArgsPatternError(fun: Tree) = NormalTypeError(fun, "too many arguments for unapply pattern, maximum = "+definitions.MaxTupleArity) + def WrongShapeExtractorExpansion(fun: Tree) = + NormalTypeError(fun, "extractor macros can only expand into extractor calls") + def WrongNumberOfArgsError(tree: Tree, fun: Tree) = NormalTypeError(tree, "wrong number of arguments for "+ treeSymTypeMsg(fun)) diff --git a/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala b/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala index 13926ca18b..cc0ffe2ac2 100644 --- a/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala @@ -413,6 +413,8 @@ trait PatternTypers { if (fun1.tpe.isErroneous) duplErrTree + else if (unapplyMethod.isMacro && !fun1.isInstanceOf[Apply]) + duplErrorTree(WrongShapeExtractorExpansion(tree)) else makeTypedUnApply() } diff --git a/test/files/neg/t5903a.check b/test/files/neg/t5903a.check new file mode 100644 index 0000000000..cbdcfd1bdd --- /dev/null +++ b/test/files/neg/t5903a.check @@ -0,0 +1,7 @@ +Test_2.scala:4: error: wrong number of patterns for <$anon: AnyRef> offering (SomeTree.type, SomeTree.type): expected 2, found 3 + case nq"$x + $y + $z" => println((x, y)) + ^ +Test_2.scala:4: error: not found: value x + case nq"$x + $y + $z" => println((x, y)) + ^ +two errors found diff --git a/test/files/neg/t5903a/Macros_1.scala b/test/files/neg/t5903a/Macros_1.scala new file mode 100644 index 0000000000..e82be0fc68 --- /dev/null +++ b/test/files/neg/t5903a/Macros_1.scala @@ -0,0 +1,28 @@ +import scala.reflect.macros.Context +import language.experimental.macros + +trait Tree +case object SomeTree extends Tree + +object NewQuasiquotes { + implicit class QuasiquoteInterpolation(c: StringContext) { + object nq { + def unapply(t: Tree) = macro QuasiquoteMacros.unapplyImpl + } + } +} + +object QuasiquoteMacros { + def unapplyImpl(c: Context)(t: c.Tree) = { + import c.universe._ + q""" + new { + def isEmpty = false + def get = this + def _1 = SomeTree + def _2 = SomeTree + def unapply(t: Tree) = this + }.unapply($t) + """ + } +} diff --git a/test/files/neg/t5903a/Test_2.scala b/test/files/neg/t5903a/Test_2.scala new file mode 100644 index 0000000000..4d78dfb5e5 --- /dev/null +++ b/test/files/neg/t5903a/Test_2.scala @@ -0,0 +1,6 @@ +object Test extends App { + import NewQuasiquotes._ + SomeTree match { + case nq"$x + $y + $z" => println((x, y)) + } +} diff --git a/test/files/neg/t5903b.check b/test/files/neg/t5903b.check new file mode 100644 index 0000000000..faeb73ad03 --- /dev/null +++ b/test/files/neg/t5903b.check @@ -0,0 +1,9 @@ +Test_2.scala:4: error: type mismatch; + found : Int + required: String + case t"$x" => println(x) + ^ +Test_2.scala:4: error: not found: value x + case t"$x" => println(x) + ^ +two errors found diff --git a/test/files/neg/t5903b/Macros_1.scala b/test/files/neg/t5903b/Macros_1.scala new file mode 100644 index 0000000000..b1b875969d --- /dev/null +++ b/test/files/neg/t5903b/Macros_1.scala @@ -0,0 +1,23 @@ +import scala.reflect.macros.Context +import language.experimental.macros + +object Interpolation { + implicit class TestInterpolation(c: StringContext) { + object t { + def unapply[T](x: T) = macro Macros.unapplyImpl[T] + } + } +} + +object Macros { + def unapplyImpl[T: c.WeakTypeTag](c: Context)(x: c.Tree) = { + import c.universe._ + q""" + new { + def isEmpty = false + def get = "2" + def unapply(x: String) = this + }.unapply($x) + """ + } +} diff --git a/test/files/neg/t5903b/Test_2.scala b/test/files/neg/t5903b/Test_2.scala new file mode 100644 index 0000000000..0f6f80d327 --- /dev/null +++ b/test/files/neg/t5903b/Test_2.scala @@ -0,0 +1,6 @@ +object Test extends App { + import Interpolation._ + 2 match { + case t"$x" => println(x) + } +} diff --git a/test/files/neg/t5903c.check b/test/files/neg/t5903c.check new file mode 100644 index 0000000000..c9476edd11 --- /dev/null +++ b/test/files/neg/t5903c.check @@ -0,0 +1,7 @@ +Test_2.scala:4: error: String is not supported + case t"$x" => println(x) + ^ +Test_2.scala:4: error: not found: value x + case t"$x" => println(x) + ^ +two errors found diff --git a/test/files/neg/t5903c/Macros_1.scala b/test/files/neg/t5903c/Macros_1.scala new file mode 100644 index 0000000000..70efab3101 --- /dev/null +++ b/test/files/neg/t5903c/Macros_1.scala @@ -0,0 +1,26 @@ +import scala.reflect.macros.Context +import language.experimental.macros + +object Interpolation { + implicit class TestInterpolation(c: StringContext) { + object t { + def unapply[T](x: T) = macro Macros.unapplyImpl[T] + } + } +} + +object Macros { + def unapplyImpl[T: c.WeakTypeTag](c: Context)(x: c.Tree) = { + import c.universe._ + if (!(c.weakTypeOf[Int] =:= c.weakTypeOf[T])) c.abort(c.enclosingPosition, s"${c.weakTypeOf[T]} is not supported") + else { + q""" + new { + def isEmpty = false + def get = 2 + def unapply(x: Int) = this + }.unapply($x) + """ + } + } +} diff --git a/test/files/neg/t5903c/Test_2.scala b/test/files/neg/t5903c/Test_2.scala new file mode 100644 index 0000000000..a1fd31dd49 --- /dev/null +++ b/test/files/neg/t5903c/Test_2.scala @@ -0,0 +1,6 @@ +object Test extends App { + import Interpolation._ + "2" match { + case t"$x" => println(x) + } +} diff --git a/test/files/neg/t5903d.check b/test/files/neg/t5903d.check new file mode 100644 index 0000000000..d5d3fdcc28 --- /dev/null +++ b/test/files/neg/t5903d.check @@ -0,0 +1,7 @@ +Test_2.scala:4: error: extractor macros can only expand into extractor calls + case t"$x" => println(x) + ^ +Test_2.scala:4: error: not found: value x + case t"$x" => println(x) + ^ +two errors found diff --git a/test/files/neg/t5903d/Macros_1.scala b/test/files/neg/t5903d/Macros_1.scala new file mode 100644 index 0000000000..15ff226cff --- /dev/null +++ b/test/files/neg/t5903d/Macros_1.scala @@ -0,0 +1,23 @@ +import scala.reflect.macros.Context +import language.experimental.macros + +object Interpolation { + implicit class TestInterpolation(c: StringContext) { + object t { + def unapply(x: Int) = macro Macros.unapplyImpl + } + } +} + +object Macros { + def unapplyImpl(c: Context)(x: c.Tree) = { + import c.universe._ + q""" + class Match(x: Int) { + def isEmpty = false + def get = x + } + new { def unapply(x: Int) = new Match(x) }.unapply($x) + """ + } +} diff --git a/test/files/neg/t5903d/Test_2.scala b/test/files/neg/t5903d/Test_2.scala new file mode 100644 index 0000000000..95c717a9d8 --- /dev/null +++ b/test/files/neg/t5903d/Test_2.scala @@ -0,0 +1,6 @@ +object Test extends App { + import Interpolation._ + 42 match { + case t"$x" => println(x) + } +} diff --git a/test/files/neg/t5903e.check b/test/files/neg/t5903e.check new file mode 100644 index 0000000000..3bdeb091a0 --- /dev/null +++ b/test/files/neg/t5903e.check @@ -0,0 +1,4 @@ +Test_2.scala:4: error: value class may not be a member of another class + case t"$x" => println(x) + ^ +one error found diff --git a/test/files/neg/t5903e/Macros_1.scala b/test/files/neg/t5903e/Macros_1.scala new file mode 100644 index 0000000000..4e1ce89c9f --- /dev/null +++ b/test/files/neg/t5903e/Macros_1.scala @@ -0,0 +1,25 @@ +import scala.reflect.macros.Context +import language.experimental.macros + +object Interpolation { + implicit class TestInterpolation(c: StringContext) { + object t { + def unapply(x: Int) = macro Macros.unapplyImpl + } + } +} + +object Macros { + def unapplyImpl(c: Context)(x: c.Tree) = { + import c.universe._ + q""" + new { + class Match(x: Int) extends AnyVal { + def isEmpty = false + def get = x + } + def unapply(x: Int) = new Match(x) + }.unapply($x) + """ + } +} diff --git a/test/files/neg/t5903e/Test_2.scala b/test/files/neg/t5903e/Test_2.scala new file mode 100644 index 0000000000..d69d472436 --- /dev/null +++ b/test/files/neg/t5903e/Test_2.scala @@ -0,0 +1,6 @@ +class C { + import Interpolation._ + 42 match { + case t"$x" => println(x) + } +} diff --git a/test/files/run/t5903a.check b/test/files/run/t5903a.check new file mode 100644 index 0000000000..ce6efd812d --- /dev/null +++ b/test/files/run/t5903a.check @@ -0,0 +1 @@ +(SomeTree,SomeTree) diff --git a/test/files/run/t5903a.flags b/test/files/run/t5903a.flags new file mode 100644 index 0000000000..02ecab49e7 --- /dev/null +++ b/test/files/run/t5903a.flags @@ -0,0 +1 @@ +-Xlog-reflective-calls \ No newline at end of file diff --git a/test/files/run/t5903a/Macros_1.scala b/test/files/run/t5903a/Macros_1.scala new file mode 100644 index 0000000000..e82be0fc68 --- /dev/null +++ b/test/files/run/t5903a/Macros_1.scala @@ -0,0 +1,28 @@ +import scala.reflect.macros.Context +import language.experimental.macros + +trait Tree +case object SomeTree extends Tree + +object NewQuasiquotes { + implicit class QuasiquoteInterpolation(c: StringContext) { + object nq { + def unapply(t: Tree) = macro QuasiquoteMacros.unapplyImpl + } + } +} + +object QuasiquoteMacros { + def unapplyImpl(c: Context)(t: c.Tree) = { + import c.universe._ + q""" + new { + def isEmpty = false + def get = this + def _1 = SomeTree + def _2 = SomeTree + def unapply(t: Tree) = this + }.unapply($t) + """ + } +} diff --git a/test/files/run/t5903a/Test_2.scala b/test/files/run/t5903a/Test_2.scala new file mode 100644 index 0000000000..3a0b68b568 --- /dev/null +++ b/test/files/run/t5903a/Test_2.scala @@ -0,0 +1,6 @@ +object Test extends App { + import NewQuasiquotes._ + SomeTree match { + case nq"$x + $y" => println((x, y)) + } +} diff --git a/test/files/run/t5903b.check b/test/files/run/t5903b.check new file mode 100644 index 0000000000..75891bc672 --- /dev/null +++ b/test/files/run/t5903b.check @@ -0,0 +1 @@ +oops diff --git a/test/files/run/t5903b.flags b/test/files/run/t5903b.flags new file mode 100644 index 0000000000..02ecab49e7 --- /dev/null +++ b/test/files/run/t5903b.flags @@ -0,0 +1 @@ +-Xlog-reflective-calls \ No newline at end of file diff --git a/test/files/run/t5903b/Macros_1.scala b/test/files/run/t5903b/Macros_1.scala new file mode 100644 index 0000000000..c0124850b8 --- /dev/null +++ b/test/files/run/t5903b/Macros_1.scala @@ -0,0 +1,25 @@ +import scala.reflect.macros.Context +import language.experimental.macros + +object Interpolation { + implicit class TestInterpolation(c: StringContext) { + object t { + def unapply[T](x: T) = macro Macros.unapplyImpl[T] + } + } +} + +object Macros { + def unapplyImpl[T: c.WeakTypeTag](c: Context)(x: c.Tree) = { + import c.universe._ + q""" + new { + def isEmpty = false + def get = this + def _1 = 2 + def unapply(x: Int) = this + override def toString = "oops" + }.unapply($x) + """ + } +} diff --git a/test/files/run/t5903b/Test_2.scala b/test/files/run/t5903b/Test_2.scala new file mode 100644 index 0000000000..0f6f80d327 --- /dev/null +++ b/test/files/run/t5903b/Test_2.scala @@ -0,0 +1,6 @@ +object Test extends App { + import Interpolation._ + 2 match { + case t"$x" => println(x) + } +} diff --git a/test/files/run/t5903c.check b/test/files/run/t5903c.check new file mode 100644 index 0000000000..0cfbf08886 --- /dev/null +++ b/test/files/run/t5903c.check @@ -0,0 +1 @@ +2 diff --git a/test/files/run/t5903c.flags b/test/files/run/t5903c.flags new file mode 100644 index 0000000000..02ecab49e7 --- /dev/null +++ b/test/files/run/t5903c.flags @@ -0,0 +1 @@ +-Xlog-reflective-calls \ No newline at end of file diff --git a/test/files/run/t5903c/Macros_1.scala b/test/files/run/t5903c/Macros_1.scala new file mode 100644 index 0000000000..f8baa2275b --- /dev/null +++ b/test/files/run/t5903c/Macros_1.scala @@ -0,0 +1,23 @@ +import scala.reflect.macros.Context +import language.experimental.macros + +object Interpolation { + implicit class TestInterpolation(c: StringContext) { + object t { + def unapply[T](x: T) = macro Macros.unapplyImpl[T] + } + } +} + +object Macros { + def unapplyImpl[T: c.WeakTypeTag](c: Context)(x: c.Tree) = { + import c.universe._ + q""" + new { + def isEmpty = false + def get = 2 + def unapply(x: Int) = this + }.unapply($x) + """ + } +} diff --git a/test/files/run/t5903c/Test_2.scala b/test/files/run/t5903c/Test_2.scala new file mode 100644 index 0000000000..0f6f80d327 --- /dev/null +++ b/test/files/run/t5903c/Test_2.scala @@ -0,0 +1,6 @@ +object Test extends App { + import Interpolation._ + 2 match { + case t"$x" => println(x) + } +} diff --git a/test/files/run/t5903d.check b/test/files/run/t5903d.check new file mode 100644 index 0000000000..d81cc0710e --- /dev/null +++ b/test/files/run/t5903d.check @@ -0,0 +1 @@ +42 diff --git a/test/files/run/t5903d.flags b/test/files/run/t5903d.flags new file mode 100644 index 0000000000..02ecab49e7 --- /dev/null +++ b/test/files/run/t5903d.flags @@ -0,0 +1 @@ +-Xlog-reflective-calls \ No newline at end of file diff --git a/test/files/run/t5903d/Macros_1.scala b/test/files/run/t5903d/Macros_1.scala new file mode 100644 index 0000000000..88d714e17b --- /dev/null +++ b/test/files/run/t5903d/Macros_1.scala @@ -0,0 +1,25 @@ +import scala.reflect.macros.Context +import language.experimental.macros + +object Interpolation { + implicit class TestInterpolation(c: StringContext) { + object t { + def unapply(x: Int) = macro Macros.unapplyImpl + } + } +} + +object Macros { + def unapplyImpl(c: Context)(x: c.Tree) = { + import c.universe._ + q""" + new { + class Match(x: Int) { + def isEmpty = false + def get = x + } + def unapply(x: Int) = new Match(x) + }.unapply($x) + """ + } +} diff --git a/test/files/run/t5903d/Test_2.scala b/test/files/run/t5903d/Test_2.scala new file mode 100644 index 0000000000..95c717a9d8 --- /dev/null +++ b/test/files/run/t5903d/Test_2.scala @@ -0,0 +1,6 @@ +object Test extends App { + import Interpolation._ + 42 match { + case t"$x" => println(x) + } +} -- cgit v1.2.3 From ef30ea34b2f44aa385ea2fc247457eb9b90ae1a6 Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Sat, 17 Aug 2013 14:23:31 -0700 Subject: Pull translatePattern entirely into BoundTree. --- .../nsc/transform/patmat/MatchTranslation.scala | 206 +++++++++++---------- 1 file changed, 104 insertions(+), 102 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala index 45003ccef1..05a4f1b298 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala @@ -57,7 +57,7 @@ trait MatchTranslation extends CpsPatternHacks { trait MatchTranslator extends TreeMakers { import typer.context - case class BoundTree(binder: Symbol, tree: Tree) { + final case class BoundTree(binder: Symbol, tree: Tree) { def pos = tree.pos def tpe = binder.info.dealiasWiden // the type of the variable bound to the pattern def pt = unbound match { @@ -71,6 +71,107 @@ trait MatchTranslation extends CpsPatternHacks { } def glbWith(other: Type) = glb(tpe :: other :: Nil).normalize + private lazy val extractor = ExtractorCall(tree) + + object MaybeBoundTyped { + object NonNullTyped { + // the Ident subpattern can be ignored, subpatBinder or patBinder tell us all we need to know about it + def unapply(tree: Typed): Option[Type] = tree match { + case Typed(Ident(_), _) if tree.tpe != null => Some((tree.tpe)) + case _ => None + } + } + + /** Decompose the pattern in `tree`, of shape C(p_1, ..., p_N), into a list of N symbols, and a list of its N sub-trees + * The list of N symbols contains symbols for every bound name as well as the un-named sub-patterns (fresh symbols are generated here for these). + * The returned type is the one inferred by inferTypedPattern (`owntype`) + * + * @arg patBinder symbol used to refer to the result of the previous pattern's extractor + * (will later be replaced by the outer tree with the correct tree to refer to that patterns result) + */ + def unapply(tree: Tree): Option[(Symbol, Type)] = tree match { + case Bound(namedBinder, MaybeBoundTyped(_, tpe)) => Some((namedBinder, tpe)) // possible nested bindings - use the outermost + case NonNullTyped(tpe) => Some((binder, tpe)) // binder used if no local bindings + case Bind(_, expr) => unapply(expr) + case _ => None + } + } + + private def rebindTo(pattern: Tree) = BoundTree(binder, pattern) + private def step(treeMakers: TreeMaker*)(subpatterns: BoundTree*): TranslationStep = TranslationStep(treeMakers.toList, subpatterns.toList) + + private def bindingStep(sub: Symbol, subpattern: Tree) = step(SubstOnlyTreeMaker(sub, binder))(rebindTo(subpattern)) + private def equalityTestStep() = step(EqualityTestTreeMaker(binder, tree, pos))() + private def typeTestStep(sub: Symbol, subPt: Type) = step(TypeTestTreeMaker(sub, binder, subPt, glbWith(subPt))(pos))() + private def alternativesStep(alts: List[Tree]) = step(AlternativesTreeMaker(binder, translatedAlts(alts), alts.head.pos))() + private def translatedAlts(alts: List[Tree]) = alts map (alt => rebindTo(alt).translate()) + private def noStep() = step()() + + private def unsupportedPatternMsg = sm""" + |unsupported pattern: $this (this is a scalac bug.) + |Tree diagnostics: + | ${asCompactDebugString(tree)} + |""".trim + + + // example check: List[Int] <:< ::[Int] + private def extractorStep(): TranslationStep = { + import extractor.{ paramType, treeMaker, subBindersAndPatterns } + if (!extractor.isTyped) + ErrorUtils.issueNormalTypeError(tree, "Could not typecheck extractor call: "+ extractor)(context) + + // chain a type-testing extractor before the actual extractor call + // it tests the type, checks the outer pointer and casts to the expected type + // TODO: the outer check is mandated by the spec for case classes, but we do it for user-defined unapplies as well [SPEC] + // (the prefix of the argument passed to the unapply must equal the prefix of the type of the binder) + lazy val typeTest = TypeTestTreeMaker(binder, binder, paramType, paramType)(pos, extractorArgTypeTest = true) + // check whether typetest implies binder is not null, + // even though the eventual null check will be on typeTest.nextBinder + // it'll be equal to binder casted to paramType anyway (and the type test is on binder) + def extraction: TreeMaker = treeMaker(typeTest.nextBinder, typeTest impliesBinderNonNull binder, pos) + + // paramType = the type expected by the unapply + // TODO: paramType may contain unbound type params (run/t2800, run/t3530) + val makers = ( + // Statically conforms to paramType + if (this ensureConformsTo paramType) treeMaker(binder, false, pos) :: Nil + else typeTest :: extraction :: Nil + ) + step(makers: _*)(subBindersAndPatterns: _*) + } + + // Summary of translation cases. I moved the excerpts from the specification further below so all + // the logic can be seen at once. + // + // [1] skip wildcard trees -- no point in checking them + // [2] extractor and constructor patterns + // [3] replace subpatBinder by patBinder, as if the Bind was not there. + // It must be patBinder, as subpatBinder has the wrong info: even if the bind assumes a better type, + // this is not guaranteed until we cast + // [4] typed patterns - a typed pattern never has any subtrees + // must treat Typed and Bind together -- we need to know the patBinder of the Bind pattern to get at the actual type + // [5] literal and stable id patterns + // [6] pattern alternatives + // [7] symbol-less bind patterns - this happens in certain ill-formed programs, there'll be an error later + // don't fail here though (or should we?) + def nextStep(): TranslationStep = tree match { + case WildcardPattern() => noStep() + case _: UnApply | _: Apply => extractorStep() + case MaybeBoundTyped(sub, subPt) => typeTestStep(sub, subPt) + case Bound(sub, subTree) => bindingStep(sub, subTree) + case Literal(Constant(_)) | Ident(_) | Select(_, _) | This(_) => equalityTestStep() + case Alternative(alts) => alternativesStep(alts) + case Bind(_, _) => devWarning(s"Bind tree with unbound symbol $tree") ; noStep() + case _ => context.unit.error(pos, unsupportedPatternMsg) ; noStep() + } + def translate(): List[TreeMaker] = tree match { + case CaseDef(pat, guard, body) => BoundTree(binder, pat).translate() ++ translateGuard(guard) ++ translateBody(body) + case _ => nextStep() merge (_.translate()) + } + def translatePat(pat: Tree) = nextStep() + def translateGuard(guard: Tree) = if (tree eq EmptyTree) Nil else GuardTreeMaker(guard) :: Nil + def translateBody(body: Tree) = BodyTreeMaker(body, pt) :: Nil + private def setInfo(paramType: Type): Boolean = { devWarning(s"resetting info of $this to $paramType") setVarInfo(binder, paramType) @@ -98,10 +199,7 @@ trait MatchTranslation extends CpsPatternHacks { } // a list of TreeMakers that encode `patTree`, and a list of arguments for recursive invocations of `translatePattern` to encode its subpatterns - private case class TranslationStep(makers: List[TreeMaker], subpatterns: List[BoundTree]) { - if ((sys.props contains "patmat") && subpatterns.nonEmpty) - Console.err.println(this) - + final case class TranslationStep(makers: List[TreeMaker], subpatterns: List[BoundTree]) { def merge(f: BoundTree => List[TreeMaker]): List[TreeMaker] = makers ::: (subpatterns flatMap f) override def toString = if (subpatterns.isEmpty) "" else subpatterns.mkString("(", ", ", ")") } @@ -291,103 +389,7 @@ trait MatchTranslation extends CpsPatternHacks { translatePattern(BoundTree(scrutSym, pattern)) ++ translateGuard(guard) :+ translateBody(body, pt) } - def translatePattern(bound: BoundTree): List[TreeMaker] = { - def patBinder = bound.binder - def patTree = bound.tree - def patType = bound.tpe - def pos = bound.pos - - def withSubPats(treeMakers: List[TreeMaker], subpats: BoundTree*): TranslationStep = TranslationStep(treeMakers, subpats.toList) - def noFurtherSubPats(treeMakers: TreeMaker*): TranslationStep = TranslationStep(treeMakers.toList, Nil) - - // example check: List[Int] <:< ::[Int] - def translateExtractorPattern(extractor: ExtractorCall): TranslationStep = { - import extractor.{ paramType, treeMaker, subBindersAndPatterns } - if (!extractor.isTyped) - ErrorUtils.issueNormalTypeError(patTree, "Could not typecheck extractor call: "+ extractor)(context) - - // chain a type-testing extractor before the actual extractor call - // it tests the type, checks the outer pointer and casts to the expected type - // TODO: the outer check is mandated by the spec for case classes, but we do it for user-defined unapplies as well [SPEC] - // (the prefix of the argument passed to the unapply must equal the prefix of the type of the binder) - lazy val typeTest = TypeTestTreeMaker(patBinder, patBinder, paramType, paramType)(pos, extractorArgTypeTest = true) - // check whether typetest implies patBinder is not null, - // even though the eventual null check will be on typeTest.nextBinder - // it'll be equal to patBinder casted to paramType anyway (and the type test is on patBinder) - def extraction: TreeMaker = treeMaker(typeTest.nextBinder, typeTest impliesBinderNonNull patBinder, pos) - - // paramType = the type expected by the unapply - // TODO: paramType may contain unbound type params (run/t2800, run/t3530) - val makers = ( - // Statically conforms to paramType - if (bound ensureConformsTo paramType) treeMaker(patBinder, false, pos) :: Nil - else typeTest :: extraction :: Nil - ) - - withSubPats(makers, subBindersAndPatterns: _*) - } - - object MaybeBoundTyped { - object NonNullTyped { - // the Ident subpattern can be ignored, subpatBinder or patBinder tell us all we need to know about it - def unapply(tree: Typed): Option[Type] = tree match { - case Typed(Ident(_), _) if tree.tpe != null => Some((tree.tpe)) - case _ => None - } - } - - /** Decompose the pattern in `tree`, of shape C(p_1, ..., p_N), into a list of N symbols, and a list of its N sub-trees - * The list of N symbols contains symbols for every bound name as well as the un-named sub-patterns (fresh symbols are generated here for these). - * The returned type is the one inferred by inferTypedPattern (`owntype`) - * - * @arg patBinder symbol used to refer to the result of the previous pattern's extractor - * (will later be replaced by the outer tree with the correct tree to refer to that patterns result) - */ - def unapply(tree: Tree): Option[(Symbol, Type)] = tree match { - case Bound(binder, MaybeBoundTyped(_, tpe)) => Some((binder, tpe)) // possible nested bindings - use the outermost - case NonNullTyped(tpe) => Some((patBinder, tpe)) // patBinder used if no local bindings - case Bind(_, expr) => unapply(expr) - case _ => None - } - } - - def unsupportedPatternMsg = sm""" - |unsupported pattern: ${patTree.shortClass} $patTree (this is a scalac bug.) - |Tree diagnostics: - | ${asCompactDebugString(patTree)} - |""".trim - - def one(maker: TreeMaker) = noFurtherSubPats(maker) - def none() = noFurtherSubPats() - def translatedAlts(alts: List[Tree]) = alts map (alt => translatePattern(BoundTree(patBinder, alt))) - - // Summary of translation cases. I moved the excerpts from the specification further below so all - // the logic can be seen at once. - // - // [1] skip wildcard trees -- no point in checking them - // [2] extractor and constructor patterns - // [3] replace subpatBinder by patBinder, as if the Bind was not there. - // It must be patBinder, as subpatBinder has the wrong info: even if the bind assumes a better type, - // this is not guaranteed until we cast - // [4] typed patterns - a typed pattern never has any subtrees - // must treat Typed and Bind together -- we need to know the patBinder of the Bind pattern to get at the actual type - // [5] literal and stable id patterns - // [6] pattern alternatives - // [7] symbol-less bind patterns - this happens in certain ill-formed programs, there'll be an error later - // don't fail here though (or should we?) - val translationStep = patTree match { - case WildcardPattern() => none() - case _: UnApply | _: Apply => translateExtractorPattern(ExtractorCall(patTree)) - case MaybeBoundTyped(subPatBinder, pt) => one(TypeTestTreeMaker(subPatBinder, patBinder, pt, bound glbWith pt)(pos)) - case Bound(subpatBinder, p) => withSubPats(List(SubstOnlyTreeMaker(subpatBinder, patBinder)), BoundTree(patBinder, p)) - case Literal(Constant(_)) | Ident(_) | Select(_, _) | This(_) => one(EqualityTestTreeMaker(patBinder, patTree, pos)) - case Alternative(alts) => one(AlternativesTreeMaker(patBinder, translatedAlts(alts), alts.head.pos)) - case Bind(_, _) => devWarning(s"Bind tree with unbound symbol $patTree") ; none() - case _ => context.unit.error(patTree.pos, unsupportedPatternMsg) ; none() - } - - translationStep merge translatePattern - } + def translatePattern(bound: BoundTree): List[TreeMaker] = bound.translate() def translateGuard(guard: Tree): List[TreeMaker] = if (guard == EmptyTree) Nil -- cgit v1.2.3 From 017460e63c3ca9d5cc1ceb836c1e223492d47c7e Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Sat, 17 Aug 2013 15:45:01 -0700 Subject: Reworked MaybeTypedBound. --- .../scala/tools/nsc/transform/patmat/Logic.scala | 1 + .../nsc/transform/patmat/MatchTranslation.scala | 161 ++++++--------------- .../tools/nsc/transform/patmat/MatchWarnings.scala | 86 +++++++++++ .../nsc/transform/patmat/PatternMatching.scala | 3 +- 4 files changed, 130 insertions(+), 121 deletions(-) create mode 100644 src/compiler/scala/tools/nsc/transform/patmat/MatchWarnings.scala (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/transform/patmat/Logic.scala b/src/compiler/scala/tools/nsc/transform/patmat/Logic.scala index e49dd22948..45aa1106f0 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/Logic.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/Logic.scala @@ -563,6 +563,7 @@ trait ScalaLogic extends Interface with Logic with TreeAndTypeAnalysis { // (At least conceptually: `true` is an instance of class `Boolean`) private def widenToClass(tp: Type): Type = if (tp.typeSymbol.isClass) tp + else if (tp.baseClasses.isEmpty) sys.error("Bad type: " + tp) else tp.baseType(tp.baseClasses.head) object TypeConst extends TypeConstExtractor { diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala index 05a4f1b298..a91f24ef0e 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala @@ -54,16 +54,32 @@ trait MatchTranslation extends CpsPatternHacks { private def setVarInfo(sym: Symbol, info: Type) = sym setInfo debug.patmatResult(s"changing ${sym.defString} to")(repeatedToSeq(info)) - trait MatchTranslator extends TreeMakers { + private def hasSym(t: Tree) = t.symbol != null && t.symbol != NoSymbol + + trait MatchTranslator extends TreeMakers with TreeMakerWarnings { import typer.context + object SymbolBound { + def unapply(tree: Tree): Option[(Symbol, Tree)] = tree match { + case Bind(_, expr) if hasSym(tree) => Some(tree.symbol -> expr) + case _ => None + } + } + + def newBoundTree(tree: Tree, pt: Type): BoundTree = tree match { + case SymbolBound(sym, expr) => BoundTree(setVarInfo(sym, pt), expr) + case _ => BoundTree(setVarInfo(freshSym(tree.pos, prefix = "p"), pt), tree) + } + final case class BoundTree(binder: Symbol, tree: Tree) { + private lazy val extractor = ExtractorCall(tree) + def pos = tree.pos def tpe = binder.info.dealiasWiden // the type of the variable bound to the pattern def pt = unbound match { - case Star(tpt) => this glbWith seqType(tpt.tpe) - case Typed(_, tpt) => tpt.tpe - case tree => tree.tpe + case Star(tpt) => this glbWith seqType(tpt.tpe) + case TypeBound(tpe) => tpe + case tree => tree.tpe } def repeatedType = unbound match { case Star(tpt) => tpt.tpe @@ -71,29 +87,18 @@ trait MatchTranslation extends CpsPatternHacks { } def glbWith(other: Type) = glb(tpe :: other :: Nil).normalize - private lazy val extractor = ExtractorCall(tree) - - object MaybeBoundTyped { - object NonNullTyped { - // the Ident subpattern can be ignored, subpatBinder or patBinder tell us all we need to know about it - def unapply(tree: Typed): Option[Type] = tree match { - case Typed(Ident(_), _) if tree.tpe != null => Some((tree.tpe)) - case _ => None - } + object SymbolAndTypeBound { + def unapply(tree: Tree): Option[(Symbol, Type)] = tree match { + case SymbolBound(sym, SymbolAndTypeBound(_, tpe)) => Some(sym -> tpe) + case TypeBound(tpe) => Some(binder -> tpe) + case _ => None } + } - /** Decompose the pattern in `tree`, of shape C(p_1, ..., p_N), into a list of N symbols, and a list of its N sub-trees - * The list of N symbols contains symbols for every bound name as well as the un-named sub-patterns (fresh symbols are generated here for these). - * The returned type is the one inferred by inferTypedPattern (`owntype`) - * - * @arg patBinder symbol used to refer to the result of the previous pattern's extractor - * (will later be replaced by the outer tree with the correct tree to refer to that patterns result) - */ - def unapply(tree: Tree): Option[(Symbol, Type)] = tree match { - case Bound(namedBinder, MaybeBoundTyped(_, tpe)) => Some((namedBinder, tpe)) // possible nested bindings - use the outermost - case NonNullTyped(tpe) => Some((binder, tpe)) // binder used if no local bindings - case Bind(_, expr) => unapply(expr) - case _ => None + object TypeBound { + def unapply(tree: Tree): Option[Type] = unbind(tree) match { + case Typed(Ident(_), _) if tree.tpe != null => Some(tree.tpe) + case _ => None } } @@ -108,15 +113,12 @@ trait MatchTranslation extends CpsPatternHacks { private def noStep() = step()() private def unsupportedPatternMsg = sm""" - |unsupported pattern: $this (this is a scalac bug.) - |Tree diagnostics: - | ${asCompactDebugString(tree)} + |unsupported pattern: ${tree.shortClass} / $this (this is a scalac bug.) |""".trim - // example check: List[Int] <:< ::[Int] private def extractorStep(): TranslationStep = { - import extractor.{ paramType, treeMaker, subBindersAndPatterns } + import extractor.{ paramType, treeMaker } if (!extractor.isTyped) ErrorUtils.issueNormalTypeError(tree, "Could not typecheck extractor call: "+ extractor)(context) @@ -137,7 +139,7 @@ trait MatchTranslation extends CpsPatternHacks { if (this ensureConformsTo paramType) treeMaker(binder, false, pos) :: Nil else typeTest :: extraction :: Nil ) - step(makers: _*)(subBindersAndPatterns: _*) + step(makers: _*)(extractor.subBoundTrees: _*) } // Summary of translation cases. I moved the excerpts from the specification further below so all @@ -157,20 +159,14 @@ trait MatchTranslation extends CpsPatternHacks { def nextStep(): TranslationStep = tree match { case WildcardPattern() => noStep() case _: UnApply | _: Apply => extractorStep() - case MaybeBoundTyped(sub, subPt) => typeTestStep(sub, subPt) - case Bound(sub, subTree) => bindingStep(sub, subTree) + case SymbolAndTypeBound(sym, tpe) => typeTestStep(sym, tpe) + case TypeBound(tpe) => typeTestStep(binder, tpe) + case SymbolBound(sym, expr) => bindingStep(sym, expr) case Literal(Constant(_)) | Ident(_) | Select(_, _) | This(_) => equalityTestStep() case Alternative(alts) => alternativesStep(alts) - case Bind(_, _) => devWarning(s"Bind tree with unbound symbol $tree") ; noStep() case _ => context.unit.error(pos, unsupportedPatternMsg) ; noStep() } - def translate(): List[TreeMaker] = tree match { - case CaseDef(pat, guard, body) => BoundTree(binder, pat).translate() ++ translateGuard(guard) ++ translateBody(body) - case _ => nextStep() merge (_.translate()) - } - def translatePat(pat: Tree) = nextStep() - def translateGuard(guard: Tree) = if (tree eq EmptyTree) Nil else GuardTreeMaker(guard) :: Nil - def translateBody(body: Tree) = BodyTreeMaker(body, pt) :: Nil + def translate(): List[TreeMaker] = nextStep() merge (_.translate()) private def setInfo(paramType: Type): Boolean = { devWarning(s"resetting info of $this to $paramType") @@ -204,69 +200,6 @@ trait MatchTranslation extends CpsPatternHacks { override def toString = if (subpatterns.isEmpty) "" else subpatterns.mkString("(", ", ", ")") } - // Why is it so difficult to say "here's a name and a context, give me any - // matching symbol in scope" ? I am sure this code is wrong, but attempts to - // use the scopes of the contexts in the enclosing context chain discover - // nothing. How to associate a name with a symbol would would be a wonderful - // linkage for which to establish a canonical acquisition mechanism. - def matchingSymbolInScope(pat: Tree): Symbol = { - def declarationOfName(tpe: Type, name: Name): Symbol = tpe match { - case PolyType(tparams, restpe) => tparams find (_.name == name) getOrElse declarationOfName(restpe, name) - case MethodType(params, restpe) => params find (_.name == name) getOrElse declarationOfName(restpe, name) - case ClassInfoType(_, _, clazz) => clazz.rawInfo member name - case _ => NoSymbol - } - pat match { - case Bind(name, _) => - context.enclosingContextChain.foldLeft(NoSymbol: Symbol)((res, ctx) => - res orElse declarationOfName(ctx.owner.rawInfo, name)) - case _ => NoSymbol - } - } - - // Issue better warnings than "unreachable code" when people mis-use - // variable patterns thinking they bind to existing identifiers. - // - // Possible TODO: more deeply nested variable patterns, like - // case (a, b) => 1 ; case (c, d) => 2 - // However this is a pain (at least the way I'm going about it) - // and I have to think these detailed errors are primarily useful - // for beginners, not people writing nested pattern matches. - def checkMatchVariablePatterns(cases: List[CaseDef]) { - // A string describing the first variable pattern - var vpat: String = null - // Using an iterator so we can recognize the last case - val it = cases.iterator - - def addendum(pat: Tree) = { - matchingSymbolInScope(pat) match { - case NoSymbol => "" - case sym => - val desc = if (sym.isParameter) s"parameter ${sym.nameString} of" else sym + " in" - s"\nIf you intended to match against $desc ${sym.owner}, you must use backticks, like: case `${sym.nameString}` =>" - } - } - - while (it.hasNext) { - val cdef = it.next() - // If a default case has been seen, then every succeeding case is unreachable. - if (vpat != null) - context.unit./*error*/warning(cdef.body.pos, "unreachable code due to " + vpat + addendum(cdef.pat)) - // If this is a default case and more cases follow, warn about this one so - // we have a reason to mention its pattern variable name and any corresponding - // symbol in scope. Errors will follow from the remaining cases, at least - // once we make the above warning an error. - else if (it.hasNext && (treeInfo isDefaultCase cdef)) { - val vpatName = cdef.pat match { - case Bind(name, _) => s" '$name'" - case _ => "" - } - vpat = s"variable pattern$vpatName on line ${cdef.pat.pos.line}" - context.unit.warning(cdef.pos, s"patterns after a variable pattern cannot match (SLS 8.1.1)" + addendum(cdef.pat)) - } - } - } - /** Implement a pattern match by turning its cases (including the implicit failure case) * into the corresponding (monadic) extractors, and combining them with the `orElse` combinator. * @@ -497,20 +430,8 @@ trait MatchTranslation extends CpsPatternHacks { // as b.info may be based on a Typed type ascription, which has not been taken into account yet by the translation // (it will later result in a type test when `tp` is not a subtype of `b.info`) // TODO: can we simplify this, together with the Bound case? - lazy val subPatBinders = { - val binders = args map { case Bound(b, _) => b ; case p => freshSym(p.pos, prefix = "p") } - if (binders.length != subPatTypes.length) - devWarning(s"Mismatched binders and subpatterns: binders=$binders, subPatTypes=$subPatTypes") - - (binders, subPatTypes).zipped map setVarInfo - } - lazy val subBindersAndPatterns: List[BoundTree] = (subPatBinders, unboundArgs).zipped map BoundTree - - private def unboundArgs = args map unBound - private def unBound(t: Tree): Tree = t match { - case Bound(_, p) => p - case _ => t - } + def subPatBinders = subBoundTrees map (_.binder) + lazy val subBoundTrees = (args, subPatTypes).zipped map newBoundTree // never store these in local variables (for PreserveSubPatBinders) lazy val ignoredSubPatBinders: Set[Symbol] = subPatBinders zip args collect { case (b, PatternBoundToUnderscore()) => b } toSet @@ -632,11 +553,12 @@ trait MatchTranslation extends CpsPatternHacks { // binders corresponding to mutable fields should be stored (SI-5158, SI-6070) // make an exception for classes under the scala package as they should be well-behaved, // to optimize matching on List - val mutableBinders = + val mutableBinders = ( if (!binder.info.typeSymbol.hasTransOwner(ScalaPackageClass) && (paramAccessors exists (_.isMutable))) subPatBinders.zipWithIndex.collect{ case (binder, idx) if paramAccessors(idx).isMutable => binder } else Nil + ) // checks binder ne null before chaining to the next extractor ProductExtractorTreeMaker(binder, lengthGuard(binder))(subPatBinders, subPatRefs(binder), mutableBinders, binderKnownNonNull, ignoredSubPatBinders) @@ -735,7 +657,6 @@ trait MatchTranslation extends CpsPatternHacks { object WildcardPattern { def unapply(pat: Tree): Boolean = pat match { case Bind(nme.WILDCARD, WildcardPattern()) => true // don't skip when binding an interesting symbol! - case Ident(nme.WILDCARD) => true case Star(WildcardPattern()) => true case x: Ident => treeInfo.isVarPattern(x) case Alternative(ps) => ps forall unapply diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchWarnings.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchWarnings.scala new file mode 100644 index 0000000000..a7d7680db1 --- /dev/null +++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchWarnings.scala @@ -0,0 +1,86 @@ +/* NSC -- new Scala compiler + * + * Copyright 2011-2013 LAMP/EPFL + * @author Adriaan Moors + */ + +package scala.tools.nsc.transform.patmat + +import scala.language.postfixOps +import scala.collection.mutable +import scala.reflect.internal.util.Statistics + +trait MatchWarnings { + self: PatternMatching => + + import global._ + + trait TreeMakerWarnings { + self: MatchTranslator => + + import typer.context + + // Why is it so difficult to say "here's a name and a context, give me any + // matching symbol in scope" ? I am sure this code is wrong, but attempts to + // use the scopes of the contexts in the enclosing context chain discover + // nothing. How to associate a name with a symbol would would be a wonderful + // linkage for which to establish a canonical acquisition mechanism. + private def matchingSymbolInScope(pat: Tree): Symbol = { + def declarationOfName(tpe: Type, name: Name): Symbol = tpe match { + case PolyType(tparams, restpe) => tparams find (_.name == name) getOrElse declarationOfName(restpe, name) + case MethodType(params, restpe) => params find (_.name == name) getOrElse declarationOfName(restpe, name) + case ClassInfoType(_, _, clazz) => clazz.rawInfo member name + case _ => NoSymbol + } + pat match { + case Bind(name, _) => + context.enclosingContextChain.foldLeft(NoSymbol: Symbol)((res, ctx) => + res orElse declarationOfName(ctx.owner.rawInfo, name)) + case _ => NoSymbol + } + } + + // Issue better warnings than "unreachable code" when people mis-use + // variable patterns thinking they bind to existing identifiers. + // + // Possible TODO: more deeply nested variable patterns, like + // case (a, b) => 1 ; case (c, d) => 2 + // However this is a pain (at least the way I'm going about it) + // and I have to think these detailed errors are primarily useful + // for beginners, not people writing nested pattern matches. + def checkMatchVariablePatterns(cases: List[CaseDef]) { + // A string describing the first variable pattern + var vpat: String = null + // Using an iterator so we can recognize the last case + val it = cases.iterator + + def addendum(pat: Tree) = { + matchingSymbolInScope(pat) match { + case NoSymbol => "" + case sym => + val desc = if (sym.isParameter) s"parameter ${sym.nameString} of" else sym + " in" + s"\nIf you intended to match against $desc ${sym.owner}, you must use backticks, like: case `${sym.nameString}` =>" + } + } + + while (it.hasNext) { + val cdef = it.next() + // If a default case has been seen, then every succeeding case is unreachable. + if (vpat != null) + context.unit./*error*/warning(cdef.body.pos, "unreachable code due to " + vpat + addendum(cdef.pat)) + // If this is a default case and more cases follow, warn about this one so + // we have a reason to mention its pattern variable name and any corresponding + // symbol in scope. Errors will follow from the remaining cases, at least + // once we make the above warning an error. + else if (it.hasNext && (treeInfo isDefaultCase cdef)) { + val vpatName = cdef.pat match { + case Bind(name, _) => s" '$name'" + case _ => "" + } + vpat = s"variable pattern$vpatName on line ${cdef.pat.pos.line}" + context.unit.warning(cdef.pos, s"patterns after a variable pattern cannot match (SLS 8.1.1)" + addendum(cdef.pat)) + } + } + } + } +} \ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala b/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala index 616abaaf3b..f3514b2d06 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala @@ -43,7 +43,8 @@ trait PatternMatching extends Transform with TypingTransformers with ScalaLogic with Solving with MatchAnalysis - with MatchOptimization { + with MatchOptimization + with MatchWarnings { import global._ val phaseName: String = "patmat" -- cgit v1.2.3 From 22b82a485a53ffad3490673a481fbd79d616ed71 Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Sat, 17 Aug 2013 22:18:21 -0700 Subject: Finish segregating patmat cps hacks. --- .../tools/nsc/transform/patmat/MatchCps.scala | 37 ++++++++++++++++++++++ .../nsc/transform/patmat/MatchTranslation.scala | 31 +----------------- .../nsc/transform/patmat/PatternMatching.scala | 1 + 3 files changed, 39 insertions(+), 30 deletions(-) create mode 100644 src/compiler/scala/tools/nsc/transform/patmat/MatchCps.scala (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchCps.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchCps.scala new file mode 100644 index 0000000000..0d08120e43 --- /dev/null +++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchCps.scala @@ -0,0 +1,37 @@ +/* NSC -- new Scala compiler + * + * Copyright 2011-2013 LAMP/EPFL + * @author Adriaan Moors + */ + +package scala.tools.nsc.transform.patmat + +/** Segregating this super hacky CPS code. */ +trait MatchCps { + self: PatternMatching => + + import global._ + + // duplicated from CPSUtils (avoid dependency from compiler -> cps plugin...) + private object CpsSymbols { + private def cpsSymbol(name: String) = rootMirror.getClassIfDefined(s"scala.util.continuations.$name") + + val MarkerCPSAdaptPlus = cpsSymbol("cpsPlus") + val MarkerCPSAdaptMinus = cpsSymbol("cpsMinus") + val MarkerCPSSynth = cpsSymbol("cpsSynth") + val MarkerCPSTypes = cpsSymbol("cpsParam") + val stripTriggerCPSAnns = Set[Symbol](MarkerCPSSynth, MarkerCPSAdaptMinus, MarkerCPSAdaptPlus) + val strippedCPSAnns = stripTriggerCPSAnns + MarkerCPSTypes + + // when one of the internal cps-type-state annotations is present, strip all CPS annotations + // a cps-type-state-annotated type makes no sense as an expected type (matchX.tpe is used as pt in translateMatch) + // (only test availability of MarkerCPSAdaptPlus assuming they are either all available or none of them are) + def removeCPSFromPt(pt: Type): Type = ( + if (MarkerCPSAdaptPlus.exists && (stripTriggerCPSAnns exists pt.hasAnnotation)) + pt filterAnnotations (ann => !(strippedCPSAnns exists ann.matches)) + else + pt + ) + } + def removeCPSFromPt(pt: Type): Type = CpsSymbols removeCPSFromPt pt +} diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala index a91f24ef0e..282a78492a 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala @@ -10,38 +10,9 @@ import scala.language.postfixOps import scala.collection.mutable import scala.reflect.internal.util.Statistics - -/** Segregating this super hacky code. */ -trait CpsPatternHacks { - self: PatternMatching => - - import global._ - - // duplicated from CPSUtils (avoid dependency from compiler -> cps plugin...) - private object CpsSymbols { - val MarkerCPSAdaptPlus = rootMirror.getClassIfDefined("scala.util.continuations.cpsPlus") - val MarkerCPSAdaptMinus = rootMirror.getClassIfDefined("scala.util.continuations.cpsMinus") - val MarkerCPSSynth = rootMirror.getClassIfDefined("scala.util.continuations.cpsSynth") - val MarkerCPSTypes = rootMirror.getClassIfDefined("scala.util.continuations.cpsParam") - val stripTriggerCPSAnns = Set[Symbol](MarkerCPSSynth, MarkerCPSAdaptMinus, MarkerCPSAdaptPlus) - val strippedCPSAnns = stripTriggerCPSAnns + MarkerCPSTypes - - // when one of the internal cps-type-state annotations is present, strip all CPS annotations - // a cps-type-state-annotated type makes no sense as an expected type (matchX.tpe is used as pt in translateMatch) - // (only test availability of MarkerCPSAdaptPlus assuming they are either all available or none of them are) - def removeCPSFromPt(pt: Type): Type = ( - if (MarkerCPSAdaptPlus.exists && (stripTriggerCPSAnns exists pt.hasAnnotation)) - pt filterAnnotations (ann => !(strippedCPSAnns exists ann.matches)) - else - pt - ) - } - def removeCPSFromPt(pt: Type): Type = CpsSymbols removeCPSFromPt pt -} - /** Translate typed Trees that represent pattern matches into the patternmatching IR, defined by TreeMakers. */ -trait MatchTranslation extends CpsPatternHacks { +trait MatchTranslation { self: PatternMatching => import PatternMatchingStats._ diff --git a/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala b/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala index f3514b2d06..21666ed0ec 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala @@ -40,6 +40,7 @@ trait PatternMatching extends Transform with TypingTransformers with MatchTranslation with MatchTreeMaking with MatchCodeGen + with MatchCps with ScalaLogic with Solving with MatchAnalysis -- cgit v1.2.3 From a905d0e7e49bf92f119b2fdcd2b9d15b71d64ca2 Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Sun, 18 Aug 2013 08:39:07 -0700 Subject: Revert "Minor improvement in pattern typer inference." This reverts commit 35122d6cda84bb2df69ca51c6b1b80e61693bf6f. It also includes a test case embodying the reversion reason: the test case no longer compiled. Parties interested in the surrounding details may want to look at SI-7472. --- .../scala/tools/nsc/typechecker/Infer.scala | 21 +++++++++------------ test/files/pos/patmat-extract-tparam.scala | 13 +++++++++++++ 2 files changed, 22 insertions(+), 12 deletions(-) create mode 100644 test/files/pos/patmat-extract-tparam.scala (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/typechecker/Infer.scala b/src/compiler/scala/tools/nsc/typechecker/Infer.scala index b199176d90..50d88d7c4d 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Infer.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Infer.scala @@ -1157,6 +1157,7 @@ trait Infer extends Checkable { val tpparams = freeTypeParamsOfTerms(pattp) def ptMatchesPattp = pt matchesPattern pattp.widen + def pattpMatchesPt = pattp matchesPattern pt /* If we can absolutely rule out a match we can fail early. * This is the case if the scrutinee has no unresolved type arguments @@ -1166,15 +1167,9 @@ trait Infer extends Checkable { IncompatibleScrutineeTypeError(tree0, pattp, pt) return ErrorType } - // This performs the "reverse" propagation of type information already used - // in pattern matcher checkability testing. See pos/t2486.scala for sample - // code which would not compile without such propagation. - def propagated = propagateKnownTypes(pt, pattp.widen.typeSymbol) - checkCheckable(tree0, pattp, pt0, inPattern = true, canRemedy) + checkCheckable(tree0, pattp, pt, inPattern = true, canRemedy) if (pattp <:< pt) () - else if (pattp <:< propagated) - log(s"!($pattp <:< $pt), but after propagateKnownTypes we find ($pattp <:< $propagated) - pattern inference improved") else { debuglog("free type params (1) = " + tpparams) @@ -1191,7 +1186,9 @@ trait Infer extends Checkable { val ptvars = ptparams map freshVar val pt1 = pt.instantiateTypeParams(ptparams, ptvars) - if (isPopulated(tp, pt1) && isInstantiatable(tvars ++ ptvars)) + // See ticket #2486 for an example of code which would incorrectly + // fail if we didn't allow for pattpMatchesPt. + if (isPopulated(tp, pt1) && isInstantiatable(tvars ++ ptvars) || pattpMatchesPt) ptvars foreach instantiateTypeVar else { PatternTypeIncompatibleWithPtError1(tree0, pattp, pt) @@ -1244,10 +1241,10 @@ trait Infer extends Checkable { // properly, we can avoid it by ignoring type parameters which // have type constructors amongst their bounds. See SI-4070. def isFreeTypeParamOfTerm(sym: Symbol) = ( - sym.isAbstractType - && sym.owner.isTerm - && !sym.info.bounds.exists(_.typeParams.nonEmpty) - ) + sym.isAbstractType + && sym.owner.isTerm + && !sym.info.bounds.exists(_.typeParams.nonEmpty) + ) // Intentionally *not* using `Type#typeSymbol` here, which would normalize `tp` // and collect symbols from the result type of any resulting `PolyType`s, which diff --git a/test/files/pos/patmat-extract-tparam.scala b/test/files/pos/patmat-extract-tparam.scala new file mode 100644 index 0000000000..6417b49c2b --- /dev/null +++ b/test/files/pos/patmat-extract-tparam.scala @@ -0,0 +1,13 @@ +trait Bip[T] { def h: T } +trait BoolBip extends Bip[Boolean] + +class A { + def g(x: Boolean): Unit = () + def f(xs: List[Bip[_]]) = xs foreach { case x: BoolBip => g(x.h) } +} + +class B { + def g(x: Boolean): Unit = () + def g(x: Int): Unit = () + def f(xs: List[Bip[_]]) = xs foreach { case x: BoolBip => g(x.h) } +} -- cgit v1.2.3 From 6d77da374e94ea8b80fc0bf9e544e11f4e9d5cc8 Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Sun, 18 Aug 2013 09:29:44 -0700 Subject: Refined name-based patmat methods. This fleshes out some of the slightly unfinished corners of the adventure, especially for unapplySeq. There's still an unhealthy amount of duplication and a paucity of specification, but I think it's in eminently good shape for a milestone. --- .../tools/nsc/transform/patmat/MatchCodeGen.scala | 6 +- .../nsc/transform/patmat/MatchTranslation.scala | 46 ++++++------- .../nsc/transform/patmat/PatternMatching.scala | 36 +--------- .../tools/nsc/typechecker/PatternTypers.scala | 40 ++++++----- .../scala/reflect/internal/Definitions.scala | 79 ++++++++++++++-------- test/files/neg/t4425b.check | 12 ++-- 6 files changed, 108 insertions(+), 111 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchCodeGen.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchCodeGen.scala index 52055dea85..2bd14d923a 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/MatchCodeGen.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchCodeGen.scala @@ -87,10 +87,8 @@ trait MatchCodeGen extends Interface { def _match(n: Name): SelectStart = matchStrategy DOT n // TODO: error message - private lazy val oneType = typer.typedOperator(_match(vpmName.one)).tpe - private def oneApplied(tp: Type): Type = appliedType(oneType, tp :: Nil) - override def pureType(tp: Type): Type = firstParamType(oneApplied(tp)) - override def mapResultType(prev: Type, elem: Type): Type = oneApplied(elem).finalResultType + private lazy val oneType = typer.typedOperator(_match(vpmName.one)).tpe + override def pureType(tp: Type): Type = firstParamType(appliedType(oneType, tp :: Nil)) } trait PureCodegen extends CodegenCore with PureMatchMonadInterface { diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala index 282a78492a..d4bbef740c 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala @@ -20,6 +20,7 @@ trait MatchTranslation { import definitions._ import global.analyzer.{ErrorUtils, formalTypes} import treeInfo.{ WildcardStarArg, Unapplied, isStar, unbind } + import CODE._ // Always map repeated params to sequences private def setVarInfo(sym: Symbol, info: Type) = @@ -252,7 +253,7 @@ trait MatchTranslation { CaseDef( Bind(exSym, Ident(nme.WILDCARD)), // TODO: does this need fixing upping? EmptyTree, - combineCasesNoSubstOnly(CODE.REF(exSym), scrutSym, casesNoSubstOnly, pt, matchOwner, Some(scrut => Throw(CODE.REF(exSym)))) + combineCasesNoSubstOnly(REF(exSym), scrutSym, casesNoSubstOnly, pt, matchOwner, Some(scrut => Throw(REF(exSym)))) ) }) } @@ -364,8 +365,6 @@ trait MatchTranslation { } abstract class ExtractorCall { - import CODE._ - def fun: Tree def args: List[Tree] @@ -380,12 +379,16 @@ trait MatchTranslation { private def hasStar = nbSubPats > 0 && isStar(args.last) private def isNonEmptySeq = nbSubPats > 0 && isSeq - def isSingle = nbSubPats == 0 && !isSeq + /** This is special cased so that a single pattern will accept any extractor + * result, even if it's a tuple (SI-6675) + */ + def isSingle = nbSubPats == 1 && !isSeq // to which type should the previous binder be casted? def paramType : Type protected def rawSubPatTypes: List[Type] + protected def resultType: Type /** Create the TreeMaker that embodies this extractor call * @@ -412,16 +415,16 @@ trait MatchTranslation { def subPatTypes: List[Type] = ( if (rawSubPatTypes.isEmpty || !isSeq) rawSubPatTypes - else if (hasStar) nonStarSubPatTypes :+ rawLast + else if (hasStar) nonStarSubPatTypes :+ sequenceType else nonStarSubPatTypes ) + private def rawGet = typeOfMemberNamedGetOrSelf(resultType) private def emptySub = rawSubPatTypes.isEmpty - private def rawLast = if (emptySub) NothingTpe else rawSubPatTypes.last private def rawInit = rawSubPatTypes dropRight 1 - protected def sequenceType = if (emptySub) NothingTpe else rawLast - protected def elementType = if (emptySub) NothingTpe else unapplySeqElementType(rawLast) - protected def repeatedType = if (emptySub) NothingTpe else scalaRepeatedType(elementType) + protected def sequenceType = typeOfLastSelectorOrSelf(rawGet) + protected def elementType = elementTypeOfLastSelectorOrSelf(rawGet) + protected def repeatedType = scalaRepeatedType(elementType) // rawSubPatTypes.last is the Seq, thus there are `rawSubPatTypes.length - 1` non-seq elements in the tuple protected def firstIndexingBinder = rawSubPatTypes.length - 1 @@ -508,6 +511,7 @@ trait MatchTranslation { // to which type should the previous binder be casted? def paramType = constructorTp.finalResultType + def resultType = fun.tpe.finalResultType def isSeq = isVarArgTypes(rawSubPatTypes) @@ -536,7 +540,7 @@ trait MatchTranslation { } // reference the (i-1)th case accessor if it exists, otherwise the (i-1)th tuple component - override protected def tupleSel(binder: Symbol)(i: Int): Tree = { import CODE._ + override protected def tupleSel(binder: Symbol)(i: Int): Tree = { val accessors = binder.caseFieldAccessors if (accessors isDefinedAt (i-1)) REF(binder) DOT accessors(i-1) else codegen.tupleSel(binder)(i) // this won't type check for case classes, as they do not inherit ProductN @@ -550,7 +554,7 @@ trait MatchTranslation { def tpe = fun.tpe def paramType = firstParamType(tpe) - def resultType = fun.tpe.finalResultType + def resultType = tpe.finalResultType def isTyped = (tpe ne NoType) && fun.isTyped && (resultInMonad ne ErrorType) def isSeq = fun.symbol.name == nme.unapplySeq def isBool = resultType =:= BooleanTpe @@ -585,20 +589,20 @@ trait MatchTranslation { } override protected def seqTree(binder: Symbol): Tree = - if (firstIndexingBinder == 0) CODE.REF(binder) + if (firstIndexingBinder == 0) REF(binder) else super.seqTree(binder) // the trees that select the subpatterns on the extractor's result, referenced by `binder` // require (nbSubPats > 0 && (!lastIsStar || isSeq)) override protected def subPatRefs(binder: Symbol): List[Tree] = - if (!isSeq && nbSubPats == 1) List(CODE.REF(binder)) // special case for extractors + if (isSingle) REF(binder) :: Nil // special case for extractors else super.subPatRefs(binder) protected def spliceApply(binder: Symbol): Tree = { object splice extends Transformer { override def transform(t: Tree) = t match { case Apply(x, List(i @ Ident(nme.SELECTOR_DUMMY))) => - treeCopy.Apply(t, x, List(CODE.REF(binder) setPos i.pos)) + treeCopy.Apply(t, x, (REF(binder) setPos i.pos) :: Nil) case _ => super.transform(t) } @@ -606,20 +610,16 @@ trait MatchTranslation { splice transform extractorCallIncludingDummy } - // what's the extractor's result type in the monad? - // turn an extractor's result type into something `monadTypeToSubPatTypesAndRefs` understands - protected lazy val resultInMonad: Type = if (isBool) UnitTpe else matchMonadResult(resultType) // the type of "get" + // what's the extractor's result type in the monad? It is the type of its nullary member `get`. + protected lazy val resultInMonad: Type = if (isBool) UnitTpe else typeOfMemberNamedGet(resultType) protected lazy val rawSubPatTypes = ( if (isBool) Nil - else if (!isSeq && nbSubPats == 1) resultInMonad :: Nil - else getNameBasedProductSelectorTypes(resultInMonad) match { - case Nil => resultInMonad :: Nil - case x => x - } + else if (isSingle) resultInMonad :: Nil // don't go looking for selectors if we only expect one pattern + else typesOfSelectorsOrSelf(resultInMonad) ) - override def toString() = s"ExtractorCallRegular($fun:${fun.tpe} / ${fun.symbol})" + override def toString() = s"ExtractorCallRegular($fun: $tpe / ${fun.symbol})" } /** A conservative approximation of which patterns do not discern anything. diff --git a/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala b/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala index 21666ed0ec..a4944caa2b 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala @@ -175,40 +175,8 @@ trait Interface extends ast.TreeDSL { val matchOwner = typer.context.owner def pureType(tp: Type): Type = tp - // Extracting from the monad: tp == Option[T], result == T - def matchMonadResult(tp: Type) = definitions typeOfMemberNamedGet tp - - // prev == CC[T] - // elem == U - // result == CC[U] - // where "CC" here is Option or any other single-type-parameter container - // - // TODO - what if it has multiple type parameters? - // If we have access to the zero, maybe we can infer the - // type parameter by contrasting with the zero's application. - def mapResultType(prev: Type, elem: Type): Type = { - // default to Option[U] if we can't reliably infer the types - def fallback(elem: Type): Type = elem match { - case TypeRef(_, sym, _) if sym.isTypeParameterOrSkolem => fallback(sym.info.bounds.hi) - case _ => optionType(elem) - } - - // optionType(elem) //pack(elem)) - // The type of "get" in CC[T] is what settles what was wrapped. - val prevElem = matchMonadResult(prev) - if (prevElem =:= elem) prev - else prev.typeArgs match { - case targ :: Nil if targ =:= prevElem => - // the type of "get" in the result should be elem. - // If not, the type arguments are doing something nonobvious - // so fall back on Option. - val result = appliedType(prev.typeConstructor, elem :: Nil) - val newElem = matchMonadResult(result) - if (elem =:= newElem) result else fallback(newElem) - case _ => - fallback(AnyTpe) - } - } + // Extracting from the monad: tp == { def get: T }, result == T + def matchMonadResult(tp: Type) = typeOfMemberNamedGet(tp) def reportUnreachable(pos: Position) = typer.context.unit.warning(pos, "unreachable code") def reportMissingCases(pos: Position, counterExamples: List[String]) = { diff --git a/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala b/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala index cc0ffe2ac2..7120aeaaa6 100644 --- a/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala @@ -190,13 +190,12 @@ trait PatternTypers { def resultType = tpe.finalResultType def method = unapplyMember(tpe) def paramType = firstParamType(unapplyType) - def rawGet = if (isBool) UnitTpe else resultOfMatchingMethod(resultType, "get")() - def rawTypes = if (isBool) Nil else if (rawProduct.isEmpty) rawGet :: Nil else rawProduct + def rawGet = if (isBool) UnitTpe else typeOfMemberNamedGetOrSelf(resultType) + def rawTypes = if (isBool) Nil else typesOfSelectorsOrSelf(rawGet) def rawArity = rawTypes.size def isBool = resultType =:= BooleanTpe // aka "Tuple0" or "Option[Unit]" + def isNothing = rawGet =:= NothingTpe def isCase = method.isCase - - private def rawProduct = getNameBasedProductSelectorTypes(rawGet) } object NoUnapplyMethodInfo extends UnapplyMethodInfo(NoSymbol, NoType) { @@ -213,7 +212,7 @@ trait PatternTypers { case _ => NoCaseClassInfo } val exInfo = UnapplyMethodInfo(symbol, tpe) - import exInfo.{ rawTypes, isUnapplySeq, rawGet } + import exInfo.{ rawGet, rawTypes, isUnapplySeq } override def toString = s"ExtractorShape($fun, $args)" @@ -223,15 +222,23 @@ trait PatternTypers { def caseClass = ccInfo.clazz def enclClass = symbol.enclClass + // TODO - merge these. The difference between these two methods is that expectedPatternTypes + // expands the list of types so it is the same length as the number of patterns, whereas formals + // leaves the varargs type unexpanded. def formals = ( if (isUnapplySeq) productTypes :+ varargsType else if (elementArity == 0) productTypes - else if (patternFixedArity == 1) squishIntoOne() + else if (isSingle) squishIntoOne() else wrongArity(patternFixedArity) ) + def expectedPatternTypes = elementArity match { + case 0 => productTypes + case _ if elementArity > 0 && isUnapplySeq => productTypes ::: elementTypes + case _ if productArity > 1 && patternFixedArity == 1 => squishIntoOne() + case _ => wrongArity(patternFixedArity) + } - def rawLast = if (rawTypes.isEmpty) rawGet else rawTypes.last - def elementType = unapplySeqElementType(rawLast) + def elementType = elementTypeOfLastSelectorOrSelf(rawGet) private def hasBogusExtractor = directUnapplyMember(tpe).exists && !unapplyMethod.exists private def expectedArity = "" + productArity + ( if (isUnapplySeq) "+" else "") @@ -254,20 +261,21 @@ trait PatternTypers { rawGet :: Nil } + // elementArity is the number of non-sequence patterns minus the + // the number of non-sequence product elements returned by the extractor. + // If it is zero, there is a perfect match between those parts, and + // if there is a wildcard star it will match any sequence. + // If it is positive, there are more patterns than products, + // so a sequence will have to fill in the elements. If it is negative, + // there are more products than patterns, which is a compile time error. + def elementArity = patternFixedArity - productArity def patternFixedArity = treeInfo effectivePatternArity args def productArity = productTypes.size - def elementArity = patternFixedArity - productArity + def isSingle = !isUnapplySeq && (patternFixedArity == 1) def productTypes = if (isUnapplySeq) rawTypes dropRight 1 else rawTypes def elementTypes = List.fill(elementArity)(elementType) def varargsType = scalaRepeatedType(elementType) - - def expectedPatternTypes = elementArity match { - case 0 => productTypes - case _ if elementArity > 0 && exInfo.isUnapplySeq => productTypes ::: elementTypes - case _ if productArity > 1 && patternFixedArity == 1 => squishIntoOne() - case _ => wrongArity(patternFixedArity) - } } private class VariantToSkolemMap extends TypeMap(trackVariance = true) { diff --git a/src/reflect/scala/reflect/internal/Definitions.scala b/src/reflect/scala/reflect/internal/Definitions.scala index f1480c6cbd..19458361e1 100644 --- a/src/reflect/scala/reflect/internal/Definitions.scala +++ b/src/reflect/scala/reflect/internal/Definitions.scala @@ -681,41 +681,23 @@ trait Definitions extends api.StandardDefinitions { def isExactProductType(tp: Type): Boolean = isProductNSymbol(tp.typeSymbol) /** if tpe <: ProductN[T1,...,TN], returns List(T1,...,TN) else Nil */ - def getProductArgs(tpe: Type): List[Type] = tpe.baseClasses find isProductNSymbol match { + @deprecated("No longer used", "2.11.0") def getProductArgs(tpe: Type): List[Type] = tpe.baseClasses find isProductNSymbol match { case Some(x) => tpe.baseType(x).typeArgs case _ => Nil } - def getNameBasedProductSelectors(tpe: Type): List[Symbol] = { - def loop(n: Int): List[Symbol] = tpe member TermName("_" + n) match { - case NoSymbol => Nil - case m if m.paramss.nonEmpty => Nil - case m => m :: loop(n + 1) - } - loop(1) - } - def getNameBasedProductSelectorTypes(tpe: Type): List[Type] = getProductArgs(tpe) match { - case xs if xs.nonEmpty => xs - case _ => getterMemberTypes(tpe, getNameBasedProductSelectors(tpe)) + + @deprecated("No longer used", "2.11.0") def unapplyUnwrap(tpe:Type) = tpe.finalResultType.dealiasWiden match { + case RefinedType(p :: _, _) => p.dealiasWiden + case tp => tp } def getterMemberTypes(tpe: Type, getters: List[Symbol]): List[Type] = getters map (m => dropNullaryMethod(tpe memberType m)) - def getNameBasedProductSeqElementType(tpe: Type) = getNameBasedProductSelectorTypes(tpe) match { - case _ :+ elem => unapplySeqElementType(elem) - case _ => NoType - } - def dropNullaryMethod(tp: Type) = tp match { case NullaryMethodType(restpe) => restpe case _ => tp } - - def unapplyUnwrap(tpe:Type) = tpe.finalResultType.dealiasWiden match { - case RefinedType(p :: _, _) => p.dealiasWiden - case tp => tp - } - def abstractFunctionForFunctionType(tp: Type) = { assert(isFunctionType(tp), tp) abstractFunctionType(tp.typeArgs.init, tp.typeArgs.last) @@ -738,13 +720,54 @@ trait Definitions extends api.StandardDefinitions { def scalaRepeatedType(arg: Type) = appliedType(RepeatedParamClass, arg) def seqType(arg: Type) = appliedType(SeqClass, arg) - def typeOfMemberNamedGet(tp: Type) = resultOfMatchingMethod(tp, nme.get)() - - def unapplySeqElementType(seqType: Type) = ( - resultOfMatchingMethod(seqType, nme.apply)(IntTpe) - orElse resultOfMatchingMethod(seqType, nme.head)() + // FYI the long clunky name is because it's really hard to put "get" into the + // name of a method without it sounding like the method "get"s something, whereas + // this method is about a type member which just happens to be named get. + def typeOfMemberNamedGet(tp: Type) = resultOfMatchingMethod(tp, nme.get)() + def typeOfMemberNamedHead(tp: Type) = resultOfMatchingMethod(tp, nme.head)() + def typeOfMemberNamedApply(tp: Type) = resultOfMatchingMethod(tp, nme.apply)() + def typeOfMemberNamedGetOrSelf(tp: Type) = typeOfMemberNamedGet(tp) orElse tp + def typesOfSelectors(tp: Type) = getterMemberTypes(tp, productSelectors(tp)) + def typesOfCaseAccessors(tp: Type) = getterMemberTypes(tp, tp.typeSymbol.caseFieldAccessors) + + /** If this is a case class, the case field accessors (which may be an empty list.) + * Otherwise, if there are any product selectors, that list. + * Otherwise, a list containing only the type itself. + */ + def typesOfSelectorsOrSelf(tp: Type): List[Type] = ( + if (tp.typeSymbol.isCase) + typesOfCaseAccessors(tp) + else typesOfSelectors(tp) match { + case Nil => tp :: Nil + case tps => tps + } ) + /** If the given type has one or more product selectors, the type of the last one. + * Otherwise, the type itself. + */ + def typeOfLastSelectorOrSelf(tp: Type) = typesOfSelectorsOrSelf(tp).last + + def elementTypeOfLastSelectorOrSelf(tp: Type) = { + val last = typeOfLastSelectorOrSelf(tp) + ( typeOfMemberNamedHead(last) + orElse typeOfMemberNamedApply(last) + orElse elementType(ArrayClass, last) + ) + } + + /** Returns the method symbols for members _1, _2, ..., _N + * which exist in the given type. + */ + def productSelectors(tpe: Type): List[Symbol] = { + def loop(n: Int): List[Symbol] = tpe member TermName("_" + n) match { + case NoSymbol => Nil + case m if m.paramss.nonEmpty => Nil + case m => m :: loop(n + 1) + } + loop(1) + } + /** If `tp` has a term member `name`, the first parameter list of which * matches `paramTypes`, and which either has no further parameter * lists or only an implicit one, then the result type of the matching diff --git a/test/files/neg/t4425b.check b/test/files/neg/t4425b.check index 3af3027da1..1186e8b609 100644 --- a/test/files/neg/t4425b.check +++ b/test/files/neg/t4425b.check @@ -22,22 +22,22 @@ t4425b.scala:10: error: object X is not a case class constructor, nor does it ha Note: def unapply(x: String)(y: String): Nothing exists in object X, but it cannot be used as an extractor due to its second non-implicit parameter list println((X: Any) match { case X(_, _) => "ok" ; case _ => "fail" }) ^ -t4425b.scala:18: error: wrong number of patterns for object X offering : expected 1, found 2 +t4425b.scala:18: error: wrong number of patterns for object X offering Nothing: expected 1, found 2 println( "" match { case _ X _ => "ok" ; case _ => "fail" }) ^ -t4425b.scala:19: error: wrong number of patterns for object X offering : expected 1, found 2 +t4425b.scala:19: error: wrong number of patterns for object X offering Nothing: expected 1, found 2 println((X: Any) match { case _ X _ => "ok" ; case _ => "fail" }) ^ -t4425b.scala:22: error: wrong number of patterns for object X offering : expected 1, found 2 +t4425b.scala:22: error: wrong number of patterns for object X offering Nothing: expected 1, found 2 println( "" match { case X(_, _) => "ok" ; case _ => "fail" }) ^ -t4425b.scala:22: error: wrong number of patterns for object X offering : expected 1, found 2 +t4425b.scala:22: error: wrong number of patterns for object X offering Nothing: expected 1, found 2 println( "" match { case X(_, _) => "ok" ; case _ => "fail" }) ^ -t4425b.scala:23: error: wrong number of patterns for object X offering : expected 1, found 2 +t4425b.scala:23: error: wrong number of patterns for object X offering Nothing: expected 1, found 2 println((X: Any) match { case X(_, _) => "ok" ; case _ => "fail" }) ^ -t4425b.scala:23: error: wrong number of patterns for object X offering : expected 1, found 2 +t4425b.scala:23: error: wrong number of patterns for object X offering Nothing: expected 1, found 2 println((X: Any) match { case X(_, _) => "ok" ; case _ => "fail" }) ^ t4425b.scala:31: error: wrong number of patterns for object X offering Nothing: expected 1, found 2 -- cgit v1.2.3 From b3d9dfa9857aeb937a987536b3e2029d3be0030b Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Sun, 18 Aug 2013 15:14:47 -0700 Subject: An unapplySeq-via-String test. There are a lot of details yet to be ironed out when it comes to sequences, but at least here's a little evidence that the basic mechanisms work. --- .../tools/nsc/transform/patmat/MatchCodeGen.scala | 13 +++++++++- .../scala/reflect/internal/Definitions.scala | 3 ++- test/files/run/string-extractor.check | 4 +++ test/files/run/string-extractor.scala | 30 ++++++++++++++++++++++ 4 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 test/files/run/string-extractor.check create mode 100644 test/files/run/string-extractor.scala (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchCodeGen.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchCodeGen.scala index 2bd14d923a..8de8eb7d92 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/MatchCodeGen.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchCodeGen.scala @@ -65,7 +65,18 @@ trait MatchCodeGen extends Interface { def fun(arg: Symbol, body: Tree): Tree = Function(List(ValDef(arg)), body) def tupleSel(binder: Symbol)(i: Int): Tree = (REF(binder) DOT nme.productAccessorName(i)) // make tree that accesses the i'th component of the tuple referenced by binder def index(tgt: Tree)(i: Int): Tree = tgt APPLY (LIT(i)) - def drop(tgt: Tree)(n: Int): Tree = gen.mkMethodCall(traversableDropMethod, tgt :: LIT(n) :: Nil) + + private def definesDrop(tgt: Tree) = (tgt.tpe ne null) && (typeOfMemberNamedDrop(tgt.tpe) != NoType) + + // Right now this calls a direct drop member if it sees one, otherwise calls + // into the drop helper in ScalaRunTime. You should not actually have to write + // a method called drop for things to work, it's just not finished yet. + def drop(tgt: Tree)(n: Int): Tree = ( + if (definesDrop(tgt)) + Apply(Select(tgt, nme.drop), LIT(n) :: Nil) + else + gen.mkMethodCall(traversableDropMethod, tgt :: LIT(n) :: Nil) + ) // NOTE: checker must be the target of the ==, that's the patmat semantics for ya def _equals(checker: Tree, binder: Symbol): Tree = checker MEMBER_== REF(binder) diff --git a/src/reflect/scala/reflect/internal/Definitions.scala b/src/reflect/scala/reflect/internal/Definitions.scala index 19458361e1..4aca81bedd 100644 --- a/src/reflect/scala/reflect/internal/Definitions.scala +++ b/src/reflect/scala/reflect/internal/Definitions.scala @@ -725,7 +725,8 @@ trait Definitions extends api.StandardDefinitions { // this method is about a type member which just happens to be named get. def typeOfMemberNamedGet(tp: Type) = resultOfMatchingMethod(tp, nme.get)() def typeOfMemberNamedHead(tp: Type) = resultOfMatchingMethod(tp, nme.head)() - def typeOfMemberNamedApply(tp: Type) = resultOfMatchingMethod(tp, nme.apply)() + def typeOfMemberNamedApply(tp: Type) = resultOfMatchingMethod(tp, nme.apply)(IntTpe) + def typeOfMemberNamedDrop(tp: Type) = resultOfMatchingMethod(tp, nme.drop)(IntTpe) def typeOfMemberNamedGetOrSelf(tp: Type) = typeOfMemberNamedGet(tp) orElse tp def typesOfSelectors(tp: Type) = getterMemberTypes(tp, productSelectors(tp)) def typesOfCaseAccessors(tp: Type) = getterMemberTypes(tp, tp.typeSymbol.caseFieldAccessors) diff --git a/test/files/run/string-extractor.check b/test/files/run/string-extractor.check new file mode 100644 index 0000000000..7268e44da9 --- /dev/null +++ b/test/files/run/string-extractor.check @@ -0,0 +1,4 @@ +by +BY +oTheClown +nope diff --git a/test/files/run/string-extractor.scala b/test/files/run/string-extractor.scala new file mode 100644 index 0000000000..4fb977df0b --- /dev/null +++ b/test/files/run/string-extractor.scala @@ -0,0 +1,30 @@ +final class StringExtract(val s: String) extends AnyVal { + def isEmpty = (s eq null) || (s == "") + def get = this + def length = s.length + def lengthCompare(n: Int) = s.length compare n + def apply(idx: Int): Char = s charAt idx + def head: Char = s charAt 0 + def tail: String = s drop 1 + def drop(n: Int): StringExtract = new StringExtract(s drop n) + + override def toString = s +} + +object Bippy { + def unapplySeq(x: Any): StringExtract = new StringExtract("" + x) +} + +object Test { + def f(x: Any) = x match { + case Bippy('B' | 'b', 'O' | 'o', 'B' | 'b', xs @ _*) => xs + case _ => "nope" + } + + def main(args: Array[String]): Unit = { + println(f("Bobby")) + println(f("BOBBY")) + println(f("BoBoTheClown")) + println(f("TomTomTheClown")) + } +} -- cgit v1.2.3 From 6d4e71c111226591a4eeb5b77efac689ef1dd79a Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Sun, 18 Aug 2013 15:36:18 -0700 Subject: Refinement of name-based unapplySeq. Can't finnesse the drop method. Call it blindly for now, even though in the long run you won't have to write drop. --- .../tools/nsc/transform/patmat/MatchCodeGen.scala | 22 ++++++++-------- test/files/run/string-extractor.check | 5 ++++ test/files/run/string-extractor.scala | 30 ++++++++++++++++++++++ 3 files changed, 46 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchCodeGen.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchCodeGen.scala index 8de8eb7d92..1b49b335c8 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/MatchCodeGen.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchCodeGen.scala @@ -66,17 +66,17 @@ trait MatchCodeGen extends Interface { def tupleSel(binder: Symbol)(i: Int): Tree = (REF(binder) DOT nme.productAccessorName(i)) // make tree that accesses the i'th component of the tuple referenced by binder def index(tgt: Tree)(i: Int): Tree = tgt APPLY (LIT(i)) - private def definesDrop(tgt: Tree) = (tgt.tpe ne null) && (typeOfMemberNamedDrop(tgt.tpe) != NoType) - - // Right now this calls a direct drop member if it sees one, otherwise calls - // into the drop helper in ScalaRunTime. You should not actually have to write - // a method called drop for things to work, it's just not finished yet. - def drop(tgt: Tree)(n: Int): Tree = ( - if (definesDrop(tgt)) - Apply(Select(tgt, nme.drop), LIT(n) :: Nil) - else - gen.mkMethodCall(traversableDropMethod, tgt :: LIT(n) :: Nil) - ) + // Right now this blindly calls drop on the result of the unapplySeq + // unless it verifiably has no drop method (this is the case in particular + // with Array.) You should not actually have to write a method called drop + // for name-based matching, but this was an expedient route for the basics. + def drop(tgt: Tree)(n: Int): Tree = { + def callDirect = fn(tgt, nme.drop, LIT(n)) + def callRuntime = Apply(REF(traversableDropMethod), tgt :: LIT(n) :: Nil) + def needsRuntime = (tgt.tpe ne null) && (typeOfMemberNamedDrop(tgt.tpe) == NoType) + + if (needsRuntime) callRuntime else callDirect + } // NOTE: checker must be the target of the ==, that's the patmat semantics for ya def _equals(checker: Tree, binder: Symbol): Tree = checker MEMBER_== REF(binder) diff --git a/test/files/run/string-extractor.check b/test/files/run/string-extractor.check index 7268e44da9..47f3722c86 100644 --- a/test/files/run/string-extractor.check +++ b/test/files/run/string-extractor.check @@ -2,3 +2,8 @@ by BY oTheClown nope +1: ob +2: obby +2: OBBY +3: BOBO +3: TomTomTheClown diff --git a/test/files/run/string-extractor.scala b/test/files/run/string-extractor.scala index 4fb977df0b..c0fe911ff3 100644 --- a/test/files/run/string-extractor.scala +++ b/test/files/run/string-extractor.scala @@ -11,9 +11,26 @@ final class StringExtract(val s: String) extends AnyVal { override def toString = s } +final class ThreeStringExtract(val s: String) extends AnyVal { + def isEmpty = (s eq null) || (s == "") + def get: (List[Int], Double, ThreeStringExtract) = ((s.length :: Nil, s.length.toDouble, this)) + def length = s.length + def lengthCompare(n: Int) = s.length compare n + def apply(idx: Int): Char = s charAt idx + def head: Char = s charAt 0 + def tail: String = s drop 1 + def drop(n: Int): ThreeStringExtract = new ThreeStringExtract(s drop n) + + override def toString = s +} + + object Bippy { def unapplySeq(x: Any): StringExtract = new StringExtract("" + x) } +object TripleBippy { + def unapplySeq(x: Any): ThreeStringExtract = new ThreeStringExtract("" + x) +} object Test { def f(x: Any) = x match { @@ -21,10 +38,23 @@ object Test { case _ => "nope" } + def g(x: Any): String = x match { + case TripleBippy(3 :: Nil, 3.0, 'b', chars @ _*) => "1: " + chars + case TripleBippy(5 :: Nil, 5.0, 'b' | 'B', chars @ _*) => "2: " + chars + case TripleBippy(_, _, chars @ _*) => "3: " + chars + case _ => "nope" + } + def main(args: Array[String]): Unit = { println(f("Bobby")) println(f("BOBBY")) println(f("BoBoTheClown")) println(f("TomTomTheClown")) + + println(g("bob")) + println(g("bobby")) + println(g("BOBBY")) + println(g("BOBO")) + println(g("TomTomTheClown")) } } -- cgit v1.2.3