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

For migration to 3, accommodate case companion as function #10648

Merged
merged 4 commits into from
Feb 5, 2024
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
129 changes: 93 additions & 36 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(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)
def phasedAppliedType(sym: Symbol, args: List[Type]) = {
Expand Down Expand Up @@ -1122,13 +1124,43 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
}
}

// if user wrote case companion C for expected function type, use C.apply or (C.apply _).tupled
def adaptApplyInsertion(): Tree = doAdaptApplyInsertion(retry = false)

def doAdaptApplyInsertion(retry: Boolean): 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)), mode, if (retry) WildcardType else pt)) match {
case SilentResultValue(applicator) =>
val arity = definitions.functionArityFromType(applicator.tpe)
if (arity < 0) EmptyTree
else 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")
applicator
case _ => EmptyTree
}
case _ if !retry => doAdaptApplyInsertion(retry = true)
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 adaptMismatchedSkolems()
else
adaptApplyInsertion() orElse adaptMismatchedSkolems()
}

// TODO: should we even get to fallbackAfterVanillaAdapt for an ill-typed tree?
Expand Down Expand Up @@ -5360,6 +5392,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 (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))
val t1 = typedSelect(t, qual, nme.apply)
typed(atPos(tree.pos)(Select(etaExpand(t1, context.owner), name)), mode, pt)
}
if (!t2.isErroneous) {
SethTisue marked this conversation as resolved.
Show resolved Hide resolved
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 +5442,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 Expand Up @@ -5614,47 +5668,50 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
val fix = runReporting.codeAction("make reference explicit", tree.pos.focusStart, w.fix, w.msg)
runReporting.warning(tree.pos, w.msg, cat, context.owner, fix)
})
if (currentRun.isScala3) {
if (currentRun.isScala3)
tree.getAndRemoveAttachment[VirtualStringContext.type].foreach(_ =>
if (symbol != definitions.StringContextModule)
runReporting.warning(
tree.pos,
s"String interpolations always use scala.StringContext in Scala 3 (${symbol.fullNameString} is used here)",
Scala3Migration,
context.owner))
}
(// this -> Foo.this
if (symbol.isThisSym)
typed1(This(symbol.owner) setPos tree.pos, mode, pt)
else if (symbol.rawname == nme.classOf && currentRun.runDefinitions.isPredefClassOf(symbol) && pt.typeSymbol == ClassClass && pt.typeArgs.nonEmpty) {
// Inferring classOf type parameter from expected type. Otherwise an
// actual call to the stubbed classOf method is generated, returning null.
typedClassOf(tree, TypeTree(pt.typeArgs.head).setPos(tree.pos.focus))
}
else {
val pre1 = if (symbol.isTopLevel) symbol.owner.thisType else if (qual == EmptyTree) NoPrefix else qual.tpe
if (settings.lintUniversalMethods && !pre1.isInstanceOf[ThisType] && isUniversalMember(symbol))
context.warning(tree.pos, s"${symbol.nameString} not selected from this instance", WarningCategory.LintUniversalMethods)
val tree1 = if (qual == EmptyTree) tree else {
val pos = tree.pos
Select(atPos(pos.focusStart)(qual), name).setPos(pos)
context.owner)
)
val onSuccess =
if (symbol.isThisSym)
typed1(This(symbol.owner).setPos(tree.pos), mode, pt) // this -> Foo.this
else if (symbol.rawname == nme.classOf && currentRun.runDefinitions.isPredefClassOf(symbol) && pt.typeSymbol == ClassClass && pt.typeArgs.nonEmpty) {
// Inferring classOf type parameter from expected type. Otherwise an
// actual call to the stubbed classOf method is generated, returning null.
typedClassOf(tree, TypeTree(pt.typeArgs.head).setPos(tree.pos.focus))
}
var tree2: Tree = null
var pre2: Type = pre1
makeAccessible(tree1, symbol, pre1, qual) match {
case (t: Tree, tp: Type) =>
tree2 = t
pre2 = tp
case t: Tree =>
tree2 = t
case x => throw new MatchError(x)
else {
val pre1 = if (symbol.isTopLevel) symbol.owner.thisType else if (qual == EmptyTree) NoPrefix else qual.tpe
if (settings.lintUniversalMethods && !pre1.isInstanceOf[ThisType] && isUniversalMember(symbol))
context.warning(tree.pos, s"${symbol.nameString} not selected from this instance", WarningCategory.LintUniversalMethods)
val tree1 = if (qual == EmptyTree) tree else {
val pos = tree.pos
Select(atPos(pos.focusStart)(qual), name).setPos(pos)
}
var tree2: Tree = null
var pre2: Type = pre1
makeAccessible(tree1, symbol, pre1, qual) match {
case (t: Tree, tp: Type) =>
tree2 = t
pre2 = tp
case t: Tree =>
tree2 = t
case x => throw new MatchError(x)
}
// scala/bug#5967 Important to replace param type A* with Seq[A] when seen from from a reference,
// to avoid inference errors in pattern matching.
stabilize(tree2, pre2, mode, pt).modifyType(dropIllegalStarTypes)
}
// scala/bug#5967 Important to replace param type A* with Seq[A] when seen from from a reference, to avoid
// inference errors in pattern matching.
stabilize(tree2, pre2, mode, pt) modifyType dropIllegalStarTypes
}) setAttachments tree.attachments
}
if (!isPastTyper && currentRun.isScala3 && !currentRun.isScala3Cross && isFunctionType(pt) && symbol.isModule && symbol.isSynthetic && symbol.companion.isCase)
context.deprecationWarning(tree.pos, symbol, s"Synthetic case companion used as a Function, use explicit object with Function parent", "2.13.13")
onSuccess.setAttachments(tree.attachments)
}
}

