Skip to content

Commit ccded5a

Browse files
committed
Intrinsify constValueTuple and summonAll
The new implementation instantiates the TupleN/TupleXXL classes directly. This avoids the expensive construction of tuples using `*:`. Fixes #15988
1 parent b614d84 commit ccded5a

File tree

9 files changed

+118
-40
lines changed

9 files changed

+118
-40
lines changed

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1514,6 +1514,25 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
15141514
}
15151515
}
15161516

1517+
/** Creates the tuple containing the elemets */
1518+
def tupleTree(elems: List[Tree])(using Context): Tree = {
1519+
val arity = elems.length
1520+
if arity == 0 then
1521+
ref(defn.EmptyTupleModule)
1522+
else if arity <= Definitions.MaxTupleArity then
1523+
// TupleN[elem1Tpe, ...](elem1, ...)
1524+
ref(defn.TupleType(arity).nn.typeSymbol.companionModule)
1525+
.select(nme.apply)
1526+
.appliedToTypes(elems.map(_.tpe.widenIfUnstable))
1527+
.appliedToArgs(elems)
1528+
else
1529+
// TupleXXL.apply(elems*) // TODO add and use Tuple.apply(elems*) ?
1530+
ref(defn.TupleXXLModule)
1531+
.select(nme.apply)
1532+
.appliedToVarargs(elems.map(_.asInstance(defn.ObjectType)), TypeTree(defn.ObjectType))
1533+
.asInstance(defn.tupleType(elems.map(elem => elem.tpe.widenIfUnstable)))
1534+
}
1535+
15171536
/** Creates the tuple type tree representation of the type trees in `ts` */
15181537
def tupleTypeTree(elems: List[Tree])(using Context): Tree = {
15191538
val arity = elems.length

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,8 +243,10 @@ class Definitions {
243243
@tu lazy val Compiletime_requireConst : Symbol = CompiletimePackageClass.requiredMethod("requireConst")
244244
@tu lazy val Compiletime_constValue : Symbol = CompiletimePackageClass.requiredMethod("constValue")
245245
@tu lazy val Compiletime_constValueOpt: Symbol = CompiletimePackageClass.requiredMethod("constValueOpt")
246+
@tu lazy val Compiletime_constValueTuple: Symbol = CompiletimePackageClass.requiredMethod("constValueTuple")
246247
@tu lazy val Compiletime_summonFrom : Symbol = CompiletimePackageClass.requiredMethod("summonFrom")
247-
@tu lazy val Compiletime_summonInline : Symbol = CompiletimePackageClass.requiredMethod("summonInline")
248+
@tu lazy val Compiletime_summonInline : Symbol = CompiletimePackageClass.requiredMethod("summonInline")
249+
@tu lazy val Compiletime_summonAll : Symbol = CompiletimePackageClass.requiredMethod("summonAll")
248250
@tu lazy val CompiletimeTestingPackage: Symbol = requiredPackage("scala.compiletime.testing")
249251
@tu lazy val CompiletimeTesting_typeChecks: Symbol = CompiletimeTestingPackage.requiredMethod("typeChecks")
250252
@tu lazy val CompiletimeTesting_typeCheckErrors: Symbol = CompiletimeTestingPackage.requiredMethod("typeCheckErrors")
@@ -932,6 +934,9 @@ class Definitions {
932934
@tu lazy val TupleTypeRef: TypeRef = requiredClassRef("scala.Tuple")
933935
def TupleClass(using Context): ClassSymbol = TupleTypeRef.symbol.asClass
934936
@tu lazy val Tuple_cons: Symbol = TupleClass.requiredMethod("*:")
937+
@tu lazy val TupleModule: Symbol = requiredModule("scala.Tuple")
938+
@tu lazy val Tuple_fromArray: Symbol = TupleModule.requiredMethod("fromArray")
939+
@tu lazy val EmptyTupleClass: Symbol = requiredClass("scala.EmptyTuple")
935940
@tu lazy val EmptyTupleModule: Symbol = requiredModule("scala.EmptyTuple")
936941
@tu lazy val NonEmptyTupleTypeRef: TypeRef = requiredClassRef("scala.NonEmptyTuple")
937942
def NonEmptyTupleClass(using Context): ClassSymbol = NonEmptyTupleTypeRef.symbol.asClass

compiler/src/dotty/tools/dotc/inlines/Inliner.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -497,8 +497,8 @@ class Inliner(val call: tpd.Tree)(using Context):
497497
// assertAllPositioned(tree) // debug
498498
tree.changeOwner(originalOwner, ctx.owner)
499499

500-
def tryConstValue: Tree =
501-
TypeComparer.constValue(callTypeArgs.head.tpe) match {
500+
def tryConstValue(tpe: Type): Tree =
501+
TypeComparer.constValue(tpe) match {
502502
case Some(c) => Literal(c).withSpan(call.span)
503503
case _ => EmptyTree
504504
}

compiler/src/dotty/tools/dotc/inlines/Inlines.scala

Lines changed: 50 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -408,36 +408,67 @@ object Inlines:
408408
return Intrinsics.codeOf(arg, call.srcPos)
409409
case _ =>
410410

411-
// Special handling of `constValue[T]`, `constValueOpt[T], and summonInline[T]`
411+
// Special handling of `constValue[T]`, `constValueOpt[T]`, `constValueTuple[T]`, and `summonInline[T]`
412412
if callTypeArgs.length == 1 then
413-
if (inlinedMethod == defn.Compiletime_constValue) {
414-
val constVal = tryConstValue
413+
414+
def constValueOrError(tpe: Type): Tree =
415+
val constVal = tryConstValue(tpe)
415416
if constVal.isEmpty then
416-
val msg = NotConstant("cannot take constValue", callTypeArgs.head.tpe)
417-
return ref(defn.Predef_undefined).withSpan(call.span).withType(ErrorType(msg))
417+
val msg = NotConstant("cannot take constValue", tpe)
418+
ref(defn.Predef_undefined).withSpan(callTypeArgs.head.span).withType(ErrorType(msg))
418419
else
419-
return constVal
420+
constVal
421+
422+
def searchImplicitOrError(tpe: Type): Tree =
423+
val evTyper = new Typer(ctx.nestingLevel + 1)
424+
val evCtx = ctx.fresh.setTyper(evTyper)
425+
inContext(evCtx) {
426+
val evidence = evTyper.inferImplicitArg(tpe, callTypeArgs.head.span)
427+
evidence.tpe match
428+
case fail: Implicits.SearchFailureType =>
429+
errorTree(call, evTyper.missingArgMsg(evidence, tpe, ""))
430+
case _ =>
431+
evidence
432+
}
433+
434+
def unrollTupleTypes(tpe: Type): Option[List[Type]] = tpe.dealias match
435+
case AppliedType(tycon, args) if defn.isTupleClass(tycon.typeSymbol) =>
436+
Some(args)
437+
case AppliedType(tycon, head :: tail :: Nil) if tycon.isRef(defn.PairClass) =>
438+
unrollTupleTypes(tail).map(head :: _)
439+
case tpe: TermRef if tpe.symbol == defn.EmptyTupleModule =>
440+
Some(Nil)
441+
case _ =>
442+
None
443+
444+
if (inlinedMethod == defn.Compiletime_constValue) {
445+
return constValueOrError(callTypeArgs.head.tpe)
420446
}
421447
else if (inlinedMethod == defn.Compiletime_constValueOpt) {
422-
val constVal = tryConstValue
448+
val constVal = tryConstValue(callTypeArgs.head.tpe)
423449
return (
424450
if (constVal.isEmpty) ref(defn.NoneModule.termRef)
425451
else New(defn.SomeClass.typeRef.appliedTo(constVal.tpe), constVal :: Nil)
426452
)
427453
}
454+
else if (inlinedMethod == defn.Compiletime_constValueTuple) {
455+
unrollTupleTypes(callTypeArgs.head.tpe) match
456+
case Some(types) =>
457+
val constants = types.map(constValueOrError)
458+
return Typed(tpd.tupleTree(constants), TypeTree(callTypeArgs.head.tpe)).withSpan(call.span)
459+
case _ =>
460+
return errorTree(call, em"Tuple elements types must be known at compile time")
461+
}
428462
else if (inlinedMethod == defn.Compiletime_summonInline) {
429-
def searchImplicit(tpt: Tree) =
430-
val evTyper = new Typer(ctx.nestingLevel + 1)
431-
val evCtx = ctx.fresh.setTyper(evTyper)
432-
inContext(evCtx) {
433-
val evidence = evTyper.inferImplicitArg(tpt.tpe, tpt.span)
434-
evidence.tpe match
435-
case fail: Implicits.SearchFailureType =>
436-
errorTree(call, evTyper.missingArgMsg(evidence, tpt.tpe, ""))
437-
case _ =>
438-
evidence
439-
}
440-
return searchImplicit(callTypeArgs.head)
463+
return searchImplicitOrError(callTypeArgs.head.tpe)
464+
}
465+
else if (inlinedMethod == defn.Compiletime_summonAll) {
466+
unrollTupleTypes(callTypeArgs.head.tpe) match
467+
case Some(types) =>
468+
val implicits = types.map(searchImplicitOrError)
469+
return Typed(tpd.tupleTree(implicits), TypeTree(callTypeArgs.head.tpe)).withSpan(call.span)
470+
case _ =>
471+
return errorTree(call, em"Tuple elements types must be known at compile time")
441472
}
442473
end if
443474

library/src/scala/compiletime/package.scala

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -117,13 +117,9 @@ transparent inline def constValue[T]: T =
117117
* `(constValue[X1], ..., constValue[Xn])`.
118118
*/
119119
inline def constValueTuple[T <: Tuple]: T =
120-
val res =
121-
inline erasedValue[T] match
122-
case _: EmptyTuple => EmptyTuple
123-
case _: (t *: ts) => constValue[t] *: constValueTuple[ts]
124-
end match
125-
res.asInstanceOf[T]
126-
end constValueTuple
120+
// implemented in dotty.tools.dotc.typer.Inliner
121+
error("Compiler bug: `constValueTuple` was not evaluated by the compiler")
122+
127123

128124
/** Summons first given matching one of the listed cases. E.g. in
129125
*

tests/neg/17211.check

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
-- [E182] Type Error: tests/neg/17211.scala:14:12 ----------------------------------------------------------------------
1+
-- [E182] Type Error: tests/neg/17211.scala:14:13 ----------------------------------------------------------------------
22
14 | constValue[IsInt[Foo.Foo]] // error
3-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
4-
| IsInt[Foo.Foo] is not a constant type; cannot take constValue
3+
| ^^^^^^^^^^^^^^
4+
| IsInt[Foo.Foo] is not a constant type; cannot take constValue
55
|
6-
| Note: a match type could not be fully reduced:
6+
| Note: a match type could not be fully reduced:
77
|
8-
| trying to reduce IsInt[Foo.Foo]
9-
| failed since selector Foo.Foo
10-
| does not match case Int => (true : Boolean)
11-
| and cannot be shown to be disjoint from it either.
12-
| Therefore, reduction cannot advance to the remaining case
8+
| trying to reduce IsInt[Foo.Foo]
9+
| failed since selector Foo.Foo
10+
| does not match case Int => (true : Boolean)
11+
| and cannot be shown to be disjoint from it either.
12+
| Therefore, reduction cannot advance to the remaining case
1313
|
14-
| case _ => (false : Boolean)
14+
| case _ => (false : Boolean)

tests/neg/i14177a.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ import scala.compiletime.*
33
trait C[A]
44

55
inline given [Tup <: Tuple]: C[Tup] with
6-
val cs = summonAll[Tuple.Map[Tup, C]] // error cannot reduce inline match with
6+
val cs = summonAll[Tuple.Map[Tup, C]] // error: Tuple elements types must be known at compile time

tests/run/i15988a.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import scala.compiletime.constValueTuple
2+
3+
@main def Test: Unit =
4+
assert(constValueTuple[EmptyTuple] == EmptyTuple)
5+
assert(constValueTuple[("foo", 5, 3.14, "bar", false)] == ("foo", 5, 3.14, "bar", false))
6+
assert(constValueTuple[(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23)] == (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23))

tests/run/i15988b.scala

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import scala.compiletime.summonAll
2+
3+
@main def Test: Unit =
4+
assert(summonAll[EmptyTuple] == EmptyTuple)
5+
assert(summonAll[(5, 5, 5)] == (5, 5, 5))
6+
assert(
7+
summonAll[(
8+
5, 5, 5, 5, 5,
9+
5, 5, 5, 5, 5,
10+
5, 5, 5, 5, 5,
11+
5, 5, 5, 5, 5,
12+
5, 5, 5, 5, 5,
13+
)] == (
14+
5, 5, 5, 5, 5,
15+
5, 5, 5, 5, 5,
16+
5, 5, 5, 5, 5,
17+
5, 5, 5, 5, 5,
18+
5, 5, 5, 5, 5,
19+
))
20+
21+
given 5 = 5

0 commit comments

Comments
 (0)