Skip to content

Commit 8f7fadd

Browse files
committed
fix scala#9482: implement manifest algorithm
1 parent cf6fa97 commit 8f7fadd

File tree

7 files changed

+278
-2
lines changed

7 files changed

+278
-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: 110 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import transform.TypeUtils._
1616
import transform.SyntheticMembers._
1717
import util.Property
1818
import annotation.{tailrec, constructorOnly}
19+
import collection.mutable
20+
import dotty.tools.dotc.core.SymDenotations.LazyType
1921

2022
/** Synthesize terms for special classes */
2123
class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
@@ -375,14 +377,121 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
375377
synthesizedSumMirror(formal, span)
376378
case _ => EmptyTree
377379

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

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

tests/neg/manifest-summoning.scala

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
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+
}

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: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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 m_Inner = manifest[opaques.Inner] // we can see the erasure of the opaque type
11+
val m_singleton = manifest[singleton.type]
12+
val om_Inner = optManifest[opaques.Inner]
13+
val om_singleton = optManifest[singleton.type]
14+
val ct_Inner = reflect.classTag[opaques.Inner]
15+
val ct_singleton = reflect.classTag[singleton.type]
16+
}
17+
18+
object Bar {
19+
type F[T] <: T
20+
manifest[Array[F[Int]]] // would not work in Scala 2
21+
}
22+
23+
val `List[Nothing]` = manifest[List[Nothing]]
24+
val `List[Array[Nothing]]` = manifest[List[Array[Nothing]]] // ok when Nothing is not the argument of top-level array
25+
26+
val `Array[Array[List[Int]]]` = manifest[Array[Array[List[Int]]]]
27+
28+
trait Mixin[T <: Mixin[T]] { type Self = T }
29+
class Baz extends Mixin[Baz] { val m = manifest[Self] }

tests/run/manifest-summoning.scala

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import scala.reflect.{classTag, ClassTag, NoManifest}
2+
3+
@main def Test: Unit =
4+
5+
/* ====== no manifest available ====== */
6+
7+
locally {
8+
noManifest[Array[? <: Int]] // available as a manifest
9+
noManifest[Array[? <: String]] // available as a manifest
10+
noManifest[Array[Nothing]]
11+
noManifest[Array[Null]]
12+
noManifest[Nothing]
13+
noManifest[Null]
14+
}
15+
16+
/* ====== ClassTag and OptManifest have the same runtime class and same equality ======= */
17+
18+
locally {
19+
interopOpt[List[Int]]
20+
interopOpt[List[? <: Int]]
21+
}
22+
23+
/* ====== Test some OptManifest have the same runtime class and are equal ======= */
24+
25+
locally {
26+
sameClassEqualOpt[List[Int], List[? <: Int]] // not equal for full manifests
27+
sameClassEqualOpt[List[Int], List[String]] // not equal for full manifests
28+
}
29+
30+
/* ============================================================================= */
31+
// The following tests rely on <:< being correct, i.e. `equals` on Manifest //
32+
// uses `<:<` underneath. //
33+
/* ============================================================================= */
34+
35+
/* ====== Test some Manifest have the same runtime class and are equal ======= */
36+
37+
locally {
38+
trait A
39+
trait B {def b: Int}
40+
trait C {def c: Int}
41+
trait D {def d: Int}
42+
class fooAnnot extends scala.annotation.StaticAnnotation
43+
44+
type SomeRefinedType =
45+
((B {def b: 0} & C) & ((A @fooAnnot) & D {def d: 2})) {def c: 1}
46+
47+
sameClassEqualMan[Array[? <: String], Array[String]]
48+
sameClassEqualMan[SomeRefinedType, A]
49+
}
50+
51+
52+
/* ====== Test some Manifest have the same runtime class but are not equal ======= */
53+
54+
locally {
55+
sameClassNonEqualMan[List[Int], List[? <: Int]]
56+
sameClassNonEqualMan[List[Int], List[String]]
57+
}
58+
59+
/* ====== Test that some Manifest have the same runtime class, are not equal, but are `<:<` ======= */
60+
61+
locally {
62+
class A
63+
class B extends A
64+
65+
sameClassSub[List[Int], List[AnyVal]]
66+
sameClassSub[List[Unit], List[AnyVal]]
67+
sameClassSub[List[B], List[A]]
68+
sameClassSub[Array[List[B]], Array[List[A]]]
69+
}
70+
71+
end Test
72+
73+
def noManifest[A: OptManifest] =
74+
assert(optManifest[A] eq NoManifest)
75+
76+
def interopOpt[A: ClassTag: OptManifest] =
77+
assert(classTag[A] == optManifest[A])
78+
optManifest[A] match
79+
case optA: ClassTag[_] =>
80+
assert(classTag[A].runtimeClass == optA.runtimeClass)
81+
82+
def sameClassEqualOpt[A: OptManifest, B: OptManifest] =
83+
assert(optManifest[A] == optManifest[B])
84+
(optManifest[A], optManifest[B]) match
85+
case (a: ClassTag[_], b: ClassTag[_]) =>
86+
assert(a.runtimeClass == b.runtimeClass)
87+
88+
def sameClassMan[A: Manifest, B: Manifest] =
89+
assert(manifest[A].runtimeClass == manifest[B].runtimeClass)
90+
91+
def sameClassEqualMan[A: Manifest, B: Manifest] =
92+
sameClassMan[A, B]
93+
assert(manifest[A] == manifest[B])
94+
95+
def sameClassNonEqualMan[A: Manifest, B: Manifest] =
96+
sameClassMan[A, B]
97+
assert(manifest[A] != manifest[B])
98+
99+
def sameClassSub[A: Manifest, B: Manifest] =
100+
sameClassNonEqualMan[A, B]
101+
assert(manifest[A] <:< manifest[B])

0 commit comments

Comments
 (0)