Skip to content
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

fix #14432: check if scala 2 case class is accessible #15008

Merged
merged 1 commit into from
May 6, 2022
Merged
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
27 changes: 24 additions & 3 deletions compiler/src/dotty/tools/dotc/typer/Synthesizer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,22 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
case OrType(tp1, tp2) => acceptable(tp1, cls) && acceptable(tp2, cls)
case _ => tp.classSymbol eq cls

/** for a case class, if it will have an anonymous mirror,
* check that its constructor can be accessed
* from the calling scope.
*/
def canAccessCtor(cls: Symbol): Boolean =
!genAnonyousMirror(cls) || {
def isAccessible(sym: Symbol): Boolean = ctx.owner.isContainedIn(sym)
def isSub(sym: Symbol): Boolean = ctx.owner.ownersIterator.exists(_.derivesFrom(sym))
val ctor = cls.primaryConstructor
(!ctor.isOneOf(Private | Protected) || isSub(cls)) // we cant access the ctor because we do not extend cls
&& (!ctor.privateWithin.exists || isAccessible(ctor.privateWithin)) // check scope is compatible
}

def genAnonyousMirror(cls: Symbol): Boolean =
cls.is(Scala2x) || cls.linkedClass.is(Case)

def makeProductMirror(cls: Symbol): Tree =
val accessors = cls.caseAccessors.filterNot(_.isAllOf(PrivateLocal))
val elemLabels = accessors.map(acc => ConstantType(Constant(acc.name.toString)))
Expand All @@ -300,7 +316,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
.refinedWith(tpnme.MirroredElemTypes, TypeAlias(elemsType))
.refinedWith(tpnme.MirroredElemLabels, TypeAlias(elemsLabels))
val mirrorRef =
if (cls.is(Scala2x) || cls.linkedClass.is(Case)) anonymousMirror(monoType, ExtendsProductMirror, span)
if (genAnonyousMirror(cls)) anonymousMirror(monoType, ExtendsProductMirror, span)
else companionPath(mirroredType, span)
mirrorRef.cast(mirrorType)
end makeProductMirror
Expand All @@ -321,8 +337,13 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
modulePath.cast(mirrorType)
else
val cls = mirroredType.classSymbol
if acceptable(mirroredType, cls) && cls.isGenericProduct then makeProductMirror(cls)
else EmptyTree
if acceptable(mirroredType, cls)
&& cls.isGenericProduct
&& canAccessCtor(cls)
then
makeProductMirror(cls)
else
EmptyTree
end productMirror

private def sumMirror(mirroredType: Type, formal: Type, span: Span)(using Context): Tree =
Expand Down
3 changes: 3 additions & 0 deletions sbt-test/scala2-compat/i14432/app1fail/Test.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import deriving.Mirror

val mFoo = summon[Mirror.Of[Foo]] // error: `Foo.<init>(Int)` is not accessible from `<empty>`.
8 changes: 8 additions & 0 deletions sbt-test/scala2-compat/i14432/app1ok/Test.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import deriving.Mirror

package example {
val mFoo = summon[Mirror.Of[Foo]] // ok, we can access Foo's ctor from here.
}

@main def Test: Unit =
assert(example.mFoo.fromProduct(Some(23)) == example.Foo(23))
5 changes: 5 additions & 0 deletions sbt-test/scala2-compat/i14432/app2fail/Test.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package example

import deriving.Mirror

val mFoo = summon[Mirror.Of[Foo]] // error: `Foo.<init>(Int)` is not accessible from any class.
30 changes: 30 additions & 0 deletions sbt-test/scala2-compat/i14432/build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
val scala3Version = sys.props("plugin.scalaVersion")
val scala2Version = sys.props("plugin.scala2Version")

lazy val lib1 = project.in(file("lib1"))
.settings(
scalaVersion := scala2Version
)

lazy val lib2 = project.in(file("lib2"))
.settings(
scalaVersion := scala2Version
)

