Skip to content

Commit

Permalink
Adapt apply/tupled/curried insertion for case companion
Browse files Browse the repository at this point in the history
  • Loading branch information
som-snytt committed Jan 31, 2024
1 parent eebf0b9 commit 751a250
Show file tree
Hide file tree
Showing 9 changed files with 107 additions and 9 deletions.
38 changes: 35 additions & 3 deletions src/compiler/scala/tools/nsc/typechecker/Typers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,8 @@ 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"))

// 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)
def phasedAppliedType(sym: Symbol, args: List[Type]) = {
Expand Down Expand Up @@ -1122,12 +1124,20 @@ 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)
}

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()
}

Expand Down Expand Up @@ -5360,6 +5370,24 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
if (!(context.unit.isJava && cls.isClass)) NoSymbol
else context.javaFindMember(cls.typeOfThis, name, s => s.isStaticMember || s.isStaticModule)._2

// 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 &&
context.undetparams.isEmpty && fixableFunctionMembers.contains(name)) {
val t2 = {
val t = atPos(tree.pos)(Select(qual, nme.apply))
val t1 = typedSelect(t, qual, nme.apply)
typed(atPos(tree.pos)(Select(etaExpand(t1, context.owner), name)), mode, pt)
}
if (!t2.isErroneous) {
val msg = s"The method `apply` is inserted. The auto insertion will be deprecated, please write `($qual.apply _).$name` explicitly."
context.deprecationWarning(tree.pos, qual.symbol, msg, "2.13.13")
t2
}
else EmptyTree
}
else EmptyTree

/* Attribute a selection where `tree` is `qual.name`.
* `qual` is already attributed.
*/
Expand Down Expand Up @@ -5392,9 +5420,13 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
context.warning(tree.pos, s"dubious usage of ${sel.symbol} with integer value", WarningCategory.LintNumericMethods)
}
val qual1 = adaptToMemberWithArgs(tree, qual, name, mode)
if ((qual1 ne qual) && !qual1.isErrorTyped)
return typed(treeCopy.Select(tree, qual1, name), mode, pt)
.tap(checkDubiousAdaptation)
val fixed =
if ((qual1 ne qual) && !qual1.isErrorTyped)
typed(treeCopy.Select(tree, qual1, name), mode, pt).tap(checkDubiousAdaptation)
else
fixUpCaseTupled(tree, qual, name, mode)
if (!fixed.isEmpty)
return fixed
}

// This special-case complements the logic in `adaptMember` in erasure, it handles selections
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/scala/tools/nsc/typechecker/Unapplies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ trait Unapplies extends ast.TreeDSL {
*/
def caseModuleDef(cdef: ClassDef): ModuleDef = {
val params = constrParamss(cdef)
def inheritFromFun = !cdef.mods.hasAbstractFlag && cdef.tparams.isEmpty && (params match {
def inheritFromFun = !currentRun.isScala3Cross && !cdef.mods.hasAbstractFlag && cdef.tparams.isEmpty && (params match {
case List(ps) if ps.length <= MaxFunctionArity => true
case _ => false
}) && applyAccess(constrMods(cdef)) != Inherit
Expand Down
2 changes: 1 addition & 1 deletion test/files/neg/t3664.check
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
t3664.scala:11: warning: Synthetic case companion used as a Function, use explicit object with Function parent
t3664.scala:9: warning: Synthetic case companion used as a Function, use explicit object with Function parent
def f(xs: List[Int]): List[C] = xs.map(C)
^
error: No warnings can be incurred under -Werror.
Expand Down
6 changes: 2 additions & 4 deletions test/files/neg/t3664.scala
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@

//> using options -Werror -Xlint -Xsource:3
//> abusing options -Werror -Xlint -Xsource:3migration

// use 3migration to warn that implicitly extending Function is deprecated
// use 3cross for dotty behavior: no extend Function, yes adapt C.apply.tupled
// 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)

Expand Down
6 changes: 6 additions & 0 deletions test/files/neg/t3664b.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
t3664b.scala:9: warning: The method `apply` is inserted. The auto insertion will be deprecated, please write `C.apply` explicitly.
def f(xs: List[Int]): List[C] = xs.map(C)
^
error: No warnings can be incurred under -Werror.
1 warning
1 error
10 changes: 10 additions & 0 deletions test/files/neg/t3664b.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
//> 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)

class Test {
def f(xs: List[Int]): List[C] = xs.map(C)
}
13 changes: 13 additions & 0 deletions test/files/pos/t3664.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@

//> using options -Werror -Xlint -Xsource:3-cross

import language.implicitConversions

case class C(i: Int)
object C // no function parent

// use conversion, don't warn about apply insertion
class Test {
implicit def cv(c: C.type): Function[Int, C] = C(_)
def f(xs: List[Int]): List[C] = xs.map(C)
}
9 changes: 9 additions & 0 deletions test/files/run/t3664.check
Original file line number Diff line number Diff line change
@@ -0,0 +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: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)
^
t3664.scala:17: warning: The method `apply` is inserted. The auto insertion will be deprecated, please write `(C.apply _).curried` explicitly.
def g(xs: List[Int]): List[C] = xs.map(C.curried).map(_(42))
^
30 changes: 30 additions & 0 deletions test/files/run/t3664.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//> using options -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 B(i: Int)
case class C(i: Int, j: Int)

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 f(xs: List[(Int, Int)]): List[C] = xs.map(C.tupled)

def g(xs: List[Int]): List[C] = xs.map(C.curried).map(_(42))

def f2(xs: List[(Int, Int)]): List[C] = xs.map((C.apply _).tupled)

def g2(xs: List[Int]): List[C] = xs.map((C.apply _).curried).map(_(42))

def g3(xs: List[Int]): List[C] = xs.map(((i: Int, j: Int) => C.apply(i, j)).curried).map(_(42))
}
object Test extends Test with App {
assert(mapped(List(52)) == List(B(52)))
//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 751a250

Please sign in to comment.