Skip to content

Commit

Permalink
Merge pull request #13453 from dotty-staging/fix-13433
Browse files Browse the repository at this point in the history
Search for type tests when matching abstract applied type
  • Loading branch information
smarter committed Oct 8, 2021
2 parents 0b3b305 + 6ff902b commit f726a24
Show file tree
Hide file tree
Showing 9 changed files with 150 additions and 20 deletions.
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/Synthesizer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
(formal, span) => formal.argInfos match {
case arg1 :: arg2 :: Nil if !defn.isBottomClass(arg2.typeSymbol) =>
val tp1 = fullyDefinedType(arg1, "TypeTest argument", span)
val tp2 = fullyDefinedType(arg2, "TypeTest argument", span)
val tp2 = fullyDefinedType(arg2, "TypeTest argument", span).normalized
val sym2 = tp2.typeSymbol
if tp1 <:< tp2 then
// optimization when we know the typetest will always succeed
Expand Down
42 changes: 23 additions & 19 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -901,28 +901,32 @@ class Typer extends Namer
* exists, rewrite to `tt(e)`.
* @pre We are in pattern-matching mode (Mode.Pattern)
*/
def tryWithTypeTest(tree: Typed, pt: Type)(using Context): Tree = tree.tpt.tpe.dealias match {
case tref: TypeRef if !tref.symbol.isClass && !ctx.isAfterTyper && !(tref =:= pt) =>
def withTag(tpe: Type): Option[Tree] = {
require(ctx.mode.is(Mode.Pattern))
withoutMode(Mode.Pattern)(
inferImplicit(tpe, EmptyTree, tree.tpt.span)
) match
case SearchSuccess(clsTag, _, _, _) =>
withMode(Mode.InTypeTest) {
Some(typed(untpd.Apply(untpd.TypedSplice(clsTag), untpd.TypedSplice(tree.expr)), pt))
}
case _ =>
None
}
val tag = withTag(defn.TypeTestClass.typeRef.appliedTo(pt, tref))
.orElse(withTag(defn.ClassTagClass.typeRef.appliedTo(tref)))
def tryWithTypeTest(tree: Typed, pt: Type)(using Context): Tree =
def withTag(tpe: Type): Option[Tree] = {
require(ctx.mode.is(Mode.Pattern))
withoutMode(Mode.Pattern)(
inferImplicit(tpe, EmptyTree, tree.tpt.span)
) match
case SearchSuccess(clsTag, _, _, _) =>
withMode(Mode.InTypeTest) {
Some(typed(untpd.Apply(untpd.TypedSplice(clsTag), untpd.TypedSplice(tree.expr)), pt))
}
case _ =>
None
}
def tagged(tpe: Type) = {
val tag = withTag(defn.TypeTestClass.typeRef.appliedTo(pt, tpe))
.orElse(withTag(defn.ClassTagClass.typeRef.appliedTo(tpe)))
.getOrElse(tree)
if tag.symbol.owner == defn.ClassTagClass && config.Feature.sourceVersion.isAtLeast(config.SourceVersion.future) then
if tag.symbol.maybeOwner == defn.ClassTagClass && config.Feature.sourceVersion.isAtLeast(config.SourceVersion.future) then
report.warning("Use of `scala.reflect.ClassTag` for type testing may be unsound. Consider using `scala.reflect.TypeTest` instead.", tree.srcPos)
tag
case _ => tree
}
}
tree.tpt.tpe.dealias match {
case tpe @ AppliedType(tref: TypeRef, _) if !tref.symbol.isClass && !ctx.isAfterTyper && !(tpe =:= pt) => tagged(tpe)
case tref: TypeRef if !tref.symbol.isClass && !ctx.isAfterTyper && !(tref =:= pt) => tagged(tref)
case _ => tree
}


def typedNamedArg(tree: untpd.NamedArg, pt: Type)(using Context): NamedArg = {
Expand Down
2 changes: 2 additions & 0 deletions compiler/test/dotc/run-test-pickling.blacklist
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ i7212
i7868.scala
i9011.scala
i9473.scala
i13433.scala
i13433b.scala
macros-in-same-project1
mixin-forwarder-overload
t10889
Expand Down
32 changes: 32 additions & 0 deletions tests/pos-special/fatal-warnings/i13433.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import scala.reflect.TypeTest

type Matcher[A] = A match { case String => String }

def patternMatch[A](a: Any)(using tt: TypeTest[Any, Matcher[A]]): Option[Matcher[A]] = {
// type T = RDF.Triple[Rdf]
a match {
case res: Matcher[A] => Some(res)
case _ => None
}
}

def patternMatchWithAlias[A](a: Any)(using tt: TypeTest[Any, Matcher[A]]): Option[Matcher[A]] = {
type T = Matcher[A]
a match {
case res: T => Some(res)
case _ => None
}
}


@main def main = {
println(patternMatch[String]("abc"))
println(patternMatchWithAlias[String]("abc"))
println(patternMatch[String]("abc")(using (s: Any) => {
if s.isInstanceOf[Matcher[String]] then Some[s.type & Matcher[String]](s.asInstanceOf[s.type & Matcher[String]]) else None }))
println(patternMatchWithAlias[String]("abc")(using (s: Any) => {
if s.isInstanceOf[Matcher[String]] then Some[s.type & Matcher[String]](s.asInstanceOf[s.type & Matcher[String]]) else None }))

println(patternMatch[String](1))
println(patternMatchWithAlias[String](1))
}
28 changes: 28 additions & 0 deletions tests/pos-special/fatal-warnings/i13433b.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import scala.reflect.ClassTag

type Matcher[A] = A match { case String => String }

def patternMatch[A](a: Any)(using tt: ClassTag[Matcher[A]]): Option[Matcher[A]] = {
// type T = RDF.Triple[Rdf]
a match {
case res: Matcher[A] => Some(res)
case _ => None
}
}

def patternMatchWithAlias[A](a: Any)(using tt: ClassTag[Matcher[A]]): Option[Matcher[A]] = {
type T = Matcher[A]
a match {
case res: T => Some(res)
case _ => None
}
}


@main def main = {
println(patternMatch[String]("abc"))
println(patternMatchWithAlias[String]("abc"))

println(patternMatch[String](1))
println(patternMatchWithAlias[String](1))
}
4 changes: 4 additions & 0 deletions tests/run/i13433.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Some(abc)
Some(abc)
None
None
28 changes: 28 additions & 0 deletions tests/run/i13433.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import scala.reflect.TypeTest

type Matcher[A] = A match { case String => String }

def patternMatch[A](a: Any)(using tt: TypeTest[Any, Matcher[A]]): Option[Matcher[A]] = {
// type T = RDF.Triple[Rdf]
a match {
case res: Matcher[A] => Some(res)
case _ => None
}
}

def patternMatchWithAlias[A](a: Any)(using tt: TypeTest[Any, Matcher[A]]): Option[Matcher[A]] = {
type T = Matcher[A]
a match {
case res: T => Some(res)
case _ => None
}
}


@main def Test = {
println(patternMatch[String]("abc"))
println(patternMatchWithAlias[String]("abc"))

println(patternMatch[String](1))
println(patternMatchWithAlias[String](1))
}
4 changes: 4 additions & 0 deletions tests/run/i13433b.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Some(abc)
Some(abc)
None
None
28 changes: 28 additions & 0 deletions tests/run/i13433b.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import scala.reflect.ClassTag

type Matcher[A] = A match { case String => String }

def patternMatch[A](a: Any)(using tt: ClassTag[Matcher[A]]): Option[Matcher[A]] = {
// type T = RDF.Triple[Rdf]
a match {
case res: Matcher[A] => Some(res)
case _ => None
}
}

def patternMatchWithAlias[A](a: Any)(using tt: ClassTag[Matcher[A]]): Option[Matcher[A]] = {
type T = Matcher[A]
a match {
case res: T => Some(res)
case _ => None
}
}


@main def Test = {
println(patternMatch[String]("abc"))
println(patternMatchWithAlias[String]("abc"))

println(patternMatch[String](1))
println(patternMatchWithAlias[String](1))
}

0 comments on commit f726a24

Please sign in to comment.