Skip to content

Commit

Permalink
Adapt tupled apply insertion for case companion
Browse files Browse the repository at this point in the history
  • Loading branch information
som-snytt committed Feb 1, 2024
1 parent 751a250 commit 393e77e
Show file tree
Hide file tree
Showing 6 changed files with 57 additions and 12 deletions.
38 changes: 28 additions & 10 deletions src/compiler/scala/tools/nsc/typechecker/Typers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
// requiring both the ACCESSOR and the SYNTHETIC bits to trigger the exemption
private def isSyntheticAccessor(sym: Symbol) = sym.isAccessor && (!sym.isLazy || isPastTyper)

private val fixableFunctionMembers = List(TermName("tupled"), TermName("curried"))
private val fixableFunctionMembers = List(nme.tupled, TermName("curried"))

// when type checking during erasure, generate erased types in spots that aren't transformed by erasure
// (it erases in TypeTrees, but not in, e.g., the type a Function node)
Expand Down Expand Up @@ -1124,21 +1124,39 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
}
}

def adaptApplyInsertion(): Tree = {
val msg = s"The method `apply` is inserted. The auto insertion will be deprecated, please write `${tree.symbol.name}.apply` explicitly."
context.deprecationWarning(tree.pos, tree.symbol, msg, "2.13.13")
typed(atPos(tree.pos)(Select(tree, nme.apply)), mode, pt)
}
// if user wrote case companion C for expected function type, use C.apply or (C.apply _).tupled
def adaptApplyInsertion(): Tree =
if (currentRun.isScala3Cross && !isPastTyper && tree.symbol != null && tree.symbol.isModule && tree.symbol.companion.isCase && isFunctionType(pt))
silent(_.typed(atPos(tree.pos)(Select(tree, nme.apply)))) match {
case SilentResultValue(applicator) =>
val arity = definitions.functionArityFromType(applicator.tpe)
functionOrPfOrSamArgTypes(pt) match {
case arg :: Nil if definitions.isTupleType(arg) && arg.typeArgs.lengthCompare(arity) == 0 =>
val tupled = typed(atPos(tree.pos)(Select(applicator, nme.tupled)), mode, pt)
if (!tupled.isErroneous) {
val msg = s"The method `apply` is inserted. The auto insertion will be deprecated, please write `(${tree.symbol.name}.apply _).tupled` explicitly."
context.deprecationWarning(tree.pos, tree.symbol, msg, "2.13.13")
tupled
}
else EmptyTree
case args if args.lengthCompare(arity) == 0 =>
val msg = s"The method `apply` is inserted. The auto insertion will be deprecated, please write `${tree.symbol.name}.apply` explicitly."
context.deprecationWarning(tree.pos, tree.symbol, msg, "2.13.13")
typed(atPos(tree.pos)(Select(tree, nme.apply)), mode, pt)
case _ => EmptyTree
}
case _ => EmptyTree
}
else EmptyTree

def adaptExprNotFunMode(): Tree = {
def lastTry(err: AbsTypeError = null): Tree = {
debuglog("error tree = " + tree)
if (settings.isDebug && settings.explaintypes.value) explainTypes(tree.tpe, pt)
if (err ne null) context.issue(err)
if (tree.tpe.isErroneous || pt.isErroneous) setError(tree)
else if (currentRun.isScala3Cross && isFunctionType(pt) && tree.symbol != null && tree.symbol.isModule && tree.symbol.companion.isCase)
adaptApplyInsertion()
else adaptMismatchedSkolems()
else
adaptApplyInsertion() orElse adaptMismatchedSkolems()
}

// TODO: should we even get to fallbackAfterVanillaAdapt for an ill-typed tree?
Expand Down Expand Up @@ -5372,7 +5390,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper

// If they try C.tupled, make it (C.apply _).tupled
def fixUpCaseTupled(tree: Tree, qual: Tree, name: Name, mode: Mode): Tree =
if (qual.symbol != null && currentRun.isScala3Cross && qual.symbol.isModule && qual.symbol.companion.isCase &&
if (currentRun.isScala3Cross && !isPastTyper && qual.symbol != null && qual.symbol.isModule && qual.symbol.companion.isCase &&
context.undetparams.isEmpty && fixableFunctionMembers.contains(name)) {
val t2 = {
val t = atPos(tree.pos)(Select(qual, nme.apply))
Expand Down
1 change: 1 addition & 0 deletions src/reflect/scala/reflect/internal/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -901,6 +901,7 @@ trait StdNames {
val tpe : NameType = nameType("tpe")
val tree : NameType = nameType("tree")
val true_ : NameType = nameType("true")
val tupled: NameType = nameType("tupled")
val typedProductIterator: NameType = nameType("typedProductIterator")
val TypeName: NameType = nameType("TypeName")
val typeTagToManifest: NameType = nameType("typeTagToManifest")
Expand Down
11 changes: 11 additions & 0 deletions test/files/neg/t3664c.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
t3664c.scala:9: error: type mismatch;
found : C.type
required: ((Int, Int, Int)) => C
def f(xs: List[(Int, Int, Int)]): List[C] = xs.map(C) // hard error
^
t3664c.scala:11: error: type mismatch;
found : ((Int, Int)) => C
required: ((Int, Int, Int)) => C
def g(xs: List[(Int, Int, Int)]): List[C] = xs.map(C.tupled) // hard error
^
2 errors
12 changes: 12 additions & 0 deletions test/files/neg/t3664c.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//> using options -Werror -Xlint -Xsource:3-cross

// use -Xsource:3 to warn that implicitly extending Function is deprecated
// use -Xsource:3-cross for dotty behavior: no extend Function, yes adapt C.apply.tupled

case class C(i: Int, j: Int)

class Test {
def f(xs: List[(Int, Int, Int)]): List[C] = xs.map(C) // hard error

def g(xs: List[(Int, Int, Int)]): List[C] = xs.map(C.tupled) // hard error
}
3 changes: 3 additions & 0 deletions test/files/run/t3664.check
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
t3664.scala:10: warning: The method `apply` is inserted. The auto insertion will be deprecated, please write `B.apply` explicitly.
def mapped(xs: List[Int]): List[B] = xs.map(B)
^
t3664.scala:13: warning: The method `apply` is inserted. The auto insertion will be deprecated, please write `(C.apply _).tupled` explicitly.
def cross(xs: List[(Int, Int)]): List[C] = xs.map(C)
^
t3664.scala:15: warning: The method `apply` is inserted. The auto insertion will be deprecated, please write `(C.apply _).tupled` explicitly.
def f(xs: List[(Int, Int)]): List[C] = xs.map(C.tupled)
^
Expand Down
4 changes: 2 additions & 2 deletions test/files/run/t3664.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class Test {
def mapped(xs: List[Int]): List[B] = xs.map(B)

// accept for cross because dotty has no C.tupled but has fancy untupling adaptation
//def cross(xs: List[(Int, Int)]): List[C] = xs.map(C)
def cross(xs: List[(Int, Int)]): List[C] = xs.map(C)

def f(xs: List[(Int, Int)]): List[C] = xs.map(C.tupled)

Expand All @@ -24,7 +24,7 @@ class Test {
}
object Test extends Test with App {
assert(mapped(List(52)) == List(B(52)))
//assert(cross(List(27->42)) == List(C(27, 42)))
assert(cross(List(27->42)) == List(C(27, 42)))
assert(f(List(27->42)) == List(C(27, 42)))
assert(g(List(27)) == List(C(27, 42)))
}

0 comments on commit 393e77e

Please sign in to comment.