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 24, 2021
1 parent cf6fa97 commit 798af02
Show file tree
Hide file tree
Showing 8 changed files with 366 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 @@ -375,14 +375,123 @@ 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

def canManifest(tp: Manifestable, topLevel: Boolean) =
val sym = tp.typeSymbol
!sym.isAbstractType
&& hasStableErasure(tp)
&& !(topLevel && defn.isBottomClassAfterErasure(sym))

/** 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 canManifest(tp, topLevel) =>
val sym = tp.typeSymbol
if sym.isPrimitiveValueClass || defn.SpecialManifestClasses.contains(sym) then
singletonManifest(sym.name.toTermName)
else
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
27 changes: 27 additions & 0 deletions tests/neg/manifest-summoning.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
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
}

object Foo {
type F[T] <: T
manifest[Array[F[Int]]] // error
}

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

val `OpaqueList[Int]` = manifest[OpaqueList[Int]] // error (opaque types are not supported)
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
}
22 changes: 22 additions & 0 deletions tests/pos/manifest-summoning.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
object Foo {

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

val singleton: opaques.Inner = opaques.i

val om_Inner = optManifest[opaques.Inner] // NoManifest
val om_singleton = optManifest[singleton.type] // NoManifest
val ct_Inner = reflect.classTag[opaques.Inner]
val ct_singleton = reflect.classTag[singleton.type]
}

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] }
83 changes: 83 additions & 0 deletions tests/run/i9482.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import scala.reflect.{OptManifest, ClassTag}

object Ref {

object Sentinel

def makeWithArr[A: OptManifest]: String = optManifest[A] match {
case m: ClassTag[_] => m.newArray(0).asInstanceOf[AnyRef] match {
// these can be reordered, so long as Unit comes before AnyRef
case _: Array[Boolean] => "bool"
case _: Array[Byte] => "byte"
case _: Array[Short] => "short"
case _: Array[Char] => "char"
case _: Array[Int] => "int"
case _: Array[Float] => "float"
case _: Array[Long] => "long"
case _: Array[Double] => "double"
case _: Array[Unit] => "unit"
case a: Array[AnyRef] => a.getClass.getComponentType.getName
}
case _ => "<?>"
}

def make[A: OptManifest]: String = optManifest[A] match {
case m: ClassTag[a] => m match {
case ClassTag.Boolean => "bool"
case ClassTag.Byte => "byte"
case ClassTag.Short => "short"
case ClassTag.Char => "char"
case ClassTag.Int => "int"
case ClassTag.Float => "float"
case ClassTag.Long => "long"
case ClassTag.Double => "double"
case ClassTag.Unit => "unit"
case ClassTag.Any => "any"
case ClassTag.AnyVal => "anyval"
case ClassTag.Object => "anyref"
case _ => m.runtimeClass.getName
}
case NoManifest => "<?>"
}

}

import Ref.*

def baz[A] = Ref.makeWithArr[A]
def qux[A] = Ref.make[A]

@main def Test = {

assert(Ref.makeWithArr[Boolean] == "bool")
assert(Ref.makeWithArr[Byte] == "byte")
assert(Ref.makeWithArr[Short] == "short")
assert(Ref.makeWithArr[Char] == "char")
assert(Ref.makeWithArr[Int] == "int")
assert(Ref.makeWithArr[Float] == "float")
assert(Ref.makeWithArr[Long] == "long")
assert(Ref.makeWithArr[Double] == "double")
assert(Ref.makeWithArr[Unit] == "unit")
assert(Ref.makeWithArr["abc"] == "java.lang.String")
assert(Ref.makeWithArr[Null] == "<?>")
assert(Ref.makeWithArr[Nothing] == "<?>")
assert(baz[Int] == "<?>")

assert(Ref.make[Boolean] == "bool")
assert(Ref.make[Byte] == "byte")
assert(Ref.make[Short] == "short")
assert(Ref.make[Char] == "char")
assert(Ref.make[Int] == "int")
assert(Ref.make[Float] == "float")
assert(Ref.make[Long] == "long")
assert(Ref.make[Double] == "double")
assert(Ref.make[Unit] == "unit")
assert(Ref.make[Any] == "any")
assert(Ref.make[AnyVal] == "anyval")
assert(Ref.make[AnyRef] == "anyref")
assert(Ref.make["abc"] == "java.lang.String")
assert(Ref.make[Null] == "<?>")
assert(Ref.make[Nothing] == "<?>")
assert(qux[Int] == "<?>")

}
Loading

0 comments on commit 798af02

Please sign in to comment.