def typedIdentOrWildcard(tree: Ident) = {
val name = tree.name
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 src/reflect/scala/reflect/internal/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -658,7 +658,7 @@ trait Definitions extends api.StandardDefinitions {
class VarArityClass(name: String, maxArity: Int, countFrom: Int = 0, init: Option[ClassSymbol] = None) extends VarArityClassApi {
private[this] val offset = countFrom - init.size
private def isDefinedAt(i: Int) = i < seq.length + offset && i >= offset
val seq: IndexedSeq[ClassSymbol] = (init ++: countFrom.to(maxArity).map { i => getRequiredClass("scala." + name + i) }).toVector
val seq: IndexedSeq[ClassSymbol] = (init ++: countFrom.to(maxArity).map(i => getRequiredClass(s"scala.$name$i"))).toVector
private[this] val symSet = new SymbolSet(seq.toList)
def contains(sym: Symbol): Boolean = symSet.contains(sym)
def apply(i: Int) = if (isDefinedAt(i)) seq(i - offset) else NoSymbol
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
6 changes: 6 additions & 0 deletions test/files/neg/t3664.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
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.
1 warning
1 error
10 changes: 10 additions & 0 deletions test/files/neg/t3664.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
//> using options -Werror -Xlint -Xsource:3

// 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)
}
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)
}
27 changes: 27 additions & 0 deletions test/files/neg/t3664c.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
t3664c.scala:20: 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:22: 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
^
t3664c.scala:24: error: type mismatch;
found : D.type
required: ((Int, Int)) => D
def d(xs: List[(Int, Int)]): List[D] = xs.map(D) // hard error
^
t3664c.scala:26: warning: An unapplied 0-arity method was eta-expanded (due to the expected type () => E), rather than applied to `()`.
Write E.apply() to invoke method apply, or change the expected type.
val e: () => E = E
^
t3664c.scala:26: warning: The method `apply` is inserted. The auto insertion will be deprecated, please write `E.apply` explicitly.
val e: () => E = E
^
t3664c.scala:28: warning: The method `apply` is inserted. The auto insertion will be deprecated, please write `F.apply` explicitly.
def ov(xs: List[Int]): List[F] = xs.map(F)
^
3 warnings
3 errors
29 changes: 29 additions & 0 deletions test/files/neg/t3664c.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//> 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)

abstract case class D(i: Int, j: Int)

case class E()

case class F(i: Int)
object F {
def apply(): F = apply(42)
def apply(i: Int): F = new F(i)
def apply(i: Int, j: Int): F = new F(i+j)
}

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

def d(xs: List[(Int, Int)]): List[D] = xs.map(D) // hard error

val e: () => E = E

def ov(xs: List[Int]): List[F] = xs.map(F)
}
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)
}
12 changes: 12 additions & 0 deletions test/files/run/t3664.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
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)
^
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)))
}