Skip to content

Commit

Permalink
fix scala#9482: implement manifest algorithm
Browse files Browse the repository at this point in the history
  • Loading branch information
bishabosha committed Jul 23, 2021
1 parent cf6fa97 commit 4ab2fef
Show file tree
Hide file tree
Showing 7 changed files with 291 additions and 2 deletions.
8 changes: 8 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -764,6 +764,12 @@ class Definitions {
@tu lazy val SelectableClass: ClassSymbol = requiredClass("scala.Selectable")
@tu lazy val WithoutPreciseParameterTypesClass: Symbol = requiredClass("scala.Selectable.WithoutPreciseParameterTypes")

@tu lazy val ManifestClass: ClassSymbol = requiredClass("scala.reflect.Manifest")
@tu lazy val ManifestFactoryModule: Symbol = requiredModule("scala.reflect.ManifestFactory")
@tu lazy val ClassManifestFactoryModule: Symbol = requiredModule("scala.reflect.ClassManifestFactory")
@tu lazy val OptManifestClass: ClassSymbol = requiredClass("scala.reflect.OptManifest")
@tu lazy val NoManifestModule: Symbol = requiredModule("scala.reflect.NoManifest")

@tu lazy val ReflectPackageClass: Symbol = requiredPackage("scala.reflect.package").moduleClass
@tu lazy val ClassTagClass: ClassSymbol = requiredClass("scala.reflect.ClassTag")
@tu lazy val ClassTagModule: Symbol = ClassTagClass.companionModule
Expand Down Expand Up @@ -1433,6 +1439,8 @@ class Definitions {

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

@tu lazy val SpecialManifestClasses: Set[Symbol] = Set(AnyClass, AnyValClass, ObjectClass, NullClass, NothingClass)

/** Classes that are known not to have an initializer irrespective of
* whether NoInits is set. Note: FunctionXXLClass is in this set
* because if it is compiled by Scala2, it does not get a NoInit flag.
Expand Down
5 changes: 4 additions & 1 deletion compiler/src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,6 @@ object StdNames {
val EnumValue: N = "EnumValue"
val ExistentialTypeTree: N = "ExistentialTypeTree"
val Flag : N = "Flag"
val floatHash: N = "floatHash"
val Ident: N = "Ident"
val Import: N = "Import"
val Literal: N = "Literal"
Expand Down Expand Up @@ -414,6 +413,7 @@ object StdNames {
val argv : N = "argv"
val arrayClass: N = "arrayClass"
val arrayElementClass: N = "arrayElementClass"
val arrayType: N = "arrayType"
val arrayValue: N = "arrayValue"
val array_apply : N = "array_apply"
val array_clone : N = "array_clone"
Expand All @@ -440,6 +440,7 @@ object StdNames {
val checkInitialized: N = "checkInitialized"
val ClassManifestFactory: N = "ClassManifestFactory"
val classOf: N = "classOf"
val classType: N = "classType"
val clone_ : N = "clone"
val common: N = "common"
val compiletime : N = "compiletime"
Expand Down Expand Up @@ -481,6 +482,7 @@ object StdNames {
val find_ : N = "find"
val flagsFromBits : N = "flagsFromBits"
val flatMap: N = "flatMap"
val floatHash: N = "floatHash"
val foreach: N = "foreach"
val format: N = "format"
val fromDigits: N = "fromDigits"
Expand Down Expand Up @@ -626,6 +628,7 @@ object StdNames {
val values: N = "values"
val view_ : N = "view"
val wait_ : N = "wait"
val wildcardType: N = "wildcardType"
val withFilter: N = "withFilter"
val withFilterIfRefutable: N = "withFilterIfRefutable$"
val WorksheetWrapper: N = "WorksheetWrapper"
Expand Down
111 changes: 110 additions & 1 deletion compiler/src/dotty/tools/dotc/typer/Synthesizer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import transform.TypeUtils._
import transform.SyntheticMembers._
import util.Property
import annotation.{tailrec, constructorOnly}
import collection.mutable
import dotty.tools.dotc.core.SymDenotations.LazyType

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

private def escapeJavaArray(elemTp: Type)(using Context): Type = elemTp match
case JavaArrayType(elemTp1) => defn.ArrayOf(escapeJavaArray(elemTp1))
case _ => elemTp

private enum ManifestKind:
case Full, Opt, Clss

/** The kind that should be used for an array element, if we are `OptManifest` then this
* prevents wildcards arguments of Arrays being converted to `NoManifest`
*/
def arrayElem = if this == Full then this else Clss

end ManifestKind

/** Manifest factory that does enough to satisfy the equality semantics for
* - `scala.reflect.OptManifest` (only runtime class is recorded)
* - `scala.reflect.Manifest` (runtime class of arguments are recorded, with wildcard upper bounds wrapped)
* however,`toString` may be different.
*
* There are some differences to `ClassTag`,
* e.g. in Scala 2 `manifest[Int @unchecked]` will fail, but `classTag[Int @unchecked]` succeeds.
*/
private def manifestFactoryOf(kind: ManifestKind): SpecialHandler = (formal, span) =>
import ManifestKind.*

/* Creates a tree that calls the factory method called constructor in object scala.reflect.Manifest */
def factoryManifest(constructor: TermName, tparg: Type, args: Tree*): Tree =
if args.contains(EmptyTree) then
EmptyTree
else
val factory = if kind == Full then defn.ManifestFactoryModule else defn.ClassManifestFactoryModule
applyOverloaded(ref(factory), constructor, args.toList, tparg :: Nil, Types.WildcardType).withSpan(span)

/* Creates a tree representing one of the singleton manifests.*/
def singletonManifest(name: TermName) =
ref(defn.ManifestFactoryModule).select(name).ensureApplied.withSpan(span)

def synthArrayManifest(elemTp: Type, kind: ManifestKind, topLevel: Boolean): Tree =
factoryManifest(nme.arrayType, elemTp, synthesize(elemTp, kind.arrayElem, topLevel))

/** manifests generated from wildcards can not equal Int,Long,Any,AnyRef,AnyVal etc,
* so we wrap their upper bound.
*/
def synthWildcardManifest(tp: Manifestable, hi: Type, topLevel: Boolean): Tree =
factoryManifest(nme.wildcardType, tp, singletonManifest(nme.Nothing), synthesize(hi, Full, topLevel))

/** `Nil` if not full manifest */
def synthArgManifests(tp: Manifestable): List[Tree] = tp match
case AppliedType(_, args) if kind == Full && tp.typeSymbol.isClass =>
args.map(synthesize(_, Full, topLevel = false))
case _ =>
Nil

/** This type contains all top-level types supported by Scala 2's algorithm */
type Manifestable =
ThisType | TermRef | ConstantType | TypeRef | AppliedType | TypeBounds | RecType | RefinedType | AndType

/** adapted from `syntheticClassTag` */
def synthManifest(tp: Manifestable, kind: ManifestKind, topLevel: Boolean) = tp match
case defn.ArrayOf(elemTp) => synthArrayManifest(elemTp, kind, topLevel)
case TypeBounds(_, hi) if kind == Full => synthWildcardManifest(tp, hi, topLevel)

case tp if hasStableErasure(tp) && !(topLevel && defn.isBottomClassAfterErasure(tp.typeSymbol)) =>
val sym =
val sym0 = tp.typeSymbol
if sym0.isOpaqueAlias then sym0.opaqueAlias.typeSymbol
else sym0
if sym.isPrimitiveValueClass || defn.SpecialManifestClasses.contains(sym) then
singletonManifest(sym.name.toTermName)
else
// should this be Scala 2 erasure? (e.g. intersection types behave differently)
erasure(tp) match
case JavaArrayType(elemTp) =>
synthArrayManifest(escapeJavaArray(elemTp), kind, topLevel)

case etp =>
val clsArg = clsOf(etp).asInstance(defn.ClassType(tp)) // cast needed to resolve overloading
factoryManifest(nme.classType, tp, (clsArg :: synthArgManifests(tp))*)

case _ =>
EmptyTree

end synthManifest

def manifestOfType(tp0: Type, kind: ManifestKind, topLevel: Boolean): Tree = tp0.dealiasKeepAnnots match
case tp1: Manifestable => synthManifest(tp1, kind, topLevel)
case tp1 => EmptyTree

def synthesize(tp: Type, kind: ManifestKind, topLevel: Boolean): Tree =
manifestOfType(tp, kind, topLevel) match
case EmptyTree if kind == Opt => ref(defn.NoManifestModule)
case result => result

formal.argInfos match
case arg :: Nil =>
synthesize(fullyDefinedType(arg, "Manifest argument", span), kind, topLevel = true)
case _ =>
EmptyTree

end manifestFactoryOf

val synthesizedManifest: SpecialHandler = manifestFactoryOf(ManifestKind.Full)
val synthesizedOptManifest: SpecialHandler = manifestFactoryOf(ManifestKind.Opt)

val specialHandlers = List(
defn.ClassTagClass -> synthesizedClassTag,
defn.TypeTestClass -> synthesizedTypeTest,
defn.CanEqualClass -> synthesizedCanEqual,
defn.ValueOfClass -> synthesizedValueOf,
defn.Mirror_ProductClass -> synthesizedProductMirror,
defn.Mirror_SumClass -> synthesizedSumMirror,
defn.MirrorClass -> synthesizedMirror)
defn.MirrorClass -> synthesizedMirror,
defn.ManifestClass -> synthesizedManifest,
defn.OptManifestClass -> synthesizedOptManifest,
)

def tryAll(formal: Type, span: Span)(using Context): Tree =
def recur(handlers: SpecialHandlers): Tree = handlers match
Expand Down
15 changes: 15 additions & 0 deletions tests/neg/manifest-summoning.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
val `Array[Nothing]` = manifest[Array[Nothing]] // error
val `Array[Null]` = manifest[Array[Null]] // error
val m_Nothing = manifest[Nothing] // error
val m_Null = manifest[Null] // error

val `Array[? <: Nothing]` = manifest[Array[? <: Nothing]] // error
val `Array[? <: Null]` = manifest[Array[? <: Null]] // error

val `Int @unchecked` = manifest[Int @unchecked] // error

val `0 | 1` = manifest[0 | 1] // error

class Box[T] {
val m = manifest[T] // error
}
11 changes: 11 additions & 0 deletions tests/pos/i9482.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import scala.reflect.OptManifest

object Ref {
def make[A: OptManifest]: Ref[A] = ???
}
trait Ref[A]

trait Foo[A] {
val bar = Ref.make[Int]
val baz: Ref[A] = Ref.make
}
29 changes: 29 additions & 0 deletions tests/pos/manifest-summoning.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
object Foo {

object opaques {
opaque type Inner = String
val i: Inner = "i"
}

val singleton: opaques.Inner = opaques.i

val m_Inner = manifest[opaques.Inner] // we can see the erasure of the opaque type
val m_singleton = manifest[singleton.type]
val om_Inner = optManifest[opaques.Inner]
val om_singleton = optManifest[singleton.type]
val ct_Inner = reflect.classTag[opaques.Inner]
val ct_singleton = reflect.classTag[singleton.type]
}

object Bar {
type F[T] <: T
manifest[Array[F[Int]]] // would not work in Scala 2
}

val `List[Nothing]` = manifest[List[Nothing]]
val `List[Array[Nothing]]` = manifest[List[Array[Nothing]]] // ok when Nothing is not the argument of top-level array

val `Array[Array[List[Int]]]` = manifest[Array[Array[List[Int]]]]

trait Mixin[T <: Mixin[T]] { type Self = T }
class Baz extends Mixin[Baz] { val m = manifest[Self] }
114 changes: 114 additions & 0 deletions tests/run/manifest-summoning.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import scala.reflect.{classTag, ClassTag, NoManifest}

@main def Test: Unit =

/* ====== no manifest available ====== */

locally {
noManifest[Array[? <: Int]] // available as a manifest
noManifest[Array[? <: String]] // available as a manifest
noManifest[Array[Nothing]]
noManifest[Array[Null]]
noManifest[Nothing]
noManifest[Null]
}

/* ====== ClassTag and OptManifest have the same runtime class and same equality ======= */

locally {
interopOpt[List[Int]]
interopOpt[List[? <: Int]]
}

/* ====== Test some OptManifest have the same runtime class and are equal ======= */

locally {
sameClassEqualOpt[List[Int], List[? <: Int]] // not equal for full manifests
sameClassEqualOpt[List[Int], List[String]] // not equal for full manifests
}

/* ============================================================================= */
// The following tests rely on <:< being correct, i.e. `equals` on Manifest //
// uses `<:<` underneath. //
/* ============================================================================= */

/* ====== Test some Manifest have the same runtime class and are equal ======= */

locally {
trait A
trait B {def b: Int}
trait C {def c: Int}
trait D {def d: Int}
class fooAnnot extends scala.annotation.StaticAnnotation

type SomeRefinedType =
((B {def b: 0} & C) & ((A @fooAnnot) & D {def d: 2})) {def c: 1}

object opaques {
opaque type OpaqueList[+A] = List[A]
opaque type OpaqueMap[A] = Map[A, A]
}
import opaques.*

sameClassEqualMan[Array[? <: String], Array[String]]
sameClassEqualMan[SomeRefinedType, A]

// manifests of opaque types so far do not track type arguments -
// if we did care we would need to fetch the manifest for the fully
// dealiased opaque type, i.e. dereference opaque aliases to opaque aliases
// otherwise the arities do not fully match
sameClassEqualMan[OpaqueList[Int], OpaqueList[String]] // arities match, List could be applied correctly
sameClassEqualMan[OpaqueMap[Int], OpaqueMap[String]] // arities do not match so `Map` would not be fully applied
}


/* ====== Test some Manifest have the same runtime class but are not equal ======= */

locally {
sameClassNonEqualMan[List[Int], List[? <: Int]]
sameClassNonEqualMan[List[Int], List[String]]
}

/* ====== Test that some Manifest have the same runtime class, are not equal, but are `<:<` ======= */

locally {
class A
class B extends A

sameClassSub[List[Int], List[AnyVal]]
sameClassSub[List[Unit], List[AnyVal]]
sameClassSub[List[B], List[A]]
sameClassSub[Array[List[B]], Array[List[A]]]
}

end Test

def noManifest[A: OptManifest] =
assert(optManifest[A] eq NoManifest)

def interopOpt[A: ClassTag: OptManifest] =
assert(classTag[A] == optManifest[A])
optManifest[A] match
case optA: ClassTag[_] =>
assert(classTag[A].runtimeClass == optA.runtimeClass)

def sameClassEqualOpt[A: OptManifest, B: OptManifest] =
assert(optManifest[A] == optManifest[B])
(optManifest[A], optManifest[B]) match
case (a: ClassTag[_], b: ClassTag[_]) =>
assert(a.runtimeClass == b.runtimeClass)

def sameClassMan[A: Manifest, B: Manifest] =
assert(manifest[A].runtimeClass == manifest[B].runtimeClass)

def sameClassEqualMan[A: Manifest, B: Manifest] =
sameClassMan[A, B]
assert(manifest[A] == manifest[B])

def sameClassNonEqualMan[A: Manifest, B: Manifest] =
sameClassMan[A, B]
assert(manifest[A] != manifest[B])

def sameClassSub[A: Manifest, B: Manifest] =
sameClassNonEqualMan[A, B]
assert(manifest[A] <:< manifest[B])

0 comments on commit 4ab2fef

Please sign in to comment.