Skip to content

Commit edff735

Browse files
committed
Rework Reflect Constant
The current definition of Constant.unapply is unsound because there is no way to tests if the constant contains a `Type`. We added `Constant.ClassTag.unapply` to work around this limitation, but the original was still unsound. That definition has it's own design flaws. To properly match all constants we provide an extractor for each kind of constant. This has the added advantage of being able to scale if new constants are added later. We also rename `Constant.ClassTag` to `ClassOf` as it represent a `classOf[T]`.
1 parent be6bcaf commit edff735

File tree

31 files changed

+419
-197
lines changed

31 files changed

+419
-197
lines changed

compiler/src/dotty/tools/dotc/quoted/QuoteContextImpl.scala

Lines changed: 82 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2061,16 +2061,91 @@ class QuoteContextImpl private (ctx: Context) extends QuoteContext:
20612061
type Constant = dotc.core.Constants.Constant
20622062

20632063
object Constant extends ConstantModule:
2064-
def apply(x: Unit | Null | Int | Boolean | Byte | Short | Int | Long | Float | Double | Char | String | Type): Constant =
2065-
dotc.core.Constants.Constant(x)
2066-
def unapply(constant: Constant): Option[Unit | Null | Int | Boolean | Byte | Short | Int | Long | Float | Double | Char | String | Type] =
2067-
Some(constant.value.asInstanceOf[Unit | Null | Int | Boolean | Byte | Short | Int | Long | Float | Double | Char | String | Type])
2068-
object ClassTag extends ClassTagModule:
2069-
def apply[T](using x: Type): Constant = dotc.core.Constants.Constant(x)
2064+
2065+
object Boolean extends ConstantBooleanModule:
2066+
def apply(x: Boolean): Constant = dotc.core.Constants.Constant(x)
2067+
def unapply(constant: Constant): Option[Boolean] =
2068+
if constant.tag == dotc.core.Constants.BooleanTag then Some(constant.booleanValue)
2069+
else None
2070+
end Boolean
2071+
2072+
object Byte extends ConstantByteModule:
2073+
def apply(x: Byte): Constant = dotc.core.Constants.Constant(x)
2074+
def unapply(constant: Constant): Option[Byte] =
2075+
if constant.tag == dotc.core.Constants.ByteTag then Some(constant.byteValue)
2076+
else None
2077+
end Byte
2078+
2079+
object Short extends ConstantShortModule:
2080+
def apply(x: Short): Constant = dotc.core.Constants.Constant(x)
2081+
def unapply(constant: Constant): Option[Short] =
2082+
if constant.tag == dotc.core.Constants.ShortTag then Some(constant.shortValue)
2083+
else None
2084+
end Short
2085+
2086+
object Int extends ConstantIntModule:
2087+
def apply(x: Int): Constant = dotc.core.Constants.Constant(x)
2088+
def unapply(constant: Constant): Option[Int] =
2089+
if constant.tag == dotc.core.Constants.IntTag then Some(constant.intValue)
2090+
else None
2091+
end Int
2092+
2093+
object Long extends ConstantLongModule:
2094+
def apply(x: Long): Constant = dotc.core.Constants.Constant(x)
2095+
def unapply(constant: Constant): Option[Long] =
2096+
if constant.tag == dotc.core.Constants.LongTag then Some(constant.longValue)
2097+
else None
2098+
end Long
2099+
2100+
object Float extends ConstantFloatModule:
2101+
def apply(x: Float): Constant = dotc.core.Constants.Constant(x)
2102+
def unapply(constant: Constant): Option[Float] =
2103+
if constant.tag == dotc.core.Constants.FloatTag then Some(constant.floatValue)
2104+
else None
2105+
end Float
2106+
2107+
object Double extends ConstantDoubleModule:
2108+
def apply(x: Double): Constant = dotc.core.Constants.Constant(x)
2109+
def unapply(constant: Constant): Option[Double] =
2110+
if constant.tag == dotc.core.Constants.DoubleTag then Some(constant.doubleValue)
2111+
else None
2112+
end Double
2113+
2114+
object Char extends ConstantCharModule:
2115+
def apply(x: Char): Constant = dotc.core.Constants.Constant(x)
2116+
def unapply(constant: Constant): Option[Char] =
2117+
if constant.tag == dotc.core.Constants.CharTag then Some(constant.charValue)
2118+
else None
2119+
end Char
2120+
2121+
object String extends ConstantStringModule:
2122+
def apply(x: String): Constant = dotc.core.Constants.Constant(x)
2123+
def unapply(constant: Constant): Option[String] =
2124+
if constant.tag == dotc.core.Constants.StringTag then Some(constant.stringValue)
2125+
else None
2126+
end String
2127+
2128+
object Unit extends ConstantUnitModule:
2129+
def apply(): Constant = dotc.core.Constants.Constant(())
2130+
def unapply(constant: Constant): Boolean =
2131+
constant.tag == dotc.core.Constants.UnitTag
2132+
end Unit
2133+
2134+
object Null extends ConstantNullModule:
2135+
def apply(): Constant = dotc.core.Constants.Constant(null)
2136+
def unapply(constant: Constant): Boolean =
2137+
constant.tag == dotc.core.Constants.NullTag
2138+
end Null
2139+
2140+
object ClassOf extends ConstantClassOfModule:
2141+
def apply(x: Type): Constant =
2142+
// TODO check that the type is a valid class when creating this constant or let Ycheck do it?
2143+
dotc.core.Constants.Constant(x)
20702144
def unapply(constant: Constant): Option[Type] =
20712145
if constant.tag == dotc.core.Constants.ClazzTag then Some(constant.typeValue)
20722146
else None
2073-
end ClassTag
2147+
end ClassOf
2148+
20742149
end Constant
20752150

20762151
object ConstantMethodsImpl extends ConstantMethods:

library/src-bootstrapped/scala/internal/quoted/Expr.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,13 @@ object Expr {
6060
/** Returns a null expresssion equivalent to `'{null}` */
6161
def `null`: QuoteContext ?=> quoted.Expr[Null] = qctx ?=> {
6262
import qctx.tasty._
63-
Literal(Constant(null)).seal.asInstanceOf[quoted.Expr[Null]]
63+
Literal(Constant.Null()).seal.asInstanceOf[quoted.Expr[Null]]
6464
}
6565

6666
/** Returns a unit expresssion equivalent to `'{}` or `'{()}` */
6767
def Unit: QuoteContext ?=> quoted.Expr[Unit] = qctx ?=> {
6868
import qctx.tasty._
69-
Literal(Constant(())).seal.asInstanceOf[quoted.Expr[Unit]]
69+
Literal(Constant.Unit()).seal.asInstanceOf[quoted.Expr[Unit]]
7070
}
7171

7272
}

library/src-bootstrapped/scala/quoted/Liftable.scala

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,38 +22,66 @@ object Liftable {
2222
// IMPORTANT Keep in sync with tests/run-staging/liftables.scala
2323

2424
/** Default liftable for Boolean */
25-
given BooleanLiftable[T <: Boolean] as Liftable[T] = new PrimitiveLiftable
25+
given BooleanLiftable[T <: Boolean] as Liftable[T] {
26+
def toExpr(x: T) =
27+
import qctx.tasty._
28+
Literal(Constant.Boolean(x)).seal.asInstanceOf[Expr[T]]
29+
}
2630

2731
/** Default liftable for Byte */
28-
given ByteLiftable[T <: Byte] as Liftable[T] = new PrimitiveLiftable
32+
given ByteLiftable[T <: Byte] as Liftable[T] {
33+
def toExpr(x: T) =
34+
import qctx.tasty._
35+
Literal(Constant.Byte(x)).seal.asInstanceOf[Expr[T]]
36+
}
2937

3038
/** Default liftable for Short */
31-
given ShortLiftable[T <: Short] as Liftable[T] = new PrimitiveLiftable
39+
given ShortLiftable[T <: Short] as Liftable[T] {
40+
def toExpr(x: T) =
41+
import qctx.tasty._
42+
Literal(Constant.Short(x)).seal.asInstanceOf[Expr[T]]
43+
}
3244

3345
/** Default liftable for Int */
34-
given IntLiftable[T <: Int] as Liftable[T] = new PrimitiveLiftable
46+
given IntLiftable[T <: Int] as Liftable[T] {
47+
def toExpr(x: T) =
48+
import qctx.tasty._
49+
Literal(Constant.Int(x)).seal.asInstanceOf[Expr[T]]
50+
}
3551

3652
/** Default liftable for Long */
37-
given LongLiftable[T <: Long] as Liftable[T] = new PrimitiveLiftable
53+
given LongLiftable[T <: Long] as Liftable[T] {
54+
def toExpr(x: T) =
55+
import qctx.tasty._
56+
Literal(Constant.Long(x)).seal.asInstanceOf[Expr[T]]
57+
}
3858

3959
/** Default liftable for Float */
40-
given FloatLiftable[T <: Float] as Liftable[T] = new PrimitiveLiftable
60+
given FloatLiftable[T <: Float] as Liftable[T] {
61+
def toExpr(x: T) =
62+
import qctx.tasty._
63+
Literal(Constant.Float(x)).seal.asInstanceOf[Expr[T]]
64+
}
4165

4266
/** Default liftable for Double */
43-
given DoubleLiftable[T <: Double] as Liftable[T] = new PrimitiveLiftable
67+
given DoubleLiftable[T <: Double] as Liftable[T] {
68+
def toExpr(x: T) =
69+
import qctx.tasty._
70+
Literal(Constant.Double(x)).seal.asInstanceOf[Expr[T]]
71+
}
4472

4573
/** Default liftable for Char */
46-
given CharLiftable[T <: Char] as Liftable[T] = new PrimitiveLiftable
74+
given CharLiftable[T <: Char] as Liftable[T] {
75+
def toExpr(x: T) =
76+
import qctx.tasty._
77+
Literal(Constant.Char(x)).seal.asInstanceOf[Expr[T]]
78+
}
4779

4880
/** Default liftable for String */
49-
given StringLiftable[T <: String] as Liftable[T] = new PrimitiveLiftable
50-
51-
/** Lift a literal constant value */
52-
private class PrimitiveLiftable[T <: Unit | Null | Int | Boolean | Byte | Short | Int | Long | Float | Double | Char | String] extends Liftable[T] {
53-
def toExpr(x: T) = qctx ?=> {
81+
given StringLiftable[T <: String] as Liftable[T] {
82+
def toExpr(x: T) =
5483
import qctx.tasty._
55-
Literal(Constant(x)).seal.asInstanceOf[Expr[T]]
56-
}
84+
Literal(Constant.String(x)).seal.asInstanceOf[Expr[T]]
5785
}
5886

5987
/** Default liftable for Class[T] */

library/src/scala/tasty/Reflection.scala

Lines changed: 129 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2317,25 +2317,144 @@ trait Reflection { reflection =>
23172317
/** Constant value represented as the constant itself */
23182318
type Constant <: AnyRef
23192319

2320-
/** Module of Constant literals */
2320+
/** Constant value represented as the constant itself */
23212321
val Constant: ConstantModule
23222322

2323+
/** Constant value represented as the constant itself */
23232324
trait ConstantModule { this: Constant.type =>
23242325

2325-
def apply(x: Unit | Null | Int | Boolean | Byte | Short | Int | Long | Float | Double | Char | String | Type): Constant
2326+
/** Constant Boolean value */
2327+
val Boolean: ConstantBooleanModule
2328+
2329+
/** Constant Boolean value */
2330+
trait ConstantBooleanModule { this: Boolean.type =>
2331+
/** Create a constant Boolean value */
2332+
def apply(x: Boolean): Constant
2333+
/** Match Boolean value constant and extract its value */
2334+
def unapply(constant: Constant): Option[Boolean]
2335+
}
2336+
2337+
/** Constant Byte value */
2338+
val Byte: ConstantByteModule
2339+
2340+
/** Constant Byte value */
2341+
trait ConstantByteModule { this: Byte.type =>
2342+
/** Create a constant Byte value */
2343+
def apply(x: Byte): Constant
2344+
/** Match Byte value constant and extract its value */
2345+
def unapply(constant: Constant): Option[Byte]
2346+
}
2347+
2348+
/** Constant Short value */
2349+
val Short: ConstantShortModule
2350+
2351+
/** Constant Short value */
2352+
trait ConstantShortModule { this: Short.type =>
2353+
/** Create a constant Short value */
2354+
def apply(x: Short): Constant
2355+
/** Match Short value constant and extract its value */
2356+
def unapply(constant: Constant): Option[Short]
2357+
}
2358+
2359+
/** Constant Int value */
2360+
val Int: ConstantIntModule
2361+
2362+
/** Constant Int value */
2363+
trait ConstantIntModule { this: Int.type =>
2364+
/** Create a constant Int value */
2365+
def apply(x: Int): Constant
2366+
/** Match Int value constant and extract its value */
2367+
def unapply(constant: Constant): Option[Int]
2368+
}
2369+
2370+
/** Constant Long value */
2371+
val Long: ConstantLongModule
2372+
2373+
/** Constant Long value */
2374+
trait ConstantLongModule { this: Long.type =>
2375+
/** Create a constant Long value */
2376+
def apply(x: Long): Constant
2377+
/** Match Long value constant and extract its value */
2378+
def unapply(constant: Constant): Option[Long]
2379+
}
2380+
2381+
/** Constant Float value */
2382+
val Float: ConstantFloatModule
23262383

2327-
def unapply(constant: Constant): Option[Unit | Null | Int | Boolean | Byte | Short | Int | Long | Float | Double | Char | String | Type]
2384+
/** Constant Float value */
2385+
trait ConstantFloatModule { this: Float.type =>
2386+
/** Create a constant Float value */
2387+
def apply(x: Float): Constant
2388+
/** Match Float value constant and extract its value */
2389+
def unapply(constant: Constant): Option[Float]
2390+
}
2391+
2392+
/** Constant Double value */
2393+
val Double: ConstantDoubleModule
2394+
2395+
/** Constant Double value */
2396+
trait ConstantDoubleModule { this: Double.type =>
2397+
/** Create a constant Double value */
2398+
def apply(x: Double): Constant
2399+
/** Match Double value constant and extract its value */
2400+
def unapply(constant: Constant): Option[Double]
2401+
}
2402+
2403+
/** Constant Char value */
2404+
val Char: ConstantCharModule
23282405

2329-
/** Module of ClassTag literals */
2330-
val ClassTag: ClassTagModule
2406+
/** Constant Char value */
2407+
trait ConstantCharModule { this: Char.type =>
2408+
/** Create a constant Char value */
2409+
def apply(x: Char): Constant
2410+
/** Match Char value constant and extract its value */
2411+
def unapply(constant: Constant): Option[Char]
2412+
}
2413+
2414+
/** Constant String value */
2415+
val String: ConstantStringModule
2416+
2417+
/** Constant String value */
2418+
trait ConstantStringModule { this: String.type =>
2419+
/** Create a constant String value */
2420+
def apply(x: String): Constant
2421+
/** Match String value constant and extract its value */
2422+
def unapply(constant: Constant): Option[String]
2423+
}
23312424

2332-
/** Module of ClassTag literals */
2333-
trait ClassTagModule { this: ClassTag.type =>
2334-
/** scala.reflect.ClassTag literal */
2335-
def apply[T](using x: Type): Constant
2336-
/** Extractor for ClassTag literals */
2425+
/** Constant Unit value */
2426+
val Unit: ConstantUnitModule
2427+
2428+
/** Constant Unit value */
2429+
trait ConstantUnitModule { this: Unit.type =>
2430+
/** Create a constant Unit value */
2431+
def apply(): Constant
2432+
/** Match Unit value constant */
2433+
def unapply(constant: Constant): Boolean
2434+
}
2435+
2436+
/** Constant null value */
2437+
val Null: ConstantNullModule
2438+
2439+
/** Constant null value */
2440+
trait ConstantNullModule { this: Null.type =>
2441+
/** Create a constant null value */
2442+
def apply(): Constant
2443+
/** Match null value constant */
2444+
def unapply(constant: Constant): Boolean
2445+
}
2446+
2447+
/** Constant class value representing a `classOf[T]` */
2448+
val ClassOf: ConstantClassOfModule
2449+
2450+
/** Constant class value representing a `classOf[T]` */
2451+
trait ConstantClassOfModule { this: ClassOf.type =>
2452+
/** Create a constant class value representing `classOf[<tpe>]` */
2453+
def apply(tpe: Type): Constant
2454+
/** Match a class value constant representing `classOf[<tpe>]` and extract its type */
23372455
def unapply(constant: Constant): Option[Type]
23382456
}
2457+
23392458
}
23402459

23412460
given ConstantMethods as ConstantMethods = ConstantMethodsImpl

library/src/scala/tasty/reflect/ExtractorsPrinter.scala

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -165,19 +165,19 @@ class ExtractorsPrinter[R <: Reflection & Singleton](val tasty: R) extends Print
165165
}
166166

167167
def visitConstant(x: Constant): Buffer = x match {
168-
case Constant(()) => this += "Constant(())"
169-
case Constant(null) => this += "Constant(null)"
170-
case Constant(value: Boolean) => this += "Constant(" += value += ")"
171-
case Constant(value: Byte) => this += "Constant(" += value += ": Byte)"
172-
case Constant(value: Short) => this += "Constant(" += value += ": Short)"
173-
case Constant(value: Char) => this += "Constant('" += value += "')"
174-
case Constant(value: Int) => this += "Constant(" += value.toString += ")"
175-
case Constant(value: Long) => this += "Constant(" += value += "L)"
176-
case Constant(value: Float) => this += "Constant(" += value += "f)"
177-
case Constant(value: Double) => this += "Constant(" += value += "d)"
178-
case Constant(value: String) => this += "Constant(\"" += value += "\")"
179-
case Constant.ClassTag(value) =>
180-
this += "Constant.ClassTag("
168+
case Constant.Unit() => this += "Constant.Unit()"
169+
case Constant.Null() => this += "Constant.Null()"
170+
case Constant.Boolean(value) => this += "Constant.Boolean(" += value += ")"
171+
case Constant.Byte(value) => this += "Constant.Byte(" += value += ")"
172+
case Constant.Short(value) => this += "Constant.Short(" += value += ")"
173+
case Constant.Int(value) => this += "Constant.Int(" += value += ")"
174+
case Constant.Long(value) => this += "Constant.Long(" += value += "L)"
175+
case Constant.Float(value) => this += "Constant.Float(" += value += "f)"
176+
case Constant.Double(value) => this += "Constant.Double(" += value += "d)"
177+
case Constant.Char(value) => this += "Constant.Char('" += value += "')"
178+
case Constant.String(value) => this += "Constant.String(\"" += value += "\")"
179+
case Constant.ClassOf(value) =>
180+
this += "Constant.ClassOf("
181181
visitType(value) += ")"
182182
}
183183

0 commit comments

Comments
 (0)