diff --git a/compiler/src/dotty/tools/dotc/core/Denotations.scala b/compiler/src/dotty/tools/dotc/core/Denotations.scala index 9b7893925e29..5d4675dc207f 100644 --- a/compiler/src/dotty/tools/dotc/core/Denotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Denotations.scala @@ -582,8 +582,9 @@ object Denotations { */ def prefix: Type = NoPrefix - /** Either the Scala or Java signature of the info, depending on where the - * symbol is defined. + /** For SymDenotations, the language-specific signature of the info, depending on + * where the symbol is defined. For non-SymDenotations, the Scala 3 + * signature. * * Invariants: * - Before erasure, the signature of a denotation is always equal to the @@ -595,17 +596,17 @@ object Denotations { * SingleDenotations will have distinct signatures (cf #9050). */ final def signature(using Context): Signature = - signature(isJava = !isType && symbol.is(JavaDefined)) + signature(sourceLanguage = if isType || !this.isInstanceOf[SymDenotation] then SourceLanguage.Scala3 else SourceLanguage(symbol)) - /** Overload of `signature` which lets the caller pick between the Java and - * Scala signature of the info. Useful to match denotations defined in + /** Overload of `signature` which lets the caller pick the language used + * to compute the signature of the info. Useful to match denotations defined in * different classes (see `matchesLoosely`). */ - def signature(isJava: Boolean)(using Context): Signature = + def signature(sourceLanguage: SourceLanguage)(using Context): Signature = if (isType) Signature.NotAMethod // don't force info if this is a type denotation else info match { case info: MethodOrPoly => - try info.signature(isJava) + try info.signature(sourceLanguage) catch { // !!! DEBUG case scala.util.control.NonFatal(ex) => report.echo(s"cannot take signature of $info") @@ -1013,36 +1014,36 @@ object Denotations { /** `matches` without a target name check. * - * We consider a Scala method and a Java method to match if they have - * matching Scala signatures. This allows us to override some Java - * definitions even if they have a different erasure (see i8615b, - * i9109b), Erasure takes care of adding any necessary bridge to make - * this work at runtime. + * For definitions coming from different languages, we pick a common + * language to compute their signatures. This allows us for example to + * override some Java definitions from Scala even if they have a different + * erasure (see i8615b, i9109b), Erasure takes care of adding any necessary + * bridge to make this work at runtime. */ def matchesLoosely(other: SingleDenotation)(using Context): Boolean = if isType then true else - val isJava = symbol.is(JavaDefined) - val otherIsJava = other.symbol.is(JavaDefined) - val useJavaSig = isJava && otherIsJava - val sig = signature(isJava = useJavaSig) - val otherSig = other.signature(isJava = useJavaSig) + val thisLanguage = SourceLanguage(symbol) + val otherLanguage = SourceLanguage(other.symbol) + val commonLanguage = SourceLanguage.commonLanguage(thisLanguage, otherLanguage) + val sig = signature(commonLanguage) + val otherSig = other.signature(commonLanguage) sig.matchDegree(otherSig) match case FullMatch => true case MethodNotAMethodMatch => !ctx.erasedTypes && { // A Scala zero-parameter method and a Scala non-method always match. - if !isJava && !otherIsJava then + if !thisLanguage.isJava && !otherLanguage.isJava then true // Java allows defining both a field and a zero-parameter method with the same name, // so they must not match. - else if isJava && otherIsJava then + else if thisLanguage.isJava && otherLanguage.isJava then false // A Java field never matches a Scala method. - else if isJava then + else if thisLanguage.isJava then symbol.is(Method) - else // otherIsJava + else // otherLanguage.isJava other.symbol.is(Method) } case ParamMatch => diff --git a/compiler/src/dotty/tools/dotc/core/Signature.scala b/compiler/src/dotty/tools/dotc/core/Signature.scala index 4636c13dde66..d9b5dcca81e3 100644 --- a/compiler/src/dotty/tools/dotc/core/Signature.scala +++ b/compiler/src/dotty/tools/dotc/core/Signature.scala @@ -109,8 +109,8 @@ case class Signature(paramsSig: List[ParamSig], resSig: TypeName) { * * Like Signature#apply, the result is only cacheable if `isUnderDefined == false`. */ - def prependTermParams(params: List[Type], isJava: Boolean)(using Context): Signature = - Signature(params.map(p => sigName(p, isJava)) ::: paramsSig, resSig) + def prependTermParams(params: List[Type], sourceLanguage: SourceLanguage)(using Context): Signature = + Signature(params.map(p => sigName(p, sourceLanguage)) ::: paramsSig, resSig) /** Construct a signature by prepending the length of a type parameter section * to the parameter part of this signature. @@ -164,9 +164,9 @@ object Signature { * otherwise the signature will change once the contained type variables have * been instantiated. */ - def apply(resultType: Type, isJava: Boolean)(using Context): Signature = { + def apply(resultType: Type, sourceLanguage: SourceLanguage)(using Context): Signature = { assert(!resultType.isInstanceOf[ExprType]) - apply(Nil, sigName(resultType, isJava)) + apply(Nil, sigName(resultType, sourceLanguage)) } val lexicographicOrdering: Ordering[Signature] = new Ordering[Signature] { diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index 31c6982696cc..f0af9e7ffc28 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -10,10 +10,45 @@ import transform.ExplicitOuter._ import transform.ValueClasses._ import transform.TypeUtils._ import transform.ContextFunctionResults._ +import unpickleScala2.Scala2Erasure import Decorators._ import Definitions.MaxImplementedFunctionArity import scala.annotation.tailrec +/** The language in which the definition being erased was written. */ +enum SourceLanguage: + case Java, Scala2, Scala3 + def isJava: Boolean = this eq Java + def isScala2: Boolean = this eq Scala2 + def isScala3: Boolean = this eq Scala3 +object SourceLanguage: + /** The language in which `sym` was defined. */ + def apply(sym: Symbol)(using Context): SourceLanguage = + if sym.is(JavaDefined) then + SourceLanguage.Java + // Scala 2 methods don't have Inline set, except for the ones injected with `patchStdlibClass` + // which are really Scala 3 methods. + else if sym.isClass && sym.is(Scala2x) || (sym.maybeOwner.is(Scala2x) && !sym.is(Inline)) then + SourceLanguage.Scala2 + else + SourceLanguage.Scala3 + + /** Number of bits needed to represent this enum. */ + def bits: Int = + val len = values.length + val log2 = 31 - Integer.numberOfLeadingZeros(len) + if len == 1 << log2 then + log2 + else + log2 + 1 + + /** A common language to use when matching definitions written in different + * languages. + */ + def commonLanguage(x: SourceLanguage, y: SourceLanguage): SourceLanguage = + if x.ordinal > y.ordinal then x else y +end SourceLanguage + /** Erased types are: * * ErasedValueType @@ -107,28 +142,29 @@ object TypeErasure { } } - private def erasureIdx(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean, wildcardOK: Boolean) = - (if (isJava) 1 else 0) + - (if (semiEraseVCs) 2 else 0) + - (if (isConstructor) 4 else 0) + - (if (wildcardOK) 8 else 0) + private def erasureIdx(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConstructor: Boolean, wildcardOK: Boolean) = + extension (b: Boolean) def toInt = if b then 1 else 0 + wildcardOK.toInt + + (isConstructor.toInt << 1) + + (semiEraseVCs.toInt << 2) + + (sourceLanguage.ordinal << 3) - private val erasures = new Array[TypeErasure](16) + private val erasures = new Array[TypeErasure](1 << (SourceLanguage.bits + 3)) - for { - isJava <- List(false, true) + for + sourceLanguage <- SourceLanguage.values semiEraseVCs <- List(false, true) isConstructor <- List(false, true) wildcardOK <- List(false, true) - } - erasures(erasureIdx(isJava, semiEraseVCs, isConstructor, wildcardOK)) = - new TypeErasure(isJava, semiEraseVCs, isConstructor, wildcardOK) + do + erasures(erasureIdx(sourceLanguage, semiEraseVCs, isConstructor, wildcardOK)) = + new TypeErasure(sourceLanguage, semiEraseVCs, isConstructor, wildcardOK) /** Produces an erasure function. See the documentation of the class [[TypeErasure]] * for a description of each parameter. */ - private def erasureFn(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean, wildcardOK: Boolean): TypeErasure = - erasures(erasureIdx(isJava, semiEraseVCs, isConstructor, wildcardOK)) + private def erasureFn(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConstructor: Boolean, wildcardOK: Boolean): TypeErasure = + erasures(erasureIdx(sourceLanguage, semiEraseVCs, isConstructor, wildcardOK)) /** The current context with a phase no later than erasure */ def preErasureCtx(using Context) = @@ -139,7 +175,7 @@ object TypeErasure { * @param tp The type to erase. */ def erasure(tp: Type)(using Context): Type = - erasureFn(isJava = false, semiEraseVCs = false, isConstructor = false, wildcardOK = false)(tp)(using preErasureCtx) + erasureFn(sourceLanguage = SourceLanguage.Scala3, semiEraseVCs = false, isConstructor = false, wildcardOK = false)(tp)(using preErasureCtx) /** The value class erasure of a Scala type, where value classes are semi-erased to * ErasedValueType (they will be fully erased in [[ElimErasedValueType]]). @@ -147,7 +183,11 @@ object TypeErasure { * @param tp The type to erase. */ def valueErasure(tp: Type)(using Context): Type = - erasureFn(isJava = false, semiEraseVCs = true, isConstructor = false, wildcardOK = false)(tp)(using preErasureCtx) + erasureFn(sourceLanguage = SourceLanguage.Scala3, semiEraseVCs = true, isConstructor = false, wildcardOK = false)(tp)(using preErasureCtx) + + /** The erasure that Scala 2 would use for this type. */ + def scala2Erasure(tp: Type)(using Context): Type = + erasureFn(sourceLanguage = SourceLanguage.Scala2, semiEraseVCs = true, isConstructor = false, wildcardOK = false)(tp)(using preErasureCtx) /** Like value class erasure, but value classes erase to their underlying type erasure */ def fullErasure(tp: Type)(using Context): Type = @@ -155,9 +195,9 @@ object TypeErasure { case ErasedValueType(_, underlying) => erasure(underlying) case etp => etp - def sigName(tp: Type, isJava: Boolean)(using Context): TypeName = { - val normTp = tp.translateFromRepeated(toArray = isJava) - val erase = erasureFn(isJava, semiEraseVCs = true, isConstructor = false, wildcardOK = true) + def sigName(tp: Type, sourceLanguage: SourceLanguage)(using Context): TypeName = { + val normTp = tp.translateFromRepeated(toArray = sourceLanguage.isJava) + val erase = erasureFn(sourceLanguage, semiEraseVCs = !sourceLanguage.isJava, isConstructor = false, wildcardOK = true) erase.sigName(normTp)(using preErasureCtx) } @@ -181,15 +221,13 @@ object TypeErasure { * - For $asInstanceOf : [T]T * - For $isInstanceOf : [T]Boolean * - For all abstract types : = ? - * - For Java-defined symbols: : the erasure of their type with isJava = true, - * semiEraseVCs = false. Semi-erasure never happens in Java. - * - For all other symbols : the semi-erasure of their types, with - * isJava, isConstructor set according to symbol. + * + * `sourceLanguage`, `isConstructor` and `semiEraseVCs` are set based on the symbol. */ def transformInfo(sym: Symbol, tp: Type)(using Context): Type = { - val isJava = sym is JavaDefined - val semiEraseVCs = !isJava - val erase = erasureFn(isJava, semiEraseVCs, sym.isConstructor, wildcardOK = false) + val sourceLanguage = SourceLanguage(sym) + val semiEraseVCs = !sourceLanguage.isJava // Java sees our value classes as regular classes. + val erase = erasureFn(sourceLanguage, semiEraseVCs, sym.isConstructor, wildcardOK = false) def eraseParamBounds(tp: PolyType): Type = tp.derivedLambdaType( @@ -391,18 +429,20 @@ object TypeErasure { case _ => false } } + import TypeErasure._ /** - * @param isJava Arguments should be treated the way Java does it - * @param semiEraseVCs If true, value classes are semi-erased to ErasedValueType - * (they will be fully erased in [[ElimErasedValueType]]). - * If false, they are erased like normal classes. - * @param isConstructor Argument forms part of the type of a constructor - * @param wildcardOK Wildcards are acceptable (true when using the erasure - * for computing a signature name). + * @param sourceLanguage Adapt our erasure rules to mimic what the given language + * would do. + * @param semiEraseVCs If true, value classes are semi-erased to ErasedValueType + * (they will be fully erased in [[ElimErasedValueType]]). + * If false, they are erased like normal classes. + * @param isConstructor Argument forms part of the type of a constructor + * @param wildcardOK Wildcards are acceptable (true when using the erasure + * for computing a signature name). */ -class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean, wildcardOK: Boolean) { +class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConstructor: Boolean, wildcardOK: Boolean) { /** The erasure |T| of a type T. This is: * @@ -450,7 +490,7 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean val tycon = tp.tycon if (tycon.isRef(defn.ArrayClass)) eraseArray(tp) else if (tycon.isRef(defn.PairClass)) erasePair(tp) - else if (tp.isRepeatedParam) apply(tp.translateFromRepeated(toArray = isJava)) + else if (tp.isRepeatedParam) apply(tp.translateFromRepeated(toArray = sourceLanguage.isJava)) else if (semiEraseVCs && isDerivedValueClass(tycon.classSymbol)) eraseDerivedValueClass(tp) else apply(tp.translucentSuperType) case _: TermRef | _: ThisType => @@ -467,13 +507,16 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean this(defn.FunctionType(paramss.head.length, isContextual = res.isImplicitMethod, isErased = res.isErasedMethod)) case tp: TypeProxy => this(tp.underlying) - case AndType(tp1, tp2) => - erasedGlb(this(tp1), this(tp2), isJava) + case tp @ AndType(tp1, tp2) => + if sourceLanguage.isScala2 then + this(Scala2Erasure.intersectionDominator(Scala2Erasure.flattenedParents(tp))) + else + erasedGlb(this(tp1), this(tp2), isJava = sourceLanguage.isJava) case OrType(tp1, tp2) => TypeComparer.orType(this(tp1), this(tp2), isErased = true) case tp: MethodType => def paramErasure(tpToErase: Type) = - erasureFn(isJava, semiEraseVCs, isConstructor, wildcardOK)(tpToErase) + erasureFn(sourceLanguage, semiEraseVCs, isConstructor, wildcardOK)(tpToErase) val (names, formals0) = if (tp.isErasedMethod) (Nil, Nil) else (tp.paramNames, tp.paramInfos) val formals = formals0.mapConserve(paramErasure) eraseResult(tp.resultType) match { @@ -516,8 +559,8 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean private def eraseArray(tp: Type)(using Context) = { val defn.ArrayOf(elemtp) = tp if (classify(elemtp).derivesFrom(defn.NullClass)) JavaArrayType(defn.ObjectType) - else if (isUnboundedGeneric(elemtp) && !isJava) defn.ObjectType - else JavaArrayType(erasureFn(isJava, semiEraseVCs = false, isConstructor, wildcardOK)(elemtp)) + else if (isUnboundedGeneric(elemtp) && !sourceLanguage.isJava) defn.ObjectType + else JavaArrayType(erasureFn(sourceLanguage, semiEraseVCs = false, isConstructor, wildcardOK)(elemtp)) } private def erasePair(tp: Type)(using Context): Type = { @@ -544,7 +587,7 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean // See doc comment for ElimByName for speculation how we could improve this. else MethodType(Nil, Nil, - eraseResult(sym.info.finalResultType.translateFromRepeated(toArray = isJava))) + eraseResult(sym.info.finalResultType.translateFromRepeated(toArray = sourceLanguage.isJava))) case tp1: PolyType => eraseResult(tp1.resultType) match case rt: MethodType => rt @@ -596,7 +639,7 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean // correctly (see SIP-15 and [[Erasure.Boxing.adaptToType]]), so the result type of a // constructor method should not be semi-erased. if semiEraseVCs && isConstructor && !tp.isInstanceOf[MethodOrPoly] then - erasureFn(isJava, semiEraseVCs = false, isConstructor, wildcardOK).eraseResult(tp) + erasureFn(sourceLanguage, semiEraseVCs = false, isConstructor, wildcardOK).eraseResult(tp) else tp match case tp: TypeRef => val sym = tp.symbol @@ -624,7 +667,7 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean if (!info.exists) assert(false, i"undefined: $tp with symbol $sym") return sigName(info) } - if (isDerivedValueClass(sym)) { + if (semiEraseVCs && isDerivedValueClass(sym)) { val erasedVCRef = eraseDerivedValueClass(tp) if (erasedVCRef.exists) return sigName(erasedVCRef) } diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index d3c710033c29..cfd441f36bc6 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -3345,6 +3345,8 @@ object Types { private var mySignatureRunId: Int = NoRunId private var myJavaSignature: Signature = _ private var myJavaSignatureRunId: Int = NoRunId + private var myScala2Signature: Signature = _ + private var myScala2SignatureRunId: Int = NoRunId /** If `isJava` is false, the Scala signature of this method. Otherwise, its Java signature. * @@ -3360,31 +3362,37 @@ object Types { * * @see SingleDenotation#signature */ - def signature(isJava: Boolean)(using Context): Signature = - def computeSignature(isJava: Boolean)(using Context): Signature = + def signature(sourceLanguage: SourceLanguage)(using Context): Signature = + def computeSignature(using Context): Signature = val resultSignature = resultType match - case tp: MethodOrPoly => tp.signature(isJava) + case tp: MethodOrPoly => tp.signature(sourceLanguage) case tp: ExprType => tp.signature case tp => if tp.isRef(defn.UnitClass) then Signature(Nil, defn.UnitClass.fullName.asTypeName) - else Signature(tp, isJava) + else Signature(tp, sourceLanguage) this match case tp: MethodType => val params = if (isErasedMethod) Nil else tp.paramInfos - resultSignature.prependTermParams(params, isJava) + resultSignature.prependTermParams(params, sourceLanguage) case tp: PolyType => resultSignature.prependTypeParams(tp.paramNames.length) - if isJava then - if ctx.runId != myJavaSignatureRunId then - myJavaSignature = computeSignature(isJava) - if !myJavaSignature.isUnderDefined then myJavaSignatureRunId = ctx.runId - myJavaSignature - else - if ctx.runId != mySignatureRunId then - mySignature = computeSignature(isJava) - if !mySignature.isUnderDefined then mySignatureRunId = ctx.runId - mySignature + sourceLanguage match + case SourceLanguage.Java => + if ctx.runId != myJavaSignatureRunId then + myJavaSignature = computeSignature + if !myJavaSignature.isUnderDefined then myJavaSignatureRunId = ctx.runId + myJavaSignature + case SourceLanguage.Scala2 => + if ctx.runId != myScala2SignatureRunId then + myScala2Signature = computeSignature + if !myScala2Signature.isUnderDefined then myScala2SignatureRunId = ctx.runId + myScala2Signature + case SourceLanguage.Scala3 => + if ctx.runId != mySignatureRunId then + mySignature = computeSignature + if !mySignature.isUnderDefined then mySignatureRunId = ctx.runId + mySignature end signature /** The Scala signature of this method. Note that two distinct Java method @@ -3392,7 +3400,7 @@ object Types { * `signature` can be used to avoid ambiguity if necessary. */ final override def signature(using Context): Signature = - signature(isJava = false) + signature(sourceLanguage = SourceLanguage.Scala3) final override def hashCode: Int = System.identityHashCode(this) diff --git a/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Erasure.scala b/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Erasure.scala new file mode 100644 index 000000000000..5dde517c4864 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Erasure.scala @@ -0,0 +1,258 @@ +package dotty.tools +package dotc +package core +package unpickleScala2 + +import Symbols._, Types._, Contexts._, Flags._, Names._, StdNames._, Phases._ +import Decorators._ +import backend.sjs.JSDefinitions +import scala.collection.mutable.ListBuffer + +/** Erasure logic specific to Scala 2 symbols. */ +object Scala2Erasure: + /** Is this a supported Scala 2 refinement or parent of such a type? + * + * We do not allow types that look like: + * ((A with B) @foo) with C + * or: + * (A { type X <: ... })#X with C` + * + * as it would make our implementation of Scala 2 intersection erasure + * significantly more complicated. The problem is that each textual + * appearance of an intersection or refinement in a parent corresponds to a + * fresh instance of RefinedType (because Scala 2 does not hash-cons these + * types) with a fresh synthetic class symbol, thus affecting the result of + * `isNonBottomSubClass`. To complicate the matter, the Scala 2 UnCurry phase + * will also recursively dealias parent types, thus creating distinct class + * symbols even in situations where the same type alias is used to refer to a + * given refinement. Note that types like `(A with B) with C` do not run into + * these issues because they get flattened into a single RefinedType with + * three parents, cf `flattenedParents`. + * + * See sbt-dotty/sbt-test/scala2-compat/erasure/changes/Main.scala for examples. + * + * @throws TypeError if this type is unsupported. + */ + def checkSupported(tp: Type)(using Context): Unit = tp match + case AndType(tp1, tp2) => + checkSupported(tp1) + checkSupported(tp2) + case RefinedType(parent, _, _) => + checkSupported(parent) + case AnnotatedType(parent, _) if parent.dealias.isInstanceOf[Scala2RefinedType] => + throw new TypeError(i"Unsupported Scala 2 type: Component $parent of intersection is annotated.") + case tp @ TypeRef(prefix, _) if !tp.symbol.exists && prefix.dealias.isInstanceOf[Scala2RefinedType] => + throw new TypeError(i"Unsupported Scala 2 type: Prefix $prefix of intersection component is an intersection or refinement.") + case _ => + + /** A type that would be represented as a RefinedType in Scala 2. + * + * The `RefinedType` of Scala 2 contains both a list of parents + * and a list of refinements, intersections are represented as a RefinedType + * with no refinements. + */ + type Scala2RefinedType = RefinedType | AndType + + /** A TypeRef that is known to represent a member of a structural type. */ + type StructuralRef = TypeRef + + /** The equivalent of a Scala 2 type symbol. + * + * In some situations, nsc will create a symbol for a type where we wouldn't: + * + * - `A with B with C { ... }` is represented with a RefinedType whose + * symbol is a fresh class symbol whose parents are `A`, `B`, `C`. + * - Structural members also get their own symbols. + * + * To emulate this, we simply use the type itself as a stand-in for its symbol. + * + * See also `sameSymbol` which determines if two pseudo-symbols are really the same. + */ + type PseudoSymbol = Symbol | StructuralRef | Scala2RefinedType + + /** The pseudo symbol of `tp`, see `PseudoSymbol`. + * + * The pseudo-symbol representation of a given type is chosen such that + * `isNonBottomSubClass` behaves like it would in Scala 2, in particular + * this lets us strip all aliases. + */ + def pseudoSymbol(tp: Type)(using Context): PseudoSymbol = tp.widenDealias match + case tpw: Scala2RefinedType => + checkSupported(tpw) + tpw + case tpw: TypeRef => + val sym = tpw.symbol + if !sym.exists then + // Since we don't have symbols for structural type members we use the + // type itself and rely on `sameSymbol` to determine whether two + // such types would be represented with the same Scala 2 symbol. + tpw + else + sym + case tpw: TypeProxy => + pseudoSymbol(tpw.underlying) + case tpw: JavaArrayType => + defn.ArrayClass + case tpw: OrType => + pseudoSymbol(TypeErasure.scala2Erasure(tpw)) + case tpw: ErrorType => + defn.ObjectClass + case tpw => + throw new Error(s"Internal error: unhandled class ${tpw.getClass} for type $tpw in pseudoSymbol($tp)") + + extension (psym: PseudoSymbol)(using Context) + /** Would these two pseudo-symbols be represented with the same symbol in Scala 2? */ + def sameSymbol(other: PseudoSymbol): Boolean = + // Pattern match on (psym1, psym2) desugared by hand to avoid allocating a tuple + if psym.isInstanceOf[StructuralRef] && other.isInstanceOf[StructuralRef] then + val tp1 = psym.asInstanceOf[StructuralRef] + val tp2 = other.asInstanceOf[StructuralRef] + // Two structural members will have the same Scala 2 symbol if they + // point to the same member. We can't just call `=:=` since different + // prefixes will still have the same symbol. + (tp1.name eq tp2.name) && pseudoSymbol(tp1.prefix).sameSymbol(pseudoSymbol(tp2.prefix)) + else + // We intentionally use referential equality here even though we may end + // up comparing two equivalent intersection types, because Scala 2 will + // create fresh symbols for each appearance of an intersection type in + // source code. + psym eq other + + /** Is this a class symbol? Also returns true for refinements + * since they get a class symbol in Scala 2. + */ + def isClass: Boolean = psym match + case sym: Symbol => + sym.isClass + case _: Scala2RefinedType => + true + case _ => + false + + /** Is this a trait symbol? */ + def isTrait: Boolean = psym match + case sym: Symbol => + sym.is(Trait) + case _ => + false + + /** An emulation of `Symbol#isNonBottomSubClass` from Scala 2. + * + * The documentation of the original method is: + * + * > Is this class symbol a subclass of that symbol, + * > and is this class symbol also different from Null or Nothing? + * + * Which sounds fine, except that it is also used with non-class symbols, + * so what does it do then? Its implementation delegates to `Type#baseTypeSeq` + * whose documentation states: + * + * > The base type sequence of T is the smallest set of [...] class types Ti, so that [...] + * + * But this is also wrong: the sequence returned by `baseTypeSeq` can + * contain non-class symbols. + * + * Given that we cannot rely on the documentation and that the + * implementation is extremely complex, this reimplementation is mostly + * based on reverse-engineering rules derived from the observed behavior of + * the original method. + */ + def isNonBottomSubClass(that: PseudoSymbol): Boolean = + /** Recurse on the upper-bound of `psym`: an abstract type is a sub of a + * pseudo-symbol, if its upper-bound is a sub of that pseudo-symbol. + */ + def goUpperBound(psym: Symbol | StructuralRef): Boolean = + val info = psym match + case sym: Symbol => sym.info + case tp: StructuralRef => tp.info + info match + case info: TypeBounds => + go(pseudoSymbol(info.hi)) + case _ => + false + + def go(psym: PseudoSymbol): Boolean = + psym.sameSymbol(that) || + // As mentioned in the documentation of `Scala2RefinedType`, in Scala 2 + // these types get their own unique synthetic class symbol, therefore they + // don't have any sub-class Note that we must return false even if the lhs + // is an abstract type upper-bounded by this refinement, since each + // textual appearance of a refinement will have its own class symbol. + !that.isInstanceOf[Scala2RefinedType] && + psym.match + case sym1: Symbol => that match + case sym2: Symbol => + if sym1.isClass && sym2.isClass then + sym1.derivesFrom(sym2) + else if !sym1.isClass then + goUpperBound(sym1) + else + // sym2 is an abstract type, return false because + // `isNonBottomSubClass` in Scala 2 never considers a class C to + // be a a sub of an abstract type T, even if it was declared as + // `type T >: C`. + false + case _ => + goUpperBound(sym1) + case tp1: StructuralRef => + goUpperBound(tp1) + case tp1: RefinedType => + go(pseudoSymbol(tp1.parent)) + case AndType(tp11, tp12) => + go(pseudoSymbol(tp11)) || go(pseudoSymbol(tp12)) + end go + + go(psym) + end isNonBottomSubClass + end extension + + /** An emulation of `Erasure#intersectionDominator` from Scala 2. + * + * Accurately reproducing the behavior of this method is extremely difficult + * because it operates on the symbols of the _non-erased_ parent types, an + * implementation detail of the compiler. Furthermore, these non-class + * symbols are passed to methods such as `isNonBottomSubClass` whose behavior + * is only specified for class symbols. Therefore, the accuracy of this + * method cannot be guaranteed, the best we can do is make sure it works on + * as many test cases as possible which can be run from sbt using: + * > sbt-dotty/scripted scala2-compat/erasure + * + * The body of this method is made to look as much as the Scala 2 version as + * possible to make them easier to compare, cf: + * https://github.com/scala/scala/blob/v2.13.5/src/reflect/scala/reflect/internal/transform/Erasure.scala#L356-L389 + */ + def intersectionDominator(parents: List[Type])(using Context): Type = + val psyms = parents.map(pseudoSymbol) + if (psyms.contains(defn.ArrayClass)) { + defn.ArrayOf( + intersectionDominator(parents.collect { case defn.ArrayOf(arg) => arg })) + } else { + def isUnshadowed(psym: PseudoSymbol) = + !(psyms.exists(qsym => !psym.sameSymbol(qsym) && qsym.isNonBottomSubClass(psym))) + val cs = parents.iterator.filter { p => + val psym = pseudoSymbol(p) + psym.isClass && !psym.isTrait && isUnshadowed(psym) + } + (if (cs.hasNext) cs else parents.iterator.filter(p => isUnshadowed(pseudoSymbol(p)))).next() + } + + /** A flattened list of parents of this intersection. + * + * Mimic what Scala 2 does: intersections like `A with (B with C)` are + * flattened to three parents. + */ + def flattenedParents(tp: AndType)(using Context): List[Type] = + val parents = ListBuffer[Type]() + + def collect(parent: Type, parents: ListBuffer[Type]): Unit = parent.dealiasKeepAnnots match + case AndType(tp1, tp2) => + collect(tp1, parents) + collect(tp2, parents) + case _ => + checkSupported(parent) + parents += parent + + collect(tp, parents) + parents.toList + end flattenedParents +end Scala2Erasure diff --git a/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala b/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala index f77dc285ee8e..a38c2a12fd0a 100644 --- a/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala @@ -491,8 +491,6 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas val owner = sym.owner if (owner.isClass) owner.asClass.enter(sym, symScope(owner)) - else if (isRefinementClass(owner)) - symScope(owner).openForMutations.enter(sym) } sym } @@ -727,6 +725,11 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas * (if restpe is not a ClassInfoType, a MethodType or a NullaryMethodType, which leaves TypeRef/SingletonType -- the latter would make the polytype a type constructor) */ protected def readType()(using Context): Type = { + def select(pre: Type, sym: Symbol): Type = + // structural members need to be selected by name, their symbols are only + // valid in the synthetic refinement class that defines them. + if !pre.isInstanceOf[ThisType] && isRefinementClass(sym.owner) then pre.select(sym.name) else pre.select(sym) + val tag = readByte() val end = readNat() + readIndex (tag: @switch) match { @@ -739,7 +742,7 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas case SINGLEtpe => val pre = readPrefix() val sym = readDisambiguatedSymbolRef(_.info.isParameterless) - pre.select(sym) + select(pre, sym) case SUPERtpe => val thistpe = readTypeRef() val supertpe = readTypeRef() @@ -770,7 +773,7 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas pre = sym.owner.thisType case _ => } - val tycon = pre.select(sym) + val tycon = select(pre, sym) val args = until(end, () => readTypeRef()) if (sym == defn.ByNameParamClass2x) ExprType(args.head) else if (args.nonEmpty) tycon.safeAppliedTo(EtaExpandIfHK(sym.typeParams, args.map(translateTempPoly))) diff --git a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala index 4b28bdb18cc9..36a9812b9864 100644 --- a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala +++ b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala @@ -385,12 +385,12 @@ class SyntheticMembers(thisPhase: DenotTransformer) { private def hasWriteReplace(clazz: ClassSymbol)(using Context): Boolean = clazz.membersNamed(nme.writeReplace) - .filterWithPredicate(s => s.signature == Signature(defn.AnyRefType, isJava = false)) + .filterWithPredicate(s => s.signature == Signature(defn.AnyRefType, sourceLanguage = SourceLanguage.Scala3)) .exists private def hasReadResolve(clazz: ClassSymbol)(using Context): Boolean = clazz.membersNamed(nme.readResolve) - .filterWithPredicate(s => s.signature == Signature(defn.AnyRefType, isJava = false)) + .filterWithPredicate(s => s.signature == Signature(defn.AnyRefType, sourceLanguage = SourceLanguage.Scala3)) .exists private def writeReplaceDef(clazz: ClassSymbol)(using Context): TermSymbol = diff --git a/sbt-dotty/sbt-test/scala2-compat/erasure/build.sbt b/sbt-dotty/sbt-test/scala2-compat/erasure/build.sbt new file mode 100644 index 000000000000..1582924e0f41 --- /dev/null +++ b/sbt-dotty/sbt-test/scala2-compat/erasure/build.sbt @@ -0,0 +1,15 @@ +lazy val scala2Lib = project.in(file("scala2Lib")) + .settings( + scalaVersion := "2.13.2" + ) + +lazy val dottyApp = project.in(file("dottyApp")) + .dependsOn(scala2Lib) + .settings( + scalaVersion := sys.props("plugin.scalaVersion"), + // https://github.com/sbt/sbt/issues/5369 + projectDependencies := { + projectDependencies.value.map(_.withDottyCompat(scalaVersion.value)) + } + ) + diff --git a/sbt-dotty/sbt-test/scala2-compat/erasure/changes/Main.scala b/sbt-dotty/sbt-test/scala2-compat/erasure/changes/Main.scala new file mode 100644 index 000000000000..341c047d29bd --- /dev/null +++ b/sbt-dotty/sbt-test/scala2-compat/erasure/changes/Main.scala @@ -0,0 +1,18 @@ +object Main { + def main(args: Array[String]): Unit = { + val z = new scala2Lib.Z + + def dummy[T]: T = null.asInstanceOf[T] + + // None of these method calls should typecheck, see `Scala2Erasure#supportedType` + z.b_04(dummy) + z.b_04X(dummy) + z.b_05(dummy) + z.a_48(dummy) + z.c_49(dummy) + z.a_51(dummy) + z.a_53(dummy) + z.b_56(dummy) + z.a_57(dummy) + } +} diff --git a/sbt-dotty/sbt-test/scala2-compat/erasure/dottyApp/Api.scala b/sbt-dotty/sbt-test/scala2-compat/erasure/dottyApp/Api.scala new file mode 100644 index 000000000000..e665b1e4dfb8 --- /dev/null +++ b/sbt-dotty/sbt-test/scala2-compat/erasure/dottyApp/Api.scala @@ -0,0 +1,149 @@ +// Keep synchronized with scala2Lib/Api.scala +package dottyApp + +class foo extends scala.annotation.StaticAnnotation + +trait A +trait B +trait SubB extends B +trait C +trait Cov[+T] + +class D + +class VC(val self: A) extends AnyVal + +class Outer { + class E + trait F extends E +} + +// The parameter type of `a_XX` should erase to A, `b_XX` to `B`, etc. +// This is enforced by dottyApp/Main.scala +class Z { + def a_01(a: A with B): Unit = {} + def b_02X(b: B with A): Unit = {} + def a_02(a: A with B with A): Unit = {} + def a_03(a: A with (B with A)): Unit = {} + def a_04(b: A with (B with A) @foo): Unit = {} + def a_04X(b: A with (B with C) @foo): Unit = {} + def a_05(b: A with (B with A) @foo with (C with B with A) @foo): Unit = {} + + type T1 <: A with B + def a_06(a: T1): Unit = {} + + type S <: B with T1 + def b_07(a: S): Unit = {} + + type T2 <: B with A + type U <: T2 with S + def b_08(b: U): Unit = {} + + val singB: B = new B {} + def a_09(a: A with singB.type): Unit = {} + def b_10(b: singB.type with A): Unit = {} + + type V >: SubB <: B + def b_11(b: V): Unit = {} + def subb_12(b: V with SubB): Unit = {} + + def d_13(d: D with A): Unit = {} + def d_14(d: A with D): Unit = {} + + val singD: D = new D {} + def d_13x(d: singD.type with A): Unit = {} + def d_14x(d: A with singD.type): Unit = {} + + type DEq = D + def d_15(d: A with DEq): Unit = {} + def d_16(d: A with (DEq @foo)): Unit = {} + def d_17(d: DEq with A): Unit = {} + def d_18(d: (DEq @foo) with A): Unit = {} + + val singDEq: DEq @foo = new D {} + def d_15b(d: A with singDEq.type): Unit = {} + def d_16b(d: A with (singDEq.type @foo)): Unit = {} + + type DSub <: D + def d_19(a: A with DSub): Unit = {} + def d_19x(d: DSub with A): Unit = {} + def d_20(z: DSub with Z): Unit = {} + + type W1 <: A with Cov[Any] + type X1 <: Cov[Int] with W1 + def cov_21(a: X1): Unit = {} + + type W2 <: A with Cov[Any] + type X2 <: Cov[Int] with W2 + def cov_22(a: X2): Unit = {} + + def z_23(z: A with this.type): Unit = {} + def z_24(z: this.type with A): Unit = {} + + def a_25(b: A with (B { type T })): Unit = {} + def a_26(a: (A { type T }) with ((B with A) { type T })): Unit = {} + + def b_27(a: VC with B): Unit = {} + def b_28(a: B with VC): Unit = {} + + val o1: Outer = new Outer + val o2: Outer = new Outer + def f_29(f: o1.E with o1.F): Unit = {} + def f_30(f: o1.F with o1.E): Unit = {} + def f_31(f: o1.E with o2.F): Unit = {} + def f_32(f: o2.F with o1.E): Unit = {} + def f_33(f: Outer#E with Outer#F): Unit = {} + def f_34(f: Outer#F with Outer#E): Unit = {} + + val structural1: { type DSub <: D } = new { type DSub <: D } + def d_35(a: A with structural1.DSub): Unit = {} + def d_36(a: structural1.DSub with A): Unit = {} + def z_37(z: Z with structural1.DSub): Unit = {} + def d_38(z: structural1.DSub with Z): Unit = {} + + val structural2: { type SubCB <: C with B } = new { type SubCB <: C with B } + def c_39(c: structural2.SubCB with B): Unit = {} + def b_40(c: B with structural2.SubCB): Unit = {} + + val structural3a: { type SubB <: B; type SubCB <: C with SubB } = new { type SubB <: B; type SubCB <: C with SubB } + val structural3b: { type SubB <: B; type SubCB <: C with SubB } = new { type SubB <: B; type SubCB <: C with SubB } + def b_41(c: structural3a.SubB with structural3a.SubCB): Unit = {} + def c_42(c: structural3a.SubCB with structural3a.SubB): Unit = {} + def b_43(b: structural3a.SubB with structural3b.SubCB): Unit = {} + def c_44(c: structural3b.SubCB with structural3a.SubB): Unit = {} + + type SubStructural <: C with structural3a.SubB + def b_45(x: structural3a.SubB with SubStructural): Unit = {} + def b_46(x: structural3b.SubB with SubStructural): Unit = {} + + type Rec1 <: A with B + type Rec2 <: C with Rec1 + def a_47(a: A with B with Rec2): Unit = {} + def a_48(a: (A with B) @foo with Rec2): Unit = {} + + type F1 = A with B + type F2 = A with B + type Rec3 <: F1 + type Rec4 <: C with Rec3 + def a_49(a: F1 @foo with Rec4): Unit = {} + def a_50(a: F1 with Rec4): Unit = {} + def a_51(a: F2 @foo with Rec4): Unit = {} + def a_52(a: F2 with Rec4): Unit = {} + + type AA = A + type F3 = AA with B + type Rec5 <: F3 + type Rec6 <: C with Rec5 + def a_53(a: F3 @foo with Rec6): Unit = {} + def a_54(a: F3 with Rec6): Unit = {} + + val structural4a: { type M[X] <: A } = new { type M[X] <: A } + val structural4b: { type N <: B with structural4a.M[Int] } = new { type N <: B with structural4a.M[Int] } + def a_55(x: structural4a.M[Any] with structural4b.N): Unit = {} + + type Bla = A { type M[X] <: A } + def a_56(x: Bla#M[Any] with ({ type N <: B with Bla#M[Int] })#N): Unit = {} + type AEq = A + type Bla2 = AEq { type M[X] <: A } + def a_57(x: Bla2#M[Any] with ({ type N <: B with Bla2#M[Int] })#N): Unit = {} +} diff --git a/sbt-dotty/sbt-test/scala2-compat/erasure/dottyApp/Main.scala b/sbt-dotty/sbt-test/scala2-compat/erasure/dottyApp/Main.scala new file mode 100644 index 000000000000..6f2b2507a4b0 --- /dev/null +++ b/sbt-dotty/sbt-test/scala2-compat/erasure/dottyApp/Main.scala @@ -0,0 +1,82 @@ +object Main { + def main(args: Array[String]): Unit = { + val z = new scala2Lib.Z + + def dummy[T]: T = null.asInstanceOf[T] + + // Commented out lines intentionally do not typecheck and are + // tested in changes/Main.scala + z.a_01(dummy) + z.a_02(dummy) + z.b_02X(dummy) + z.a_03(dummy) + // z.b_04(dummy) + // z.b_04X(dummy) + // z.b_05(dummy) + z.a_06(dummy) + z.a_07(dummy) + z.b_08(dummy) + z.a_09(dummy) + z.b_10(dummy) + z.b_11(dummy) + z.b_12(dummy) + z.d_13(dummy) + z.d_14(dummy) + z.d_15(dummy) + z.d_15b(dummy) + z.d_16(dummy) + z.d_16b(dummy) + z.d_17(dummy) + z.d_18(dummy) + z.a_19(dummy) + z.d_19x(dummy) + z.z_20(dummy) + z.a_21(dummy) + z.a_22(dummy) + z.z_23(dummy) + z.z_24(dummy) + z.b_25(dummy) + z.a_26(dummy) + z.a_27(dummy) + z.a_28(dummy) + z.f_29(dummy) + z.f_30(dummy) + z.f_31(dummy) + z.f_32(dummy) + z.f_33(dummy) + z.f_34(dummy) + z.a_35(dummy) + z.d_36(dummy) + z.z_37(dummy) + z.z_38(dummy) + z.c_39(dummy) + z.c_40(dummy) + z.c_41(dummy) + z.c_42(dummy) + z.b_43(dummy) + z.c_44(dummy) + z.c_45(dummy) + z.b_46(dummy) + z.c_47(dummy) + // z.a_48(dummy) + // z.c_49(dummy) + z.c_50(dummy) + // z.a_51(dummy) + z.c_52(dummy) + // z.a_53(dummy) + z.c_54(dummy) + z.b_55(dummy) + // z.b_56(dummy) + // z.a_57(dummy) + + val methods = classOf[scala2Lib.Z].getDeclaredMethods.toList ++ classOf[dottyApp.Z].getDeclaredMethods.toList + methods.foreach { m => + m.getName match { + case s"${prefix}_${suffix}" => + val paramClass = m.getParameterTypes()(0).getSimpleName + assert(prefix == paramClass.toLowerCase, s"Method `$m` erased to `$paramClass` which does not match its prefix `$prefix`") + case _ => + } + } + } +} diff --git a/sbt-dotty/sbt-test/scala2-compat/erasure/project/plugins.sbt b/sbt-dotty/sbt-test/scala2-compat/erasure/project/plugins.sbt new file mode 100644 index 000000000000..c17caab2d98c --- /dev/null +++ b/sbt-dotty/sbt-test/scala2-compat/erasure/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % sys.props("plugin.version")) diff --git a/sbt-dotty/sbt-test/scala2-compat/erasure/scala2Lib/Api.scala b/sbt-dotty/sbt-test/scala2-compat/erasure/scala2Lib/Api.scala new file mode 100644 index 000000000000..7ea084a99b4e --- /dev/null +++ b/sbt-dotty/sbt-test/scala2-compat/erasure/scala2Lib/Api.scala @@ -0,0 +1,149 @@ +// Keep synchronized with dottyApp/Api.scala +package scala2Lib + +class foo extends scala.annotation.StaticAnnotation + +trait A +trait B +trait SubB extends B +trait C +trait Cov[+T] + +class D + +class VC(val self: A) extends AnyVal + +class Outer { + class E + trait F extends E +} + +// The parameter type of `a_XX` should erase to A, `b_XX` to `B`, etc. +// This is enforced by dottyApp/Main.scala +class Z { + def a_01(a: A with B): Unit = {} + def b_02X(b: B with A): Unit = {} + def a_02(a: A with B with A): Unit = {} + def a_03(a: A with (B with A)): Unit = {} + def b_04(b: A with (B with A) @foo): Unit = {} + def b_04X(b: A with (B with C) @foo): Unit = {} + def b_05(b: A with (B with A) @foo with (C with B with A) @foo): Unit = {} + + type T1 <: A with B + def a_06(a: T1): Unit = {} + + type S <: B with T1 + def a_07(a: S): Unit = {} + + type T2 <: B with A + type U <: T2 with S + def b_08(b: U): Unit = {} + + val singB: B = new B {} + def a_09(a: A with singB.type): Unit = {} + def b_10(b: singB.type with A): Unit = {} + + type V >: SubB <: B + def b_11(b: V): Unit = {} + def b_12(b: V with SubB): Unit = {} + + def d_13(d: D with A): Unit = {} + def d_14(d: A with D): Unit = {} + + val singD: D = new D {} + def d_13x(d: singD.type with A): Unit = {} + def d_14x(d: A with singD.type): Unit = {} + + type DEq = D + def d_15(d: A with DEq): Unit = {} + def d_16(d: A with (DEq @foo)): Unit = {} + def d_17(d: DEq with A): Unit = {} + def d_18(d: (DEq @foo) with A): Unit = {} + + val singDEq: DEq @foo = new D {} + def d_15b(d: A with singDEq.type): Unit = {} + def d_16b(d: A with (singDEq.type @foo)): Unit = {} + + type DSub <: D + def a_19(a: A with DSub): Unit = {} + def d_19x(d: DSub with A): Unit = {} + def z_20(z: DSub with Z): Unit = {} + + type W1 <: A with Cov[Any] + type X1 <: Cov[Int] with W1 + def a_21(a: X1): Unit = {} + + type W2 <: A with Cov[Any] + type X2 <: Cov[Int] with W2 + def a_22(a: X2): Unit = {} + + def z_23(z: A with this.type): Unit = {} + def z_24(z: this.type with A): Unit = {} + + def b_25(b: A with (B { type T })): Unit = {} + def a_26(a: (A { type T }) with ((B with A) { type T })): Unit = {} + + def a_27(a: VC with B): Unit = {} + def a_28(a: B with VC): Unit = {} + + val o1: Outer = new Outer + val o2: Outer = new Outer + def f_29(f: o1.E with o1.F): Unit = {} + def f_30(f: o1.F with o1.E): Unit = {} + def f_31(f: o1.E with o2.F): Unit = {} + def f_32(f: o2.F with o1.E): Unit = {} + def f_33(f: Outer#E with Outer#F): Unit = {} + def f_34(f: Outer#F with Outer#E): Unit = {} + + val structural1: { type DSub <: D } = new { type DSub <: D } + def a_35(a: A with structural1.DSub): Unit = {} + def d_36(a: structural1.DSub with A): Unit = {} + def z_37(z: Z with structural1.DSub): Unit = {} + def z_38(z: structural1.DSub with Z): Unit = {} + + val structural2: { type SubCB <: C with B } = new { type SubCB <: C with B } + def c_39(c: structural2.SubCB with B): Unit = {} + def c_40(c: B with structural2.SubCB): Unit = {} + + val structural3a: { type SubB <: B; type SubCB <: C with SubB } = new { type SubB <: B; type SubCB <: C with SubB } + val structural3b: { type SubB <: B; type SubCB <: C with SubB } = new { type SubB <: B; type SubCB <: C with SubB } + def c_41(c: structural3a.SubB with structural3a.SubCB): Unit = {} + def c_42(c: structural3a.SubCB with structural3a.SubB): Unit = {} + def b_43(b: structural3a.SubB with structural3b.SubCB): Unit = {} + def c_44(c: structural3b.SubCB with structural3a.SubB): Unit = {} + + type SubStructural <: C with structural3a.SubB + def c_45(x: structural3a.SubB with SubStructural): Unit = {} + def b_46(x: structural3b.SubB with SubStructural): Unit = {} + + type Rec1 <: A with B + type Rec2 <: C with Rec1 + def c_47(a: A with B with Rec2): Unit = {} + def a_48(a: (A with B) @foo with Rec2): Unit = {} + + type F1 = A with B + type F2 = A with B + type Rec3 <: F1 + type Rec4 <: C with Rec3 + def c_49(a: F1 @foo with Rec4): Unit = {} + def c_50(a: F1 with Rec4): Unit = {} + def a_51(a: F2 @foo with Rec4): Unit = {} + def c_52(a: F2 with Rec4): Unit = {} + + type AA = A + type F3 = AA with B + type Rec5 <: F3 + type Rec6 <: C with Rec5 + def a_53(a: F3 @foo with Rec6): Unit = {} + def c_54(a: F3 with Rec6): Unit = {} + + val structural4a: { type M[X] <: A } = new { type M[X] <: A } + val structural4b: { type N <: B with structural4a.M[Int] } = new { type N <: B with structural4a.M[Int] } + def b_55(x: structural4a.M[Any] with structural4b.N): Unit = {} + + type Bla = A { type M[X] <: A } + def b_56(x: Bla#M[Any] with ({ type N <: B with Bla#M[Int] })#N): Unit = {} + type AEq = A + type Bla2 = AEq { type M[X] <: A } + def a_57(x: Bla2#M[Any] with ({ type N <: B with Bla2#M[Int] })#N): Unit = {} +} diff --git a/sbt-dotty/sbt-test/scala2-compat/erasure/test b/sbt-dotty/sbt-test/scala2-compat/erasure/test new file mode 100644 index 000000000000..3ef735f7d6a1 --- /dev/null +++ b/sbt-dotty/sbt-test/scala2-compat/erasure/test @@ -0,0 +1,3 @@ +> dottyApp/run +$ copy-file changes/Main.scala dottyApp/Main.scala +-> dottyApp/compile diff --git a/sbt-dotty/sbt-test/scala2-compat/structural/build.sbt b/sbt-dotty/sbt-test/scala2-compat/structural/build.sbt new file mode 100644 index 000000000000..61d8f34cd5f6 --- /dev/null +++ b/sbt-dotty/sbt-test/scala2-compat/structural/build.sbt @@ -0,0 +1,9 @@ +val scala3Version = sys.props("plugin.scalaVersion") +val scala2Version = "2.13.5" + +lazy val lib = (project in file ("lib")) + .settings(scalaVersion := scala2Version) + +lazy val test = (project in file ("main")) + .dependsOn(lib) + .settings(scalaVersion := scala3Version) diff --git a/sbt-dotty/sbt-test/scala2-compat/structural/lib/lib.scala b/sbt-dotty/sbt-test/scala2-compat/structural/lib/lib.scala new file mode 100644 index 000000000000..7dadd67ab546 --- /dev/null +++ b/sbt-dotty/sbt-test/scala2-compat/structural/lib/lib.scala @@ -0,0 +1,10 @@ +class D +object Scala2 { + val structural1: { type DSub <: D; val member: Int } = new { type DSub <: D; val member: Int = 1 } + val dsub: structural1.DSub = null.asInstanceOf[structural1.DSub] + val mbr: structural1.member.type = structural1.member + + def a(a: structural1.DSub): Unit = {} + + def b(a: structural1.member.type): Unit = {} +} diff --git a/sbt-dotty/sbt-test/scala2-compat/structural/main/test.scala b/sbt-dotty/sbt-test/scala2-compat/structural/main/test.scala new file mode 100644 index 000000000000..ac108f64c425 --- /dev/null +++ b/sbt-dotty/sbt-test/scala2-compat/structural/main/test.scala @@ -0,0 +1,4 @@ +object Test extends App { + Scala2.a(Scala2.dsub) + Scala2.b(Scala2.mbr) +} diff --git a/sbt-dotty/sbt-test/scala2-compat/structural/project/plugins.sbt b/sbt-dotty/sbt-test/scala2-compat/structural/project/plugins.sbt new file mode 100644 index 000000000000..c17caab2d98c --- /dev/null +++ b/sbt-dotty/sbt-test/scala2-compat/structural/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % sys.props("plugin.version")) diff --git a/sbt-dotty/sbt-test/scala2-compat/structural/test b/sbt-dotty/sbt-test/scala2-compat/structural/test new file mode 100644 index 000000000000..8aedcd64cf5b --- /dev/null +++ b/sbt-dotty/sbt-test/scala2-compat/structural/test @@ -0,0 +1 @@ +> test/run diff --git a/tests/run/array-erasure.scala b/tests/run/array-erasure.scala new file mode 100644 index 000000000000..ba5f9e12c39c --- /dev/null +++ b/tests/run/array-erasure.scala @@ -0,0 +1,45 @@ +object Test { + def arr0[T](x: Array[T]) = { + assert(x(0) == 0) + x.sameElements(x) + x match { + case x: Array[Int] => + x(0) = 1 + x.sameElements(x) + } + assert(x(0) == 1) + } + + def arr1(x: Array[Int]) = { + assert(x(0) == 1) + x.sameElements(x) + x match { + case x: Array[_] => + x(0) = 2 + x.sameElements(x) + } + assert(x(0) == 2) + } + + def arr2[T](x: T) = { + x match { + case x: Array[_] => + assert(x(0) == 2) + x.sameElements(x) + } + x match { + case x: Array[Int] => + assert(x(0) == 2) + x(0) = 3 + x.sameElements(x) + } + } + + def main(args: Array[String]): Unit = { + val x: Array[Int] = Array(0) + + arr0(x) + arr1(x) + arr2(x) + } +} diff --git a/tests/run/i9175.scala b/tests/run/i9175.scala new file mode 100644 index 000000000000..d099de72a96d --- /dev/null +++ b/tests/run/i9175.scala @@ -0,0 +1,10 @@ +import scala.collection.immutable.SortedMap + +object Test { + + def main(args: Array[String]): Unit = { + val sortedMap = SortedMap("a" -> 1, "b" -> 2, "c" -> 3) + val empty = sortedMap.empty + println(empty) + } +} diff --git a/tests/run/java-vc-overload/A_2.java b/tests/run/java-vc-overload/A_2.java new file mode 100644 index 000000000000..caa333f45301 --- /dev/null +++ b/tests/run/java-vc-overload/A_2.java @@ -0,0 +1,4 @@ +public class A_2 { + public void foo(VC vc) {} + public void foo(String string) {} +} diff --git a/tests/run/java-vc-overload/Test_3.scala b/tests/run/java-vc-overload/Test_3.scala new file mode 100644 index 000000000000..34669f91f7f8 --- /dev/null +++ b/tests/run/java-vc-overload/Test_3.scala @@ -0,0 +1,7 @@ +object Test { + def main(args: Array[String]): Unit = { + val a = new A_2 + a.foo("") + a.foo(new VC("")) + } +} diff --git a/tests/run/java-vc-overload/VC_1.scala b/tests/run/java-vc-overload/VC_1.scala new file mode 100644 index 000000000000..e854861d77dc --- /dev/null +++ b/tests/run/java-vc-overload/VC_1.scala @@ -0,0 +1 @@ +class VC(val value: String) extends AnyVal