Skip to content

Fix #4342: Adapt outer types when calling into Scala 2 classes #4344

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 14 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1678,7 +1678,7 @@ object SymDenotations {
case tp @ AppliedType(tycon, args) =>
val subsym = tycon.typeSymbol
if (subsym eq symbol) tp
else tycon.typeParams match {
else (tycon.typeParams: @unchecked) match {
case LambdaParam(_, _) :: _ =>
baseTypeOf(tp.superType)
case tparams: List[Symbol @unchecked] =>
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2674,7 +2674,7 @@ object Types {
* either a list of type parameter symbols or a list of lambda parameters
*/
def integrate(tparams: List[ParamInfo], tp: Type)(implicit ctx: Context): Type =
tparams match {
(tparams: @unchecked) match {
case LambdaParam(lam, _) :: _ => tp.subst(lam, this)
case params: List[Symbol @unchecked] => tp.subst(params, paramRefs)
}
Expand Down
7 changes: 5 additions & 2 deletions compiler/src/dotty/tools/dotc/sbt/ExtractDependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -295,8 +295,12 @@ private class ExtractDependenciesCollector extends tpd.TreeTraverser { thisTreeT
val fromClass = resolveDependencySource
if (fromClass.exists) { // can happen when visiting imports
assert(fromClass.isClass)
_dependencies += ClassDependency(fromClass, enclOrModuleClass, DependencyByMemberRef)

addUsedName(fromClass, mangledName(sym), UseScope.Default)
// packages have class symbol. Only record them as used names but not dependency
if (!sym.is(Package)) {
_dependencies += ClassDependency(fromClass, enclOrModuleClass, DependencyByMemberRef)
}
}
}

Expand All @@ -315,7 +319,6 @@ private class ExtractDependenciesCollector extends tpd.TreeTraverser { thisTreeT
!sym.exists ||
sym.unforcedIsAbsent || // ignore dependencies that have a symbol but do not exist.
// e.g. java.lang.Object companion object
sym.is(PackageClass) ||
sym.isEffectiveRoot ||
sym.isAnonymousFunction ||
sym.isAnonymousClass
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ class ExpandSAMs extends MiniPhase {
cpy.Block(tree)(List(applyDef, isDefinedAtDef), anonCls)
}

private def checkRefinements(tpe: Type, pos: Position)(implicit ctx: Context): Type = tpe match {
private def checkRefinements(tpe: Type, pos: Position)(implicit ctx: Context): Type = tpe.dealias match {
case RefinedType(parent, name, _) =>
if (name.isTermName && tpe.member(name).symbol.ownersIterator.isEmpty) // if member defined in the refinement
ctx.error("Lambda does not define " + name, pos)
Expand Down
18 changes: 16 additions & 2 deletions compiler/src/dotty/tools/dotc/transform/ExplicitOuter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,20 @@ object ExplicitOuter {
private def newOuterAccessors(cls: ClassSymbol)(implicit ctx: Context) =
newOuterAccessor(cls, cls) :: (if (cls is Trait) Nil else newOuterParamAccessor(cls) :: Nil)

/** Scala 2.x and Dotty don't always agree on what should be the type of the outer parameter,
* so we replicate the old behavior when passing arguments to methods coming from Scala 2.x.
*/
private def outerClass(cls: ClassSymbol)(implicit ctx: Context): Symbol = {
val encl = cls.owner.enclosingClass
if (cls.is(Scala2x))
encl.asClass.classInfo.selfInfo match {
case tp: TypeRef => tp.classSymbol
case self: Symbol => self
case _ => encl
}
else encl
}

/** A new outer accessor or param accessor.
* @param owner The class where the outer accessor is located
* @param cls The class relative to which the outer is computed (can be a base class of owner)
Expand All @@ -156,7 +170,7 @@ object ExplicitOuter {
*/
private def newOuterSym(owner: ClassSymbol, cls: ClassSymbol, name: TermName, flags: FlagSet)(implicit ctx: Context) = {
val outerThis = owner.owner.enclosingClass.thisType
val outerCls = cls.owner.enclosingClass
val outerCls = outerClass(cls)
val target =
if (owner == cls)
outerCls.appliedRef
Expand Down Expand Up @@ -341,7 +355,7 @@ object ExplicitOuter {
if (hasOuterParam(cls)) {
val mt @ MethodTpe(pnames, ptypes, restpe) = tp
mt.derivedLambdaType(
nme.OUTER :: pnames, cls.owner.enclosingClass.typeRef :: ptypes, restpe)
nme.OUTER :: pnames, outerClass(cls).typeRef :: ptypes, restpe)
} else tp

/** If function in an apply node is a constructor that needs to be passed an
Expand Down
86 changes: 52 additions & 34 deletions compiler/src/dotty/tools/dotc/transform/patmat/Space.scala
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ trait SpaceLogic {
case Prod(tp, fun, sym, spaces, full) =>
val sp = Prod(tp, fun, sym, spaces.map(simplify(_)), full)
if (sp.params.contains(Empty)) Empty
else if (canDecompose(tp) && decompose(tp).isEmpty) Empty
else sp
case Or(spaces) =>
val set = spaces.map(simplify(_)).flatMap {
Expand Down Expand Up @@ -349,18 +350,19 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
Empty
}

/* Erase a type binding according to erasure semantics in pattern matching */
def erase(tp: Type): Type = tp match {
case tp @ AppliedType(tycon, args) =>
if (tycon.isRef(defn.ArrayClass)) tp.derivedAppliedType(tycon, args.map(erase))
else tp.derivedAppliedType(tycon, args.map(t => WildcardType))
case OrType(tp1, tp2) =>
OrType(erase(tp1), erase(tp2))
case AndType(tp1, tp2) =>
AndType(erase(tp1), erase(tp2))
case tp @ RefinedType(parent, refinedName, _) if refinedName.isTermName => // see pos/dependent-extractors.scala
tp.derivedRefinedType(erase(parent), refinedName, WildcardType)
case _ => tp
/* Erase pattern bound types with WildcardType */
def erase(tp: Type) = {
def isPatternTypeSymbol(sym: Symbol) = !sym.isClass && sym.is(Case)

val map = new TypeMap {
def apply(tp: Type) = tp match {
case tref: TypeRef if isPatternTypeSymbol(tref.typeSymbol) =>
tref.underlying.bounds
case _ => mapOver(tp)
}
}

map(tp)
}

/** Space of the pattern: unapplySeq(a, b, c: _*)
Expand All @@ -384,7 +386,7 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
/** Is `tp1` a subtype of `tp2`? */
def isSubType(tp1: Type, tp2: Type): Boolean = {
val res = (tp1 != nullType || tp2 == nullType) && tp1 <:< tp2
debug.println(s"${tp1.show} <:< ${tp2.show} = $res")
debug.println(s"${tp1} <:< ${tp2} = $res")
res
}

Expand Down Expand Up @@ -587,8 +589,8 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
noClassConflict &&
(!isSingleton(tp1) || tp1 <:< tp2) &&
(!isSingleton(tp2) || tp2 <:< tp1) &&
(!bases1.exists(_ is Final) || tp1 <:< tp2) &&
(!bases2.exists(_ is Final) || tp2 <:< tp1)
(!bases1.exists(_ is Final) || tp1 <:< maxTypeMap.apply(tp2)) &&
(!bases2.exists(_ is Final) || tp2 <:< maxTypeMap.apply(tp1))
}
case OrType(tp1, tp2) =>
recur(tp1) || recur(tp2)
Expand All @@ -607,6 +609,41 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
res
}

/** expose abstract type references to their bounds or tvars according to variance */
private class AbstractTypeMap(maximize: Boolean)(implicit ctx: Context) extends TypeMap {
def expose(lo: Type, hi: Type): Type =
if (variance == 0)
newTypeVar(TypeBounds(lo, hi))
else if (variance == 1)
if (maximize) hi else lo
else
if (maximize) lo else hi

def apply(tp: Type): Type = tp match {
case tp: TypeRef if tp.underlying.isInstanceOf[TypeBounds] =>
val lo = this(tp.info.loBound)
val hi = this(tp.info.hiBound)
// See tests/patmat/gadt.scala tests/patmat/exhausting.scala tests/patmat/t9657.scala
val exposed = expose(lo, hi)
debug.println(s"$tp exposed to =====> $exposed")
exposed

case AppliedType(tycon: TypeRef, args) if tycon.underlying.isInstanceOf[TypeBounds] =>
val args2 = args.map(this)
val lo = this(tycon.info.loBound).applyIfParameterized(args2)
val hi = this(tycon.info.hiBound).applyIfParameterized(args2)
val exposed = expose(lo, hi)
debug.println(s"$tp exposed to =====> $exposed")
exposed

case _ =>
mapOver(tp)
}
}

private def minTypeMap(implicit ctx: Context) = new AbstractTypeMap(maximize = false)
private def maxTypeMap(implicit ctx: Context) = new AbstractTypeMap(maximize = true)

/** Instantiate type `tp1` to be a subtype of `tp2`
*
* Return the instantiated type if type parameters and this type
Expand All @@ -616,25 +653,6 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
*
*/
def instantiate(tp1: NamedType, tp2: Type)(implicit ctx: Context): Type = {
// expose type param references to their bounds according to variance
class AbstractTypeMap(maximize: Boolean)(implicit ctx: Context) extends ApproximatingTypeMap {
variance = if (maximize) 1 else -1

def apply(tp: Type): Type = tp match {
case tp: TypeRef if tp.underlying.isInstanceOf[TypeBounds] =>
val lo = this(tp.info.loBound)
val hi = this(tp.info.hiBound)
// See tests/patmat/gadt.scala tests/patmat/exhausting.scala tests/patmat/t9657.scala
range(lo, hi)

case _ =>
mapOver(tp)
}
}

def minTypeMap(implicit ctx: Context) = new AbstractTypeMap(maximize = false)
def maxTypeMap(implicit ctx: Context) = new AbstractTypeMap(maximize = true)

// Fix subtype checking for child instantiation,
// such that `Foo(Test.this.foo) <:< Foo(Foo.this)`
// See tests/patmat/i3938.scala
Expand Down
2 changes: 1 addition & 1 deletion project/Build.scala
Original file line number Diff line number Diff line change
Expand Up @@ -900,7 +900,7 @@ object Build {
settings(commonSettings).
settings(
EclipseKeys.skipProject := true,
version := "0.1.3", // Keep in sync with package.json
version := "0.1.4", // Keep in sync with package.json
autoScalaLibrary := false,
publishArtifact := false,
includeFilter in unmanagedSources := NothingFilter | "*.ts" | "**.json",
Expand Down
19 changes: 19 additions & 0 deletions sbt-bridge/test/xsbt/DependencySpecification.scala
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,25 @@ class DependencySpecification {
assertEquals(Set("abc.A"), deps("H"))
}

@Test
def extractedClassDependenciesOnPackageObject = {
val srcA = "package object foo { def bar = 1 }"
val srcB =
"""|package foo
|
|class Test {
| bar
|}
""".stripMargin

val compilerForTesting = new ScalaCompilerForUnitTesting
val classDependencies =
compilerForTesting.extractDependenciesFromSrcs(srcA, srcB)

val memberRef = classDependencies.memberRef
assertEquals(Set("foo.package"), memberRef("foo.Test"))
}

private def extractClassDependenciesPublic: ExtractedClassDependencies = {
val srcA = "class A"
val srcB = "class B extends D[A]"
Expand Down
15 changes: 15 additions & 0 deletions sbt-bridge/test/xsbt/ExtractUsedNamesSpecification.scala
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,21 @@ class ExtractUsedNamesSpecification {
assertEquals(Set(), findPatMatUsages(notUsedInPatternMatch))
}

@Test
def extractedNamesInImport = {
val src =
"""|import java.util.List
|
|class Test
""".stripMargin

val compilerForTesting = new ScalaCompilerForUnitTesting
val usedNames = compilerForTesting.extractUsedNamesFromSrc(src)

val expectedNames = standardNames ++ Set("java", "util", "List")
assertEquals(expectedNames, usedNames("Test"))
}

/**
* Standard names that appear in every compilation unit that has any class
* definition.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package foo

object Test {
def test(implicit x: Int = 1): String = x.toString
def main(args: Array[String]): Unit = test
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package object foo {
implicit val x: Int = ???
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
> run

$ copy-file changes/package.scala package.scala

# New implicit in scope from package object that makes run throws
# clean run fails correctly
-> run
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import sbt._
import Keys._

object DottyInjectedPlugin extends AutoPlugin {
override def requires = plugins.JvmPlugin
override def trigger = allRequirements

override val projectSettings = Seq(
scalaVersion := sys.props("plugin.scalaVersion"),
scalacOptions += "-language:Scala2"
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % sys.props("plugin.version"))
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ object Test {
case x: Array[String] => x.size
case x: Array[AnyRef] => 5
case x: Array[_] => 6
case _ => 7
case _ => 7 // error: only null is matched
}
def f3[T](a: Array[T]) = a match {
case x: Array[Int] => x(0)
Expand All @@ -26,7 +26,7 @@ object Test {
case x: Array[String] => x.size
case x: Array[AnyRef] => 5
case x: Array[_] => 6
case _ => 7
case _ => 7 // error: only null is matched
}


Expand Down
19 changes: 19 additions & 0 deletions tests/neg/i4339.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
class Test {
def test: Unit = {
val a: PartialFunction[Int, Int]{ def foo: Int } = { case x => x } // error

type PF = PartialFunction[Int, Int] { def foo: Int }
val b: PF = { case x => x } // error

type PF1 = PartialFunction[Int, Int] {def foo: Int }
val c: PF1 {} = { case x => x } // error

type PF2 = PartialFunction[Int, Int]
val d: PF2 {} = { case x => x }

type PF3 = PartialFunction[Int, Int] {}
val e: PF3 = { case x => x }

val f: PartialFunction[Int, Int] {} { def foo: Int } = { case x => x } // error
}
}
10 changes: 10 additions & 0 deletions tests/patmat/i4314.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
sealed trait Foo[A]
case class One[A]() extends Foo[A]
sealed abstract case class Bar[A]() extends Foo[A]
class Two() extends Bar[String]

object Test {
def test(x: Foo[Int]) = x match {
case One() =>
}
}
1 change: 1 addition & 0 deletions tests/patmat/i4314b.check
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
9: Match case Unreachable
11 changes: 11 additions & 0 deletions tests/patmat/i4314b.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
sealed trait Foo[A]
case class One[A]() extends Foo[A]
sealed abstract case class Bar[A]() extends Foo[A]
class Two() extends Bar[String]

object Test {
def test(x: Foo[Int]) = x match {
case One() =>
case Bar() =>
}
}
Loading