lazy val app1fail = project.in(file("app1fail"))
.dependsOn(lib1)
.settings(
scalaVersion := scala3Version
)

lazy val app1ok = project.in(file("app1ok"))
.dependsOn(lib1)
.settings(
scalaVersion := scala3Version
)

lazy val app2fail = project.in(file("app2fail"))
.dependsOn(lib2)
.settings(
scalaVersion := scala3Version
)
3 changes: 3 additions & 0 deletions sbt-test/scala2-compat/i14432/lib1/Foo.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package example

case class Foo private[example] (i: Int)
3 changes: 3 additions & 0 deletions sbt-test/scala2-compat/i14432/lib2/Foo.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package example

case class Foo private (i: Int)
5 changes: 5 additions & 0 deletions sbt-test/scala2-compat/i14432/test
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
> lib1/compile
> lib2/compile
-> app1fail/compile
> app1ok/run
-> app2fail/compile
4 changes: 4 additions & 0 deletions tests/neg/i14432.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-- Error: tests/neg/i14432.scala:13:33 ---------------------------------------------------------------------------------
13 |val mFoo = summon[Mirror.Of[Foo]] // error: no mirror found
| ^
|no given instance of type deriving.Mirror.Of[example.Foo] was found for parameter x of method summon in object Predef
13 changes: 13 additions & 0 deletions tests/neg/i14432.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package example

import deriving.Mirror

case class Foo private (i: Int)

// case object companion here prevents Foo from caching
// the mirror in its companion, so all potential mirrors for Foo will be anonymous.
case object Foo

// however we can not provide an anonymous mirror
// at this call site because the constructor is not accessible.
val mFoo = summon[Mirror.Of[Foo]] // error: no mirror found
4 changes: 4 additions & 0 deletions tests/neg/i14432a.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-- Error: tests/neg/i14432a.scala:14:43 --------------------------------------------------------------------------------
14 | val mFoo = summon[Mirror.Of[example.Foo]] // error: no mirror found
| ^
|no given instance of type deriving.Mirror.Of[example.Foo] was found for parameter x of method summon in object Predef
15 changes: 15 additions & 0 deletions tests/neg/i14432a.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import deriving.Mirror

package example {
case class Foo private[example] (val i: Int)

// case object companion here prevents Foo from caching
// the mirror in its companion, so all potential mirrors for Foo will be anonymous.
case object Foo
}

@main def Test: Unit =
// however we can not provide an anonymous mirror
// at this call site because the constructor is not accessible.
val mFoo = summon[Mirror.Of[example.Foo]] // error: no mirror found
assert(mFoo.fromProduct(Tuple1(1)).i == 1)
4 changes: 4 additions & 0 deletions tests/neg/i14432b.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-- Error: tests/neg/i14432b.scala:15:43 --------------------------------------------------------------------------------
15 | val mFoo = summon[Mirror.Of[example.Foo]] // error: no mirror found
| ^
|no given instance of type deriving.Mirror.Of[example.Foo] was found for parameter x of method summon in object Predef
16 changes: 16 additions & 0 deletions tests/neg/i14432b.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import deriving.Mirror

package example {
case class Foo protected [example] (val i: Int)

// case object companion here prevents Foo from caching
// the mirror in its companion, so all potential mirrors for Foo will be anonymous.
case object Foo

}

