diff --git a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala index 2dedcd788ec4..24c3d2e412f7 100644 --- a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala +++ b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala @@ -232,6 +232,7 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe case UnnecessaryNN // errorNumber: 216 case ErasedNotPureID // errorNumber: 217 case IllegalErasedDefID // errorNumber: 218 + case CannotInstantiateQuotedTypeVarID // errorNumber: 219 def errorNumber = ordinal - 1 diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index eb5e692b2915..f84fd3ba538e 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -3435,6 +3435,18 @@ final class QuotedTypeMissing(tpe: Type)(using Context) extends StagingMessage(Q end QuotedTypeMissing +final class CannotInstantiateQuotedTypeVar(symbol: Symbol)(using patternCtx: Context) extends StagingMessage(CannotInstantiateQuotedTypeVarID): + override protected def msg(using Context): String = + i"""Quoted pattern type variable `${symbol.name}` cannot be instantiated. + |If you meant to refer to a class named `${symbol.name}`, wrap it in backticks. + |If you meant to introduce a binding, this is not allowed after `new`. You might + |want to use the lower-level `quotes.reflect` API instead. + |Read more about type variables in quoted pattern in the Scala documentation: + |https://docs.scala-lang.org/scala3/guides/macros/quotes.html#type-variables-in-quoted-patterns + """ + + override protected def explain(using Context): String = "" + final class DeprecatedAssignmentSyntax(key: Name, value: untpd.Tree)(using Context) extends SyntaxMsg(DeprecatedAssignmentSyntaxID): override protected def msg(using Context): String = i"""Deprecated syntax: since 3.7 this is interpreted as a named tuple with one element, diff --git a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala index 4e7c4336b852..f979654e9811 100644 --- a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala +++ b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala @@ -222,7 +222,7 @@ trait QuotesAndSplices { if ctx.mode.is(Mode.InPatternAlternative) then report.error(IllegalVariableInPatternAlternative(tree.name), tree.srcPos) val typeSym = inContext(quotePatternOuterContext(ctx)) { - newSymbol(ctx.owner, tree.name.toTypeName, Case, typeSymInfo, NoSymbol, tree.span) + newSymbol(ctx.owner, tree.name.toTypeName, Synthetic | Case, typeSymInfo, NoSymbol, tree.span) } addQuotedPatternTypeVariable(typeSym) Bind(typeSym, untpd.Ident(nme.WILDCARD).withType(typeSymInfo)).withSpan(tree.span) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 10a061ab8fc4..c45c00845120 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1237,6 +1237,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case _ => var tpt1 = typedType(tree.tpt) val tsym = tpt1.tpe.underlyingClassRef(refinementOK = false).typeSymbol + if ctx.mode.isQuotedPattern && tpt1.tpe.typeSymbol.isAllOf(Synthetic | Case) then + val errorTp = errorType(CannotInstantiateQuotedTypeVar(tpt1.tpe.typeSymbol), tpt1.srcPos) + return cpy.New(tree)(tpt1).withType(errorTp) if tsym.is(Package) then report error(em"$tsym cannot be instantiated", tpt1.srcPos) tpt1 = tpt1.withType(ensureAccessible(tpt1.tpe, superAccess = false, tpt1.srcPos)) diff --git a/tests/neg-macros/i22616.check b/tests/neg-macros/i22616.check new file mode 100644 index 000000000000..d830d0c3fe00 --- /dev/null +++ b/tests/neg-macros/i22616.check @@ -0,0 +1,16 @@ +-- [E219] Staging Issue Error: tests/neg-macros/i22616.scala:13:22 ----------------------------------------------------- +13 | case '{ new caseName(${ Expr(name) }) } => Some(caseName(name)) // error // error + | ^^^^^^^^ + | Quoted pattern type variable `caseName` cannot be instantiated. + | If you meant to refer to a class named `caseName`, wrap it in backticks. + | If you meant to introduce a binding, this is not allowed after `new`. You might + | want to use the lower-level `quotes.reflect` API instead. + | Read more about type variables in quoted pattern in the Scala documentation: + | https://docs.scala-lang.org/scala3/guides/macros/quotes.html#type-variables-in-quoted-patterns + | +-- [E006] Not Found Error: tests/neg-macros/i22616.scala:13:67 --------------------------------------------------------- +13 | case '{ new caseName(${ Expr(name) }) } => Some(caseName(name)) // error // error + | ^^^^ + | Not found: name + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-macros/i22616.scala b/tests/neg-macros/i22616.scala new file mode 100644 index 000000000000..bd86716bc30c --- /dev/null +++ b/tests/neg-macros/i22616.scala @@ -0,0 +1,18 @@ +import scala.quoted.* + +final case class caseName(name: String) extends scala.annotation.Annotation +object caseName { + + given FromExpr[caseName] = + new FromExpr[caseName] { + override def unapply(x: Expr[caseName])(using Quotes): Option[caseName] = + val y: Int = 42 + x match { + case '{ caseName(${ Expr(name) }) } => Some(caseName(name)) + // with/without the following line... + case '{ new caseName(${ Expr(name) }) } => Some(caseName(name)) // error // error + case _ => println(x.show); None + } + } + +} diff --git a/tests/neg-macros/i22616b.check b/tests/neg-macros/i22616b.check new file mode 100644 index 000000000000..3c6007276cef --- /dev/null +++ b/tests/neg-macros/i22616b.check @@ -0,0 +1,13 @@ +-- [E007] Type Mismatch Error: tests/neg-macros/i22616b.scala:17:18 ---------------------------------------------------- +17 | case '{ Foo($y: t) } => // error + | ^^^^^ + | Found: t + | Required: String + | + | longer explanation available when compiling with `-explain` +-- [E006] Not Found Error: tests/neg-macros/i22616b.scala:18:19 -------------------------------------------------------- +18 | '{type S = t; ()} // error + | ^ + | Not found: type t + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-macros/i22616b.scala b/tests/neg-macros/i22616b.scala new file mode 100644 index 000000000000..45926f4e0d37 --- /dev/null +++ b/tests/neg-macros/i22616b.scala @@ -0,0 +1,22 @@ +// This test illustrates a current limitation of quoted pattern type variables, +// which has been discussed in https://github.com/scala/scala3/issues/22616#issuecomment-3012534064: +// These type variables do not have bound in general (see `typedQuotedTypeVar`), +// so they might not conform to the expected type. Here, `t` does not conform +// to `String`. + +import scala.quoted.{FromExpr, Expr, Quotes} + +case class Foo(x: String) + +object Macro: + inline def myMacro(): Unit = + ${ myMacroImpl('{Foo("hello")}) } + + def myMacroImpl(x: Expr[Foo])(using Quotes): Expr[Unit] = + x match + case '{ Foo($y: t) } => // error + '{type S = t; ()} // error + case _ => + println("not a foo") + + '{()} diff --git a/tests/run-macros/i22616c.check b/tests/run-macros/i22616c.check new file mode 100644 index 000000000000..d1918272a8f7 --- /dev/null +++ b/tests/run-macros/i22616c.check @@ -0,0 +1 @@ +_B_ diff --git a/tests/run-macros/i22616c/Macro_4.scala b/tests/run-macros/i22616c/Macro_4.scala new file mode 100644 index 000000000000..02e677db3428 --- /dev/null +++ b/tests/run-macros/i22616c/Macro_4.scala @@ -0,0 +1,11 @@ +import scala.quoted.* + +object Macro: + inline def myMacro[T](): String = + ${ myMacroImpl[T]() } + + def myMacroImpl[T: Type]()(using Quotes): Expr[String] = + import quotes.reflect.* + val myTypeRepr = MyTypeRepr(TypeRepr.of[T]) + val `caseName`(name) = myTypeRepr.requiredAnnotationValue[caseName] + Expr(name) diff --git a/tests/run-macros/i22616c/MyTypeRepr_3.scala b/tests/run-macros/i22616c/MyTypeRepr_3.scala new file mode 100644 index 000000000000..1a35889d403d --- /dev/null +++ b/tests/run-macros/i22616c/MyTypeRepr_3.scala @@ -0,0 +1,33 @@ +import scala.quoted.* + +final class MyTypeRepr(using val quotes: Quotes)(val unwrap: quotes.reflect.TypeRepr) { + import quotes.reflect.* + + def getAnnotation(annotTpe: quotes.reflect.Symbol): Option[quotes.reflect.Term] = + unwrap.typeSymbol.getAnnotation(annotTpe) + + def optionalAnnotation[Annot: Type]: Option[Expr[Annot]] = { + val annotTpe = TypeRepr.of[Annot] + val annotFlags = annotTpe.typeSymbol.flags + + if (annotFlags.is(Flags.Abstract) || annotFlags.is(Flags.Trait)) + report.errorAndAbort(s"Bad annotation type ${annotTpe.show} is abstract") + + this.getAnnotation(annotTpe.typeSymbol) match + case Some(tree) if tree.tpe <:< annotTpe => Some(tree.asExprOf[Annot]) + case _ => None + } + + def requiredAnnotation[Annot: Type]: Expr[Annot] = + optionalAnnotation[Annot].getOrElse(report.errorAndAbort(s"Missing required annotation `${TypeRepr.of[Annot].show}` for `$this`")) + + def optionalAnnotationValue[Annot: {Type, FromExpr}]: Option[Annot] = + optionalAnnotation[Annot].map { expr => + expr.value.getOrElse(report.errorAndAbort(s"Found annotation `${TypeRepr.of[Annot].show}` for `$this`, but are unable to extract Expr.value\n${expr.show}")) + } + + def requiredAnnotationValue[Annot: {Type, FromExpr}]: Annot = { + val expr = requiredAnnotation[Annot] + expr.value.getOrElse(report.errorAndAbort(s"Found annotation `${TypeRepr.of[Annot].show}` for `$this`, but are unable to extract Expr.value\n${expr.show}")) + } +} diff --git a/tests/run-macros/i22616c/SealedTrait3_2.scala b/tests/run-macros/i22616c/SealedTrait3_2.scala new file mode 100644 index 000000000000..9141a9ebd08b --- /dev/null +++ b/tests/run-macros/i22616c/SealedTrait3_2.scala @@ -0,0 +1,8 @@ +sealed trait SealedTrait3[+A, +B] +object SealedTrait3 { + final case class AB1[+B, +A](a: B, b: A) extends SealedTrait3[B, A] + final case class AB2[+C, +D](a: C, b: D) extends SealedTrait3[D, C] + final case class A[+T](a: T) extends SealedTrait3[T, Nothing] + @caseName("_B_") final case class B[+T](b: T) extends SealedTrait3[Nothing, T] + case object Neither extends SealedTrait3[Nothing, Nothing] +} diff --git a/tests/run-macros/i22616c/Test_5.scala b/tests/run-macros/i22616c/Test_5.scala new file mode 100644 index 000000000000..c8d177a5ae1b --- /dev/null +++ b/tests/run-macros/i22616c/Test_5.scala @@ -0,0 +1,2 @@ +@main def Test = + println(Macro.myMacro[SealedTrait3.B[Any]]()) diff --git a/tests/run-macros/i22616c/caseName_1.scala b/tests/run-macros/i22616c/caseName_1.scala new file mode 100644 index 000000000000..c8fee116e2e9 --- /dev/null +++ b/tests/run-macros/i22616c/caseName_1.scala @@ -0,0 +1,14 @@ +import scala.quoted.* + +final case class caseName(name: String) extends scala.annotation.Annotation +object caseName { + // This demonstrates a workaround for issue #22616. + given FromExpr[caseName] = + new FromExpr[caseName] { + override def unapply(x: Expr[caseName])(using Quotes): Option[caseName] = + x match { + case '{ new `caseName`(${ Expr(name) }) } => Some(caseName(name)) + case _ => println(x.show); None + } + } +}