diff --git a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala index a7ab6d3dded9..75e7c55ada28 100644 --- a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala @@ -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 diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 4f4b07f02ca2..31041bd5102a 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -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 = { diff --git a/compiler/test/dotc/run-test-pickling.blacklist b/compiler/test/dotc/run-test-pickling.blacklist index f12d011b8a53..9f19b439135c 100644 --- a/compiler/test/dotc/run-test-pickling.blacklist +++ b/compiler/test/dotc/run-test-pickling.blacklist @@ -9,6 +9,8 @@ i7212 i7868.scala i9011.scala i9473.scala +i13433.scala +i13433b.scala macros-in-same-project1 mixin-forwarder-overload t10889 diff --git a/tests/pos-special/fatal-warnings/i13433.scala b/tests/pos-special/fatal-warnings/i13433.scala new file mode 100644 index 000000000000..47a4a520e63a --- /dev/null +++ b/tests/pos-special/fatal-warnings/i13433.scala @@ -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)) +} diff --git a/tests/pos-special/fatal-warnings/i13433b.scala b/tests/pos-special/fatal-warnings/i13433b.scala new file mode 100644 index 000000000000..5e3625166fc0 --- /dev/null +++ b/tests/pos-special/fatal-warnings/i13433b.scala @@ -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)) +} diff --git a/tests/run/i13433.check b/tests/run/i13433.check new file mode 100644 index 000000000000..88223478e837 --- /dev/null +++ b/tests/run/i13433.check @@ -0,0 +1,4 @@ +Some(abc) +Some(abc) +None +None diff --git a/tests/run/i13433.scala b/tests/run/i13433.scala new file mode 100644 index 000000000000..fe634a5ac14a --- /dev/null +++ b/tests/run/i13433.scala @@ -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)) +} diff --git a/tests/run/i13433b.check b/tests/run/i13433b.check new file mode 100644 index 000000000000..88223478e837 --- /dev/null +++ b/tests/run/i13433b.check @@ -0,0 +1,4 @@ +Some(abc) +Some(abc) +None +None diff --git a/tests/run/i13433b.scala b/tests/run/i13433b.scala new file mode 100644 index 000000000000..914530b91fd1 --- /dev/null +++ b/tests/run/i13433b.scala @@ -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)) +}