class Bar extends example.Foo(23) {
// however we can not provide an anonymous mirror
// at this call site because the constructor is not accessible.
val mFoo = summon[Mirror.Of[example.Foo]] // error: no mirror found
}
12 changes: 12 additions & 0 deletions tests/neg/i14432c.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
-- Error: tests/neg/i14432c.scala:12:18 --------------------------------------------------------------------------------
12 |class Bar extends example.Foo(23) { // error // error: cant access private[example] ctor
| ^^^^^^^^^^^
| constructor Foo cannot be accessed as a member of example.Foo from class Bar.
-- Error: tests/neg/i14432c.scala:12:6 ---------------------------------------------------------------------------------
12 |class Bar extends example.Foo(23) { // error // error: cant access private[example] ctor
| ^
| constructor Foo cannot be accessed as a member of example.Foo from class Bar.
-- Error: tests/neg/i14432c.scala:16:43 --------------------------------------------------------------------------------
16 | val mFoo = summon[Mirror.Of[example.Foo]] // error: no mirror
| ^
|no given instance of type deriving.Mirror.Of[example.Foo] was found for parameter x of method summon in object Predef
18 changes: 18 additions & 0 deletions tests/neg/i14432c.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import deriving.Mirror

package example {
case class Foo private [example] (val i: Int)

// case object companion here prevents Foo from caching
// the mirror in its companion, so all potential mirrors for Foo will be anonymous.
case object Foo

}

class Bar extends example.Foo(23) { // error // error: cant access private[example] ctor

// however we can not provide an anonymous mirror
// at this call site because the constructor is not accessible.
val mFoo = summon[Mirror.Of[example.Foo]] // error: no mirror

}
4 changes: 4 additions & 0 deletions tests/neg/i14432d.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-- Error: tests/neg/i14432d.scala:17:45 --------------------------------------------------------------------------------
17 | val mFoo = summon[Mirror.Of[example.Foo]] // error
| ^
|no given instance of type deriving.Mirror.Of[example.Foo] was found for parameter x of method summon in object Predef
20 changes: 20 additions & 0 deletions tests/neg/i14432d.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import deriving.Mirror

package example {
case class Foo protected [example] (val i: Int)

// case object companion here prevents Foo from caching
// the mirror in its companion, so all potential mirrors for Foo will be anonymous.
case object Foo

}

class Bar extends example.Foo(23) {

class Inner {
// however we can not provide an anonymous mirror
// at this call site because the constructor is not accessible.
val mFoo = summon[Mirror.Of[example.Foo]] // error
}

}
11 changes: 11 additions & 0 deletions tests/run/i14432.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import deriving.Mirror

package example {
// Foo caches the mirror in its companion,
// which can still access the constructor.
case class Foo private (val i: Int)
}

@main def Test: Unit =
val mFoo = summon[Mirror.Of[example.Foo]]
assert(mFoo.fromProduct(Tuple1(1)).i == 1)
16 changes: 16 additions & 0 deletions tests/run/i14432a.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import deriving.Mirror

package example {
case class Foo private[example] (val i: Int)

// case object companion here prevents Foo from caching
// the mirror in its companion, so all potential mirrors for Foo will be anonymous.
case object Foo

// here, we can synthesize an anonymous mirror
// because at this call site the constructor is accessible.
val mFoo = summon[Mirror.Of[example.Foo]]
}

@main def Test: Unit =
assert(example.mFoo.fromProduct(Tuple1(1)).i == 1)
20 changes: 20 additions & 0 deletions tests/run/i14432b.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import deriving.Mirror

package example {
case class Foo protected [example] (val i: Int)

// case object companion here prevents Foo from caching
// the mirror in its companion, so all potential mirrors for Foo will be anonymous.
case object Foo

class Bar extends Foo(23) {
// here, we can synthesize an anonymous mirror
// because at this call site the constructor is accessible.
val mFoo = summon[Mirror.Of[example.Foo]]
}

}

@main def Test: Unit =
val bar = new example.Bar
assert(bar.mFoo.fromProduct(Tuple1(1)).i == 1)
23 changes: 23 additions & 0 deletions tests/run/i14432c.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import deriving.Mirror

package example {
case class Foo protected [example] (val i: Int)

// case object companion here prevents Foo from caching
// the mirror in its companion, so all potential mirrors for Foo will be anonymous.
case object Foo

class Bar extends Foo(23) {
class Inner {
// here, we can synthesize an anonymous mirror
// because at this call site the constructor is accessible.
val mFoo = summon[Mirror.Of[example.Foo]]
}
val inner = Inner()
}

}

@main def Test: Unit =
val bar = new example.Bar
assert(bar.inner.mFoo.fromProduct(Tuple1(1)).i == 1)