Skip to content

Commit 25ddf81

Browse files
committed
Fix HK quoted pattern type variables
The issue was in the encoding into `{ExprMatchModule,TypeMatchModule}.unapply`. Specifically with the `TypeBindings` argument. This arguments holds the list of type variable definitions (`tpd.Bind` trees). We used a `Tuple` to list all the types inside. The problem is that higher-kinded type variables do not conform with the upper bounds of the tuple elements. The solution is to use an HList with any-kinded elements.
1 parent 1a4f3ac commit 25ddf81

File tree

9 files changed

+52
-10
lines changed

9 files changed

+52
-10
lines changed

compiler/src/dotty/tools/dotc/ast/tpd.scala

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1502,7 +1502,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
15021502
}
15031503
}
15041504

1505-
/** Creates the tuple type tree repesentation of the type trees in `ts` */
1505+
/** Creates the tuple type tree representation of the type trees in `ts` */
15061506
def tupleTypeTree(elems: List[Tree])(using Context): Tree = {
15071507
val arity = elems.length
15081508
if arity <= Definitions.MaxTupleArity then
@@ -1513,10 +1513,14 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
15131513
else nestedPairsTypeTree(elems)
15141514
}
15151515

1516-
/** Creates the nested pairs type tree repesentation of the type trees in `ts` */
1516+
/** Creates the nested pairs type tree representation of the type trees in `ts` */
15171517
def nestedPairsTypeTree(ts: List[Tree])(using Context): Tree =
15181518
ts.foldRight[Tree](TypeTree(defn.EmptyTupleModule.termRef))((x, acc) => AppliedTypeTree(TypeTree(defn.PairClass.typeRef), x :: acc :: Nil))
15191519

1520+
/** Creates the nested higher-kinded pairs type tree representation of the type trees in `ts` */
1521+
def hkNestedPairsTypeTree(ts: List[Tree])(using Context): Tree =
1522+
ts.foldRight[Tree](TypeTree(defn.QuoteMatching_HKNil.typeRef))((x, acc) => AppliedTypeTree(TypeTree(defn.QuoteMatching_HKCons.typeRef), x :: acc :: Nil))
1523+
15201524
/** Replaces all positions in `tree` with zero-extent positions */
15211525
private def focusPositions(tree: Tree)(using Context): Tree = {
15221526
val transformer = new tpd.TreeMap {

compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -865,6 +865,9 @@ class Definitions {
865865
@tu lazy val QuoteMatching_ExprMatchModule: Symbol = QuoteMatchingClass.requiredClass("ExprMatchModule")
866866
@tu lazy val QuoteMatching_TypeMatch: Symbol = QuoteMatchingClass.requiredMethod("TypeMatch")
867867
@tu lazy val QuoteMatching_TypeMatchModule: Symbol = QuoteMatchingClass.requiredClass("TypeMatchModule")
868+
@tu lazy val QuoteMatchingModule: Symbol = requiredModule("scala.quoted.runtime.QuoteMatching")
869+
@tu lazy val QuoteMatching_HKNil: Symbol = QuoteMatchingModule.requiredType("HKNil")
870+
@tu lazy val QuoteMatching_HKCons: Symbol = QuoteMatchingModule.requiredType("HKCons")
868871

869872
@tu lazy val ToExprModule: Symbol = requiredModule("scala.quoted.ToExpr")
870873
@tu lazy val ToExprModule_BooleanToExpr: Symbol = ToExprModule.requiredMethod("BooleanToExpr")

compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -411,7 +411,7 @@ trait QuotesAndSplices {
411411
val replaceBindings = new ReplaceBindings
412412
val patType = defn.tupleType(splices.tpes.map(tpe => replaceBindings(tpe.widen)))
413413

414-
val typeBindingsTuple = tpd.tupleTypeTree(typeBindings.values.toList)
414+
val typeBindingsTuple = tpd.hkNestedPairsTypeTree(typeBindings.values.toList)
415415

416416
val replaceBindingsInTree = new TreeMap {
417417
private var bindMap = Map.empty[Symbol, Symbol]

compiler/src/scala/quoted/runtime/impl/QuoteMatcher.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,9 +121,9 @@ object QuoteMatcher {
121121

122122
private def withEnv[T](env: Env)(body: Env ?=> T): T = body(using env)
123123

124-
def treeMatch(scrutineeTerm: Tree, patternTerm: Tree)(using Context): Option[Tuple] =
124+
def treeMatch(scrutineeTree: Tree, patternTree: Tree)(using Context): Option[Tuple] =
125125
given Env = Map.empty
126-
scrutineeTerm =?= patternTerm
126+
scrutineeTree =?= patternTree
127127

128128
/** Check that all trees match with `mtch` and concatenate the results with &&& */
129129
private def matchLists[T](l1: List[T], l2: List[T])(mtch: (T, T) => Matching): Matching = (l1, l2) match {

compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3092,14 +3092,14 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
30923092
new TypeImpl(tree, SpliceScope.getCurrent).asInstanceOf[scala.quoted.Type[T]]
30933093

30943094
object ExprMatch extends ExprMatchModule:
3095-
def unapply[TypeBindings <: Tuple, Tup <: Tuple](scrutinee: scala.quoted.Expr[Any])(using pattern: scala.quoted.Expr[Any]): Option[Tup] =
3095+
def unapply[TypeBindings, Tup <: Tuple](scrutinee: scala.quoted.Expr[Any])(using pattern: scala.quoted.Expr[Any]): Option[Tup] =
30963096
val scrutineeTree = reflect.asTerm(scrutinee)
30973097
val patternTree = reflect.asTerm(pattern)
30983098
treeMatch(scrutineeTree, patternTree).asInstanceOf[Option[Tup]]
30993099
end ExprMatch
31003100

31013101
object TypeMatch extends TypeMatchModule:
3102-
def unapply[TypeBindings <: Tuple, Tup <: Tuple](scrutinee: scala.quoted.Type[?])(using pattern: scala.quoted.Type[?]): Option[Tup] =
3102+
def unapply[TypeBindings, Tup <: Tuple](scrutinee: scala.quoted.Type[?])(using pattern: scala.quoted.Type[?]): Option[Tup] =
31033103
val scrutineeTree = reflect.TypeTree.of(using scrutinee)
31043104
val patternTree = reflect.TypeTree.of(using pattern)
31053105
treeMatch(scrutineeTree, patternTree).asInstanceOf[Option[Tup]]

library/src/scala/quoted/runtime/QuoteMatching.scala

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package scala.quoted.runtime
22

33
import scala.quoted.{Expr, Type}
4+
import scala.annotation.experimental
45

56
/** Part of the Quotes interface that needs to be implemented by the compiler but is not visible to users */
67
trait QuoteMatching:
@@ -17,7 +18,7 @@ trait QuoteMatching:
1718
* - `ExprMatch.unapply('{ f(0, myInt) })('{ f(patternHole[Int], patternHole[Int]) }, _)`
1819
* will return `Some(Tuple2('{0}, '{ myInt }))`
1920
* - `ExprMatch.unapply('{ f(0, "abc") })('{ f(0, patternHole[Int]) }, _)`
20-
* will return `None` due to the missmatch of types in the hole
21+
* will return `None` due to the mismatch of types in the hole
2122
*
2223
* Holes:
2324
* - scala.quoted.runtime.Patterns.patternHole[T]: hole that matches an expression `x` of type `Expr[U]`
@@ -27,7 +28,8 @@ trait QuoteMatching:
2728
* @param pattern `Expr[Any]` containing the pattern tree
2829
* @return None if it did not match, `Some(tup)` if it matched where `tup` contains `Expr[Ti]``
2930
*/
30-
def unapply[TypeBindings <: Tuple, Tup <: Tuple](scrutinee: Expr[Any])(using pattern: Expr[Any]): Option[Tup]
31+
// FIXME: is this change TASTy binary compatible? Or we need to create a new version of the interface?
32+
def unapply[TypeBindings, Tup <: Tuple](scrutinee: Expr[Any])(using pattern: Expr[Any]): Option[Tup]
3133
}
3234

3335
val TypeMatch: TypeMatchModule
@@ -40,5 +42,13 @@ trait QuoteMatching:
4042
* @param pattern `Type[?]` containing the pattern tree
4143
* @return None if it did not match, `Some(tup)` if it matched where `tup` contains `Type[Ti]``
4244
*/
43-
def unapply[TypeBindings <: Tuple, Tup <: Tuple](scrutinee: Type[?])(using pattern: Type[?]): Option[Tup]
45+
// FIXME: is this change TASTy binary compatible? Or we need to create a new version of the interface?
46+
def unapply[TypeBindings, Tup <: Tuple](scrutinee: Type[?])(using pattern: Type[?]): Option[Tup]
4447
}
48+
49+
@experimental
50+
object QuoteMatching:
51+
// FIXME: Where should this be defined?
52+
type HKHList
53+
type HKCons[+H <: AnyKind, +T <: HKHList] <: HKHList
54+
type HKNil <: HKHList
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import scala.quoted._
2+
3+
private def impl(x: Expr[Any])(using Quotes): Expr[Unit] = {
4+
x match
5+
case '{ foo[x] } =>
6+
assert(Type.show[x] == "scala.Int", Type.show[x])
7+
case '{ type f[X]; foo[`f`] } =>
8+
assert(Type.show[f] == "[A >: scala.Nothing <: scala.Any] => scala.collection.immutable.List[A]", Type.show[f])
9+
case '{ type f <: AnyKind; foo[`f`] } =>
10+
assert(Type.show[f] == "[K >: scala.Nothing <: scala.Any, V >: scala.Nothing <: scala.Any] => scala.collection.immutable.Map[K, V]", Type.show[f])
11+
case x => throw MatchError(x.show)
12+
'{}
13+
}
14+
15+
inline def test(inline x: Any): Unit = ${ impl('x) }
16+
17+
def foo[T <: AnyKind]: Any = ???
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
@main
2+
def Test =
3+
test(foo[Int])
4+
test(foo[List])
5+
test(foo[Map])

tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ val experimentalDefinitionInLibrary = Set(
6666
"scala.annotation.MacroAnnotation",
6767

6868
//// New APIs: Quotes
69+
// Can be stabilized in 3.4.0
70+
"scala.quoted.runtime.QuoteMatching",
71+
"scala.quoted.runtime.QuoteMatching$",
6972
// Can be stabilized in 3.3.0 (unsure) or later
7073
"scala.quoted.Quotes.reflectModule.CompilationInfoModule.XmacroSettings",
7174
"scala.quoted.Quotes.reflectModule.FlagsModule.JavaAnnotation",

0 commit comments

Comments
 (0)