@@ -85,7 +85,7 @@ object TypeErasure {
8585 *
8686 * @return The arity if it can be determined or -1 otherwise.
8787 */
88- def tupleArity (tp : Type )(using Context ): Int = tp/* .dealias*/ match {
88+ def tupleArity (tp : Type )(using Context ): Int = tp/* .dealias*/ match
8989 case AppliedType (tycon, _ :: tl :: Nil ) if tycon.isRef(defn.PairClass ) =>
9090 val arity = tupleArity(tl)
9191 if (arity < 0 ) arity else arity + 1
@@ -94,11 +94,14 @@ object TypeErasure {
9494 case tp : AndOrType =>
9595 val arity1 = tupleArity(tp.tp1)
9696 val arity2 = tupleArity(tp.tp2)
97- if arity1 == arity2 then arity1 else - 1
97+ if arity1 == arity2 then arity1 else math.min(- 1 , math.min(arity1, arity2))
98+ case tp : WildcardType => - 2
99+ case tp : TypeVar if ! tp.isInstantiated => - 2
98100 case _ =>
99101 if defn.isTupleNType(tp) then tp.dealias.argInfos.length
100- else - 1
101- }
102+ else tp.dealias match
103+ case tp : TypeVar if ! tp.isInstantiated => - 2
104+ case _ => - 1
102105
103106 def normalizeClass (cls : ClassSymbol )(using Context ): ClassSymbol = {
104107 if (cls.owner == defn.ScalaPackageClass ) {
@@ -204,19 +207,19 @@ object TypeErasure {
204207 * @param tp The type to erase.
205208 */
206209 def erasure (tp : Type )(using Context ): Type =
207- erasureFn(sourceLanguage = SourceLanguage .Scala3 , semiEraseVCs = false , isConstructor = false , isSymbol = false , inSigName = false )(tp)(using preErasureCtx)
210+ erasureFn(sourceLanguage = SourceLanguage .Scala3 , semiEraseVCs = false , isConstructor = false , isSymbol = false , inSigName = false )(tp)(using preErasureCtx).nn
208211
209212 /** The value class erasure of a Scala type, where value classes are semi-erased to
210213 * ErasedValueType (they will be fully erased in [[ElimErasedValueType ]]).
211214 *
212215 * @param tp The type to erase.
213216 */
214217 def valueErasure (tp : Type )(using Context ): Type =
215- erasureFn(sourceLanguage = SourceLanguage .Scala3 , semiEraseVCs = true , isConstructor = false , isSymbol = false , inSigName = false )(tp)(using preErasureCtx)
218+ erasureFn(sourceLanguage = SourceLanguage .Scala3 , semiEraseVCs = true , isConstructor = false , isSymbol = false , inSigName = false )(tp)(using preErasureCtx).nn
216219
217220 /** The erasure that Scala 2 would use for this type. */
218221 def scala2Erasure (tp : Type )(using Context ): Type =
219- erasureFn(sourceLanguage = SourceLanguage .Scala2 , semiEraseVCs = true , isConstructor = false , isSymbol = false , inSigName = false )(tp)(using preErasureCtx)
222+ erasureFn(sourceLanguage = SourceLanguage .Scala2 , semiEraseVCs = true , isConstructor = false , isSymbol = false , inSigName = false )(tp)(using preErasureCtx).nn
220223
221224 /** Like value class erasure, but value classes erase to their underlying type erasure */
222225 def fullErasure (tp : Type )(using Context ): Type =
@@ -265,8 +268,8 @@ object TypeErasure {
265268 if (defn.isPolymorphicAfterErasure(sym)) eraseParamBounds(sym.info.asInstanceOf [PolyType ])
266269 else if (sym.isAbstractType) TypeAlias (WildcardType )
267270 else if sym.is(ConstructorProxy ) then NoType
268- else if (sym.isConstructor) outer.addParam(sym.owner.asClass, erase(tp)(using preErasureCtx))
269- else if (sym.is(Label )) erase.eraseResult(sym.info)(using preErasureCtx)
271+ else if (sym.isConstructor) outer.addParam(sym.owner.asClass, erase(tp)(using preErasureCtx).nn )
272+ else if (sym.is(Label )) erase.eraseResult(sym.info)(using preErasureCtx).nn
270273 else erase.eraseInfo(tp, sym)(using preErasureCtx) match {
271274 case einfo : MethodType =>
272275 if (sym.isGetter && einfo.resultType.isRef(defn.UnitClass ))
@@ -587,8 +590,14 @@ import TypeErasure._
587590 */
588591class TypeErasure (sourceLanguage : SourceLanguage , semiEraseVCs : Boolean , isConstructor : Boolean , isSymbol : Boolean , inSigName : Boolean ) {
589592
590- /** The erasure |T| of a type T. This is:
593+ /** The erasure |T| of a type T.
594+ *
595+ * If computing the erasure of T requires erasing a WildcardType or an
596+ * uninstantiated type variable, then an exception signaling an internal
597+ * error will be thrown, unless `inSigName` is set in which case `null`
598+ * will be returned.
591599 *
600+ * In all other situations, |T| will be non-null and computed as follow:
592601 * - For a refined type scala.Array+[T]:
593602 * - if T is Nothing or Null, []Object
594603 * - otherwise, if T <: Object, []|T|
@@ -620,7 +629,7 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst
620629 * - For NoType or NoPrefix, the type itself.
621630 * - For any other type, exception.
622631 */
623- private def apply (tp : Type )(using Context ): Type = tp match {
632+ private def apply (tp : Type )(using Context ): Type | Null = ( tp match
624633 case _ : ErasedValueType =>
625634 tp
626635 case tp : TypeRef =>
@@ -641,13 +650,19 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst
641650 case _ : ThisType =>
642651 this (tp.widen)
643652 case SuperType (thistpe, supertpe) =>
644- SuperType (this (thistpe), this (supertpe))
653+ val eThis = this (thistpe)
654+ val eSuper = this (supertpe)
655+ if eThis == null || eSuper == null then null
656+ else SuperType (eThis, eSuper)
645657 case ExprType (rt) =>
646658 defn.FunctionType (0 )
647659 case RefinedType (parent, nme.apply, refinedInfo) if parent.typeSymbol eq defn.PolyFunctionClass =>
648660 erasePolyFunctionApply(refinedInfo)
649661 case RefinedType (parent, nme.apply, refinedInfo : MethodType ) if defn.isErasedFunctionType(parent) =>
650662 eraseErasedFunctionApply(refinedInfo)
663+ case tp : TypeVar if ! tp.isInstantiated =>
664+ assert(inSigName, i " Cannot erase uninstantiated type variable $tp" )
665+ null
651666 case tp : TypeProxy =>
652667 this (tp.underlying)
653668 case tp @ AndType (tp1, tp2) =>
@@ -656,7 +671,10 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst
656671 else if sourceLanguage.isScala2 then
657672 this (Scala2Erasure .intersectionDominator(Scala2Erasure .flattenedParents(tp)))
658673 else
659- erasedGlb(this (tp1), this (tp2))
674+ val e1 = this (tp1)
675+ val e2 = this (tp2)
676+ if e1 == null || e2 == null then null
677+ else erasedGlb(e1, e2)
660678 case OrType (tp1, tp2) =>
661679 if isSymbol && sourceLanguage.isScala2 && ctx.settings.scalajs.value then
662680 // In Scala2Unpickler we unpickle Scala.js pseudo-unions as if they were
@@ -670,10 +688,13 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst
670688 // alone (and this doesn't impact the SJSIR we generate).
671689 JSDefinitions .jsdefn.PseudoUnionType
672690 else
673- TypeComparer .orType(this (tp1), this (tp2), isErased = true )
691+ val e1 = this (tp1)
692+ val e2 = this (tp2)
693+ if e1 == null || e2 == null then null
694+ else TypeComparer .orType(e1, e2, isErased = true )
674695 case tp : MethodType =>
675696 def paramErasure (tpToErase : Type ) =
676- erasureFn(sourceLanguage, semiEraseVCs, isConstructor, isSymbol, inSigName)(tpToErase)
697+ erasureFn(sourceLanguage, semiEraseVCs, isConstructor, isSymbol, inSigName = false )(tpToErase).nn
677698 val (names, formals0) = if tp.hasErasedParams then
678699 tp.paramNames
679700 .zip(tp.paramInfos)
@@ -700,7 +721,7 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst
700721 else {
701722 def eraseParent (tp : Type ) = tp.dealias match { // note: can't be opaque, since it's a class parent
702723 case tp : AppliedType if tp.tycon.isRef(defn.PairClass ) => defn.ObjectType
703- case _ => apply(tp)
724+ case _ => apply(tp).nn
704725 }
705726 val erasedParents : List [Type ] =
706727 if ((cls eq defn.ObjectClass ) || cls.isPrimitiveValueClass) Nil
@@ -725,11 +746,12 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst
725746 }
726747 case _ : ErrorType | JavaArrayType (_) =>
727748 tp
728- case tp : WildcardType if inSigName =>
749+ case tp : WildcardType =>
750+ assert(inSigName, i " Cannot erase wildcard type $tp" )
751+ null
752+ case tp if (tp `eq` NoType ) || (tp `eq` NoPrefix ) => // Why is this check different?
729753 tp
730- case tp if (tp `eq` NoType ) || (tp `eq` NoPrefix ) =>
731- tp
732- }
754+ ).ensuring(etp => etp != null || inSigName)
733755
734756 /** Like translucentSuperType, but issue a fatal error if it does not exist. */
735757 private def checkedSuperType (tp : TypeProxy )(using Context ): Type =
@@ -760,15 +782,21 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst
760782 val defn .ArrayOf (elemtp) = tp : @ unchecked
761783 if (isGenericArrayElement(elemtp, isScala2 = sourceLanguage.isScala2)) defn.ObjectType
762784 else
763- try JavaArrayType (erasureFn(sourceLanguage, semiEraseVCs = false , isConstructor, isSymbol, inSigName)(elemtp))
785+ try
786+ val eElem = erasureFn(sourceLanguage, semiEraseVCs = false , isConstructor, isSymbol, inSigName)(elemtp)
787+ if eElem == null then null
788+ else JavaArrayType (eElem)
764789 catch case ex : Throwable =>
765790 handleRecursive(" erase array type" , tp.show, ex)
766791 }
767792
768- private def erasePair (tp : Type )(using Context ): Type = {
793+ private def erasePair (tp : Type )(using Context ): Type | Null = {
794+ // NOTE: `tupleArity` does not consider TypeRef(EmptyTuple$) equivalent to EmptyTuple.type,
795+ // we fix this for printers, but type erasure should be preserved.
769796 val arity = tupleArity(tp)
770- if (arity < 0 ) defn.ProductClass .typeRef
771- else if (arity <= Definitions .MaxTupleArity ) defn.TupleType (arity).nn
797+ if arity == - 2 then null // erasure depends on an uninstantiated type variable or WildcardType
798+ else if arity == - 1 then defn.ProductClass .typeRef
799+ else if arity <= Definitions .MaxTupleArity then defn.TupleType (arity).nn
772800 else defn.TupleXXLClass .typeRef
773801 }
774802
@@ -777,12 +805,13 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst
777805 * to the underlying type.
778806 */
779807 def eraseInfo (tp : Type , sym : Symbol )(using Context ): Type =
808+ assert(! inSigName) // therefore apply(...).nn won't fail
780809 val tp1 = tp match
781810 case tp : MethodicType => integrateContextResults(tp, contextResultCount(sym))
782811 case _ => tp
783812 tp1 match
784813 case ExprType (rt) =>
785- if sym.is(Param ) then apply(tp1)
814+ if sym.is(Param ) then apply(tp1).nn
786815 // Note that params with ExprTypes are eliminated by ElimByName,
787816 // but potentially re-introduced by ResolveSuper, when we add
788817 // forwarders to mixin methods.
@@ -794,9 +823,9 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst
794823 eraseResult(tp1.resultType) match
795824 case rt : MethodType => rt
796825 case rt => MethodType (Nil , Nil , rt)
797- case tp1 => this (tp1)
826+ case tp1 => this (tp1).nn
798827
799- private def eraseDerivedValueClass (tp : Type )(using Context ): Type = {
828+ private def eraseDerivedValueClass (tp : Type )(using Context ): Type | Null = {
800829 val cls = tp.classSymbol.asClass
801830 val unbox = valueClassUnbox(cls)
802831 if unbox.exists then
@@ -806,6 +835,7 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst
806835 // The underlying part of an ErasedValueType cannot be an ErasedValueType itself
807836 val erase = erasureFn(sourceLanguage, semiEraseVCs = false , isConstructor, isSymbol, inSigName)
808837 val erasedUnderlying = erase(underlying)
838+ if erasedUnderlying == null then return null
809839
810840 // Ideally, we would just use `erasedUnderlying` as the erasure of `tp`, but to
811841 // be binary-compatible with Scala 2 we need two special cases for polymorphic
@@ -839,6 +869,7 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst
839869
840870 /** The erasure of a function result type. */
841871 def eraseResult (tp : Type )(using Context ): Type =
872+ assert(! inSigName) // therefore apply(...).nn won't fail
842873 // For a value class V, "new V(x)" should have type V for type adaptation to work
843874 // correctly (see SIP-15 and [[Erasure.Boxing.adaptToType]]), so the result type of a
844875 // constructor method should not be semi-erased.
@@ -848,18 +879,25 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst
848879 case tp : TypeRef =>
849880 val sym = tp.symbol
850881 if (sym eq defn.UnitClass ) sym.typeRef
851- else this (tp)
882+ else apply (tp).nn
852883 case tp : AppliedType =>
853884 val sym = tp.tycon.typeSymbol
854885 if (sym.isClass && ! erasureDependsOnArgs(sym)) eraseResult(tp.tycon)
855- else this (tp)
886+ else apply (tp).nn
856887 case _ =>
857- this (tp)
888+ apply (tp).nn
858889
859890 /** The name of the type as it is used in `Signature`s.
860- * Need to ensure correspondence with erasure!
891+ *
892+ * If `tp` is null, or if computing its erasure requires erasing a
893+ * WildcardType or an uninstantiated type variable, then the special name
894+ * `tpnme.Uninstantiated` which is used to signal an underdefined signature
895+ * is used.
896+ *
897+ * Note: Need to ensure correspondence with erasure!
861898 */
862- private def sigName (tp : Type )(using Context ): TypeName = try
899+ private def sigName (tp : Type | Null )(using Context ): TypeName = try
900+ if tp == null then return tpnme.Uninstantiated
863901 tp match {
864902 case tp : TypeRef =>
865903 if (! tp.denot.exists)
@@ -873,6 +911,7 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst
873911 }
874912 if (semiEraseVCs && isDerivedValueClass(sym)) {
875913 val erasedVCRef = eraseDerivedValueClass(tp)
914+ if erasedVCRef == null then return tpnme.Uninstantiated
876915 if (erasedVCRef.exists) return sigName(erasedVCRef)
877916 }
878917 if (defn.isSyntheticFunctionClass(sym))
@@ -897,14 +936,15 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst
897936 case ErasedValueType (_, underlying) =>
898937 sigName(underlying)
899938 case JavaArrayType (elem) =>
900- sigName(elem) ++ " []"
939+ val elemName = sigName(elem)
940+ if elemName eq tpnme.Uninstantiated then elemName
941+ else elemName ++ " []"
901942 case tp : TermRef =>
902943 sigName(underlyingOfTermRef(tp))
903944 case ExprType (rt) =>
904945 sigName(defn.FunctionOf (Nil , rt))
905- case tp : TypeVar =>
906- val inst = tp.instanceOpt
907- if (inst.exists) sigName(inst) else tpnme.Uninstantiated
946+ case tp : TypeVar if ! tp.isInstantiated =>
947+ tpnme.Uninstantiated
908948 case tp @ RefinedType (parent, nme.apply, _) if parent.typeSymbol eq defn.PolyFunctionClass =>
909949 // we need this case rather than falling through to the default
910950 // because RefinedTypes <: TypeProxy and it would be caught by
@@ -916,7 +956,9 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst
916956 sigName(tp.underlying)
917957 case tp : WildcardType =>
918958 tpnme.Uninstantiated
919- case _ : ErrorType | NoType =>
959+ case tp : ErrorType =>
960+ tpnme.ERROR
961+ case _ if tp eq NoType => // Can't write `case NoType` because of #18083.
920962 tpnme.ERROR
921963 case _ =>
922964 val erasedTp = this (tp)
0 commit comments