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 22, 2021
1 parent cf6fa97 commit 29a8172
Show file tree
Hide file tree
Showing 4 changed files with 182 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 @@ -1200,6 +1206,8 @@ class Definitions {

@tu lazy val untestableClasses: Set[Symbol] = Set(NothingClass, NullClass, SingletonClass)

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

@tu lazy val AbstractFunctionType: Array[TypeRef] = mkArityArray("scala.runtime.AbstractFunction", MaxImplementedFunctionArity, 0)
val AbstractFunctionClassPerRun: PerRun[Array[Symbol]] = new PerRun(AbstractFunctionType.map(_.symbol.asClass))
def AbstractFunctionClass(n: Int)(using Context): Symbol = AbstractFunctionClassPerRun()(using ctx)(n)
Expand Down
8 changes: 7 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 All @@ -489,6 +491,7 @@ object StdNames {
val genericClass: N = "genericClass"
val get: N = "get"
val getClass_ : N = "getClass"
val getClassLoader: N = "getClassLoader"
val getOrElse: N = "getOrElse"
val hasNext: N = "hasNext"
val hashCode_ : N = "hashCode"
Expand All @@ -497,6 +500,7 @@ object StdNames {
val head: N = "head"
val higherKinds: N = "higherKinds"
val identity: N = "identity"
val intersectionType: N = "intersectionType"
val implicitConversions: N = "implicitConversions"
val implicitly: N = "implicitly"
val in: N = "in"
Expand Down Expand Up @@ -588,6 +592,7 @@ object StdNames {
val setSymbol: N = "setSymbol"
val setType: N = "setType"
val setTypeSignature: N = "setTypeSignature"
val singleType: N = "singleType"
val standardInterpolator: N = "standardInterpolator"
val staticClass : N = "staticClass"
val staticModule : N = "staticModule"
Expand Down Expand Up @@ -626,6 +631,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
157 changes: 156 additions & 1 deletion compiler/src/dotty/tools/dotc/typer/Synthesizer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,21 @@ package typer

import core._
import util.Spans.Span
import unpickleScala2.Scala2Erasure
import Contexts._
import Types._, Flags._, Symbols._, Types._, Names._, StdNames._, Constants._
import TypeErasure.{erasure, hasStableErasure}
import Decorators._
import ProtoTypes._
import Inferencing.{fullyDefinedType, isFullyDefined}
import Implicits.SearchSuccess
import ast.untpd
import transform.SymUtils._
import transform.TypeUtils._
import transform.SyntheticMembers._
import util.Property
import annotation.{tailrec, constructorOnly}
import collection.mutable

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

private type Scala2RefinedType = RecType | RefinedType | AndType

/** @see dotty.tools.dotc.core.unpickleScala2.Scala2Erasure.flattenedParents */
private object Scala2RefinedType:

def unapply(tp: Scala2RefinedType)(using Context): Option[List[Type]] =

def checkSupported(tp: Type): Boolean = tp match
case AnnotatedType(_, _) => false
case tp @ TypeRef(prefix, _) => !(!tp.symbol.exists && prefix.dealias.isInstanceOf[Scala2RefinedType])
case ground => true

@tailrec
def inner(explore: List[Type], acc: mutable.ListBuffer[Type]): Option[List[Type]] = explore match
case tp :: rest => tp match
case tp: RecType => inner(tp.parent :: rest, acc)
case RefinedType(parent, _, _) => inner(parent :: rest, acc)
case AndType(l, r) => inner(l :: r :: rest, acc)
case tp if checkSupported(tp) => inner(rest, acc += tp)
case _ => None // we can not create a manifest for unsupported parents

case nil => Some(acc.toList)
end inner

inner(tp :: Nil, new mutable.ListBuffer())

end unapply

end Scala2RefinedType

/** Replication of the `Implicits.ImplicitSearch.manifestOfType` algorithm found in nsc compiler.
* @see (https://github.com/scala/scala/blob/0c011547b1ccf961ca427c3d3459955e618e93d5/src/compiler/scala/tools/nsc/typechecker/Implicits.scala#L1519)
*/
private def manifestOfFactory(flavor: Symbol): SpecialHandler = (formal, span) =>

def materializeImplicit(formal: Type, span: Span)(using Context): Tree =
val arg = typer.inferImplicitArg(formal, span)
if arg.tpe.isError then
EmptyTree
else
arg

def inner(tp: Type, flavour: Symbol): Tree =

val full = flavor == defn.ManifestClass
val opt = flavor == defn.OptManifestClass

/* Creates a tree that calls the factory method called constructor in object scala.reflect.Manifest */
def manifestFactoryCall(constructor: TermName, tparg: Type, args: Tree*): Tree =
if args contains EmptyTree then
EmptyTree
else
val factory = if 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 findSingletonManifest(name: TermName) =
ref(defn.ManifestFactoryModule)
.select(name)
.ensureApplied
.withSpan(span)

/** Re-wraps a type in a manifest before calling `materializeImplicit` on the result
*
* TODO: in scala 2 if not full the default is `reflect.ClassManifest`,
* not `reflect.ClassTag`, which is treated differently.
*/
def findManifest(tp: Type, manifestClass: Symbol = if full then defn.ManifestClass else NoSymbol) =
if manifestClass.exists then
materializeImplicit(manifestClass.typeRef.appliedTo(tp), span)
else
inner(tp, NoSymbol) // workaround so that a `ClassManifest` will be generated

def findSubManifest(tp: Type) =
findManifest(tp, if (full) defn.ManifestClass else defn.OptManifestClass)

def manifestOfType(tp0: Type, isLowerBound: Boolean): Tree =

val tp1 = tp0.dealiasKeepAnnots

extension [T](xs: List[T]) def isSingleton = xs.lengthCompare(1) == 0

def classManifest(clsRef: Type, args: List[Type]): Tree =
val classarg = ref(defn.Predef_classOf).appliedToType(tp1)
val suffix0 = classarg :: (args map findSubManifest)
val pre = clsRef.normalizedPrefix
val ignorePrefix = (pre eq NoPrefix) || pre.typeSymbol.isStaticOwner
val suffix = if ignorePrefix then suffix0 else findSubManifest(pre) :: suffix0
manifestFactoryCall(nme.classType, tp, suffix*)

tp1 match
case ThisType(_) | TermRef(_,_) => manifestFactoryCall(nme.singleType, tp, singleton(tp1))
case ConstantType(c) => inner(c.tpe, defn.ManifestClass)

case tp1 @ TypeRef(pre, desig) =>
val tpSym = tp1.typeSymbol
if tpSym.isPrimitiveValueClass || defn.isPhantomClass(tpSym) then
if !isLowerBound && defn.isBottomClassAfterErasure(tpSym) then
EmptyTree // `Nothing`/`Null` manifests are ClassTags, lets not make a loophole for them
else
findSingletonManifest(tpSym.name.toTermName)
else if tpSym == defn.ObjectClass || tpSym == defn.AnyRefAlias then
findSingletonManifest(nme.Object)
else if tpSym.isClass then
classManifest(tp1, Nil)
else
EmptyTree

case tp1 @ AppliedType(tycon, args) =>
val tpSym = tycon.typeSymbol
if tpSym == defn.RepeatedParamClass then
EmptyTree
else if tpSym == defn.ArrayClass && args.isSingleton then
manifestFactoryCall(nme.arrayType, args.head, findManifest(args.head))
else if tpSym.isClass then
classManifest(tycon, args)
else
EmptyTree

case TypeBounds(lo, hi) if full =>
manifestFactoryCall(nme.wildcardType, tp,
manifestOfType(lo, isLowerBound = true), manifestOfType(hi, isLowerBound))

case Scala2RefinedType(parents) =>
if parents.isSingleton then findManifest(parents.head)
else if full then manifestFactoryCall(nme.intersectionType, tp, (parents map findSubManifest)*)
else manifestOfType(Scala2Erasure.intersectionDominator(parents), isLowerBound)

case _ =>
EmptyTree
end manifestOfType

manifestOfType(tp, isLowerBound = false) match
case EmptyTree if opt => ref(defn.NoManifestModule)
case result => result

end inner

formal.argInfos match
case arg :: Nil =>
inner(fullyDefinedType(arg, "Manifest argument", span), flavor)
case _ =>
EmptyTree
end manifestOfFactory

val synthesizedManifest: SpecialHandler = manifestOfFactory(defn.ManifestClass)
val synthesizedOptManifest: SpecialHandler = manifestOfFactory(defn.OptManifestClass)

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
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
}

0 comments on commit 29a8172

Please sign in to comment.