Skip to content

Commit 5b98f8a

Browse files
committed
fix #9482: implement manifest algorithm
1 parent cf6fa97 commit 5b98f8a

File tree

8 files changed

+364
-2
lines changed

8 files changed

+364
-2
lines changed

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -764,6 +764,12 @@ class Definitions {
764764
@tu lazy val SelectableClass: ClassSymbol = requiredClass("scala.Selectable")
765765
@tu lazy val WithoutPreciseParameterTypesClass: Symbol = requiredClass("scala.Selectable.WithoutPreciseParameterTypes")
766766

767+
@tu lazy val ManifestClass: ClassSymbol = requiredClass("scala.reflect.Manifest")
768+
@tu lazy val ManifestFactoryModule: Symbol = requiredModule("scala.reflect.ManifestFactory")
769+
@tu lazy val ClassManifestFactoryModule: Symbol = requiredModule("scala.reflect.ClassManifestFactory")
770+
@tu lazy val OptManifestClass: ClassSymbol = requiredClass("scala.reflect.OptManifest")
771+
@tu lazy val NoManifestModule: Symbol = requiredModule("scala.reflect.NoManifest")
772+
767773
@tu lazy val ReflectPackageClass: Symbol = requiredPackage("scala.reflect.package").moduleClass
768774
@tu lazy val ClassTagClass: ClassSymbol = requiredClass("scala.reflect.ClassTag")
769775
@tu lazy val ClassTagModule: Symbol = ClassTagClass.companionModule
@@ -1433,6 +1439,8 @@ class Definitions {
14331439

14341440
@tu lazy val SpecialClassTagClasses: Set[Symbol] = Set(UnitClass, AnyClass, AnyValClass)
14351441

1442+
@tu lazy val SpecialManifestClasses: Set[Symbol] = Set(AnyClass, AnyValClass, ObjectClass, NullClass, NothingClass)
1443+
14361444
/** Classes that are known not to have an initializer irrespective of
14371445
* whether NoInits is set. Note: FunctionXXLClass is in this set
14381446
* because if it is compiled by Scala2, it does not get a NoInit flag.

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,6 @@ object StdNames {
367367
val EnumValue: N = "EnumValue"
368368
val ExistentialTypeTree: N = "ExistentialTypeTree"
369369
val Flag : N = "Flag"
370-
val floatHash: N = "floatHash"
371370
val Ident: N = "Ident"
372371
val Import: N = "Import"
373372
val Literal: N = "Literal"
@@ -414,6 +413,7 @@ object StdNames {
414413
val argv : N = "argv"
415414
val arrayClass: N = "arrayClass"
416415
val arrayElementClass: N = "arrayElementClass"
416+
val arrayType: N = "arrayType"
417417
val arrayValue: N = "arrayValue"
418418
val array_apply : N = "array_apply"
419419
val array_clone : N = "array_clone"
@@ -440,6 +440,7 @@ object StdNames {
440440
val checkInitialized: N = "checkInitialized"
441441
val ClassManifestFactory: N = "ClassManifestFactory"
442442
val classOf: N = "classOf"
443+
val classType: N = "classType"
443444
val clone_ : N = "clone"
444445
val common: N = "common"
445446
val compiletime : N = "compiletime"
@@ -481,6 +482,7 @@ object StdNames {
481482
val find_ : N = "find"
482483
val flagsFromBits : N = "flagsFromBits"
483484
val flatMap: N = "flatMap"
485+
val floatHash: N = "floatHash"
484486
val foreach: N = "foreach"
485487
val format: N = "format"
486488
val fromDigits: N = "fromDigits"
@@ -626,6 +628,7 @@ object StdNames {
626628
val values: N = "values"
627629
val view_ : N = "view"
628630
val wait_ : N = "wait"
631+
val wildcardType: N = "wildcardType"
629632
val withFilter: N = "withFilter"
630633
val withFilterIfRefutable: N = "withFilterIfRefutable$"
631634
val WorksheetWrapper: N = "WorksheetWrapper"

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

Lines changed: 108 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import transform.TypeUtils._
1616
import transform.SyntheticMembers._
1717
import util.Property
1818
import annotation.{tailrec, constructorOnly}
19+
import collection.mutable
1920

2021
/** Synthesize terms for special classes */
2122
class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
@@ -375,14 +376,120 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
375376
synthesizedSumMirror(formal, span)
376377
case _ => EmptyTree
377378

379+
private def escapeJavaArray(elemTp: Type)(using Context): Type = elemTp match
380+
case JavaArrayType(elemTp1) => defn.ArrayOf(escapeJavaArray(elemTp1))
381+
case _ => elemTp
382+
383+
private enum ManifestKind:
384+
case Full, Opt, Clss
385+
386+
/** The kind that should be used for an array element, if we are `OptManifest` then this
387+
* prevents wildcards arguments of Arrays being converted to `NoManifest`
388+
*/
389+
def arrayElem = if this == Full then this else Clss
390+
391+
end ManifestKind
392+
393+
/** Manifest factory that does enough to satisfy the equality semantics for
394+
* - `scala.reflect.OptManifest` (only runtime class is recorded)
395+
* - `scala.reflect.Manifest` (runtime class of arguments are recorded, with wildcard upper bounds wrapped)
396+
* however,`toString` may be different.
397+
*
398+
* There are some differences to `ClassTag`,
399+
* e.g. in Scala 2 `manifest[Int @unchecked]` will fail, but `classTag[Int @unchecked]` succeeds.
400+
*/
401+
private def manifestFactoryOf(kind: ManifestKind): SpecialHandler = (formal, span) =>
402+
import ManifestKind.*
403+
404+
/* Creates a tree that calls the factory method called constructor in object scala.reflect.Manifest */
405+
def factoryManifest(constructor: TermName, tparg: Type, args: Tree*): Tree =
406+
if args.contains(EmptyTree) then
407+
EmptyTree
408+
else
409+
val factory = if kind == Full then defn.ManifestFactoryModule else defn.ClassManifestFactoryModule
410+
applyOverloaded(ref(factory), constructor, args.toList, tparg :: Nil, Types.WildcardType).withSpan(span)
411+
412+
/* Creates a tree representing one of the singleton manifests.*/
413+
def singletonManifest(name: TermName) =
414+
ref(defn.ManifestFactoryModule).select(name).ensureApplied.withSpan(span)
415+
416+
def synthArrayManifest(elemTp: Type, kind: ManifestKind, topLevel: Boolean): Tree =
417+
factoryManifest(nme.arrayType, elemTp, synthesize(elemTp, kind.arrayElem, topLevel))
418+
419+
/** manifests generated from wildcards can not equal Int,Long,Any,AnyRef,AnyVal etc,
420+
* so we wrap their upper bound.
421+
*/
422+
def synthWildcardManifest(tp: Manifestable, hi: Type, topLevel: Boolean): Tree =
423+
factoryManifest(nme.wildcardType, tp, singletonManifest(nme.Nothing), synthesize(hi, Full, topLevel))
424+
425+
/** `Nil` if not full manifest */
426+
def synthArgManifests(tp: Manifestable): List[Tree] = tp match
427+
case AppliedType(_, args) if kind == Full && tp.typeSymbol.isClass =>
428+
args.map(synthesize(_, Full, topLevel = false))
429+
case _ =>
430+
Nil
431+
432+
/** This type contains all top-level types supported by Scala 2's algorithm */
433+
type Manifestable =
434+
ThisType | TermRef | ConstantType | TypeRef | AppliedType | TypeBounds | RecType | RefinedType | AndType
435+
436+
/** adapted from `syntheticClassTag` */
437+
def synthManifest(tp: Manifestable, kind: ManifestKind, topLevel: Boolean) = tp match
438+
case defn.ArrayOf(elemTp) => synthArrayManifest(elemTp, kind, topLevel)
439+
case TypeBounds(_, hi) if kind == Full => synthWildcardManifest(tp, hi, topLevel)
440+
441+
case tp
442+
if hasStableErasure(tp)
443+
&& !tp.typeSymbol.isOpaqueAlias // if we support opaques we would need to fully dealias first
444+
&& !(topLevel && defn.isBottomClassAfterErasure(tp.typeSymbol)) =>
445+
val sym = tp.typeSymbol
446+
if sym.isPrimitiveValueClass || defn.SpecialManifestClasses.contains(sym) then
447+
singletonManifest(sym.name.toTermName)
448+
else
449+
erasure(tp) match
450+
case JavaArrayType(elemTp) =>
451+
synthArrayManifest(escapeJavaArray(elemTp), kind, topLevel)
452+
453+
case etp =>
454+
val clsArg = clsOf(etp).asInstance(defn.ClassType(tp)) // cast needed to resolve overloading
455+
factoryManifest(nme.classType, tp, (clsArg :: synthArgManifests(tp))*)
456+
457+
case _ =>
458+
EmptyTree
459+
460+
end synthManifest
461+
462+
def manifestOfType(tp0: Type, kind: ManifestKind, topLevel: Boolean): Tree = tp0.dealiasKeepAnnots match
463+
case tp1: Manifestable => synthManifest(tp1, kind, topLevel)
464+
case tp1 => EmptyTree
465+
466+
def synthesize(tp: Type, kind: ManifestKind, topLevel: Boolean): Tree =
467+
manifestOfType(tp, kind, topLevel) match
468+
case EmptyTree if kind == Opt => ref(defn.NoManifestModule)
469+
case result => result
470+
471+
formal.argInfos match
472+
case arg :: Nil =>
473+
synthesize(fullyDefinedType(arg, "Manifest argument", span), kind, topLevel = true)
474+
case _ =>
475+
EmptyTree
476+
477+
end manifestFactoryOf
478+
479+
val synthesizedManifest: SpecialHandler = manifestFactoryOf(ManifestKind.Full)
480+
val synthesizedOptManifest: SpecialHandler = manifestFactoryOf(ManifestKind.Opt)
481+
378482
val specialHandlers = List(
379483
defn.ClassTagClass -> synthesizedClassTag,
380484
defn.TypeTestClass -> synthesizedTypeTest,
381485
defn.CanEqualClass -> synthesizedCanEqual,
382486
defn.ValueOfClass -> synthesizedValueOf,
383487
defn.Mirror_ProductClass -> synthesizedProductMirror,
384488
defn.Mirror_SumClass -> synthesizedSumMirror,
385-
defn.MirrorClass -> synthesizedMirror)
489+
defn.MirrorClass -> synthesizedMirror,
490+
defn.ManifestClass -> synthesizedManifest,
491+
defn.OptManifestClass -> synthesizedOptManifest,
492+
)
386493

387494
def tryAll(formal: Type, span: Span)(using Context): Tree =
388495
def recur(handlers: SpecialHandlers): Tree = handlers match

tests/neg/manifest-summoning.scala

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
val `Array[Nothing]` = manifest[Array[Nothing]] // error
2+
val `Array[Null]` = manifest[Array[Null]] // error
3+
val m_Nothing = manifest[Nothing] // error
4+
val m_Null = manifest[Null] // error
5+
6+
val `Array[? <: Nothing]` = manifest[Array[? <: Nothing]] // error
7+
val `Array[? <: Null]` = manifest[Array[? <: Null]] // error
8+
9+
val `Int @unchecked` = manifest[Int @unchecked] // error
10+
11+
val `0 | 1` = manifest[0 | 1] // error
12+
13+
class Box[T] {
14+
val m = manifest[T] // error
15+
}
16+
17+
object opaques {
18+
opaque type OpaqueList[+A] = List[A]
19+
}
20+
import opaques.*
21+
22+
val `OpaqueList[Int]` = manifest[OpaqueList[Int]] // error (opaque types are not supported)

tests/pos/i9482.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import scala.reflect.OptManifest
2+
3+
object Ref {
4+
def make[A: OptManifest]: Ref[A] = ???
5+
}
6+
trait Ref[A]
7+
8+
trait Foo[A] {
9+
val bar = Ref.make[Int]
10+
val baz: Ref[A] = Ref.make
11+
}

tests/pos/manifest-summoning.scala

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
object Foo {
2+
3+
object opaques {
4+
opaque type Inner = String
5+
val i: Inner = "i"
6+
}
7+
8+
val singleton: opaques.Inner = opaques.i
9+
10+
val om_Inner = optManifest[opaques.Inner] // NoManifest
11+
val om_singleton = optManifest[singleton.type] // NoManifest
12+
val ct_Inner = reflect.classTag[opaques.Inner]
13+
val ct_singleton = reflect.classTag[singleton.type]
14+
}
15+
16+
object Bar {
17+
type F[T] <: T
18+
manifest[Array[F[Int]]] // would not work in Scala 2
19+
}
20+
21+
val `List[Nothing]` = manifest[List[Nothing]]
22+
val `List[Array[Nothing]]` = manifest[List[Array[Nothing]]] // ok when Nothing is not the argument of top-level array
23+
24+
val `Array[Array[List[Int]]]` = manifest[Array[Array[List[Int]]]]
25+
26+
trait Mixin[T <: Mixin[T]] { type Self = T }
27+
class Baz extends Mixin[Baz] { val m = manifest[Self] }

tests/run/i9482.scala

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import scala.reflect.{OptManifest, ClassTag}
2+
3+
object Ref {
4+
5+
object Sentinel
6+
7+
def makeWithArr[A: OptManifest]: String = optManifest[A] match {
8+
case m: ClassTag[_] => m.newArray(0).asInstanceOf[AnyRef] match {
9+
// these can be reordered, so long as Unit comes before AnyRef
10+
case _: Array[Boolean] => "bool"
11+
case _: Array[Byte] => "byte"
12+
case _: Array[Short] => "short"
13+
case _: Array[Char] => "char"
14+
case _: Array[Int] => "int"
15+
case _: Array[Float] => "float"
16+
case _: Array[Long] => "long"
17+
case _: Array[Double] => "double"
18+
case _: Array[Unit] => "unit"
19+
case a: Array[AnyRef] => a.getClass.getComponentType.getName
20+
}
21+
case _ => "<?>"
22+
}
23+
24+
def make[A: OptManifest]: String = optManifest[A] match {
25+
case m: ClassTag[a] => m match {
26+
case ClassTag.Boolean => "bool"
27+
case ClassTag.Byte => "byte"
28+
case ClassTag.Short => "short"
29+
case ClassTag.Char => "char"
30+
case ClassTag.Int => "int"
31+
case ClassTag.Float => "float"
32+
case ClassTag.Long => "long"
33+
case ClassTag.Double => "double"
34+
case ClassTag.Unit => "unit"
35+
case ClassTag.Any => "any"
36+
case ClassTag.AnyVal => "anyval"
37+
case ClassTag.Object => "anyref"
38+
case _ => m.runtimeClass.getName
39+
}
40+
case NoManifest => "<?>"
41+
}
42+
43+
}
44+
45+
import Ref.*
46+
47+
def baz[A] = Ref.makeWithArr[A]
48+
def qux[A] = Ref.make[A]
49+
50+
@main def Test = {
51+
52+
assert(Ref.makeWithArr[Boolean] == "bool")
53+
assert(Ref.makeWithArr[Byte] == "byte")
54+
assert(Ref.makeWithArr[Short] == "short")
55+
assert(Ref.makeWithArr[Char] == "char")
56+
assert(Ref.makeWithArr[Int] == "int")
57+
assert(Ref.makeWithArr[Float] == "float")
58+
assert(Ref.makeWithArr[Long] == "long")
59+
assert(Ref.makeWithArr[Double] == "double")
60+
assert(Ref.makeWithArr[Unit] == "unit")
61+
assert(Ref.makeWithArr["abc"] == "java.lang.String")
62+
assert(Ref.makeWithArr[Null] == "<?>")
63+
assert(Ref.makeWithArr[Nothing] == "<?>")
64+
assert(baz[Int] == "<?>")
65+
66+
assert(Ref.make[Boolean] == "bool")
67+
assert(Ref.make[Byte] == "byte")
68+
assert(Ref.make[Short] == "short")
69+
assert(Ref.make[Char] == "char")
70+
assert(Ref.make[Int] == "int")
71+
assert(Ref.make[Float] == "float")
72+
assert(Ref.make[Long] == "long")
73+
assert(Ref.make[Double] == "double")
74+
assert(Ref.make[Unit] == "unit")
75+
assert(Ref.make[Any] == "any")
76+
assert(Ref.make[AnyVal] == "anyval")
77+
assert(Ref.make[AnyRef] == "anyref")
78+
assert(Ref.make["abc"] == "java.lang.String")
79+
assert(Ref.make[Null] == "<?>")
80+
assert(Ref.make[Nothing] == "<?>")
81+
assert(qux[Int] == "<?>")
82+
83+
}

0 commit comments

Comments
 (0)