Skip to content

Fix #6190: eta-expand companion object if functions are expected #7207

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

Merged
merged 11 commits into from
Dec 9, 2020
2 changes: 1 addition & 1 deletion community-build/community-projects/endpoints4s
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ final case class SbtCommunityProject(
)

private val baseCommand =
"clean; set logLevel in Global := Level.Error; set updateOptions in Global ~= (_.withLatestSnapshots(false)); "
"clean; set updateOptions in Global ~= (_.withLatestSnapshots(false)); "
++ s"""set dependencyOverrides in ThisBuild ++= ${dependencyOverrides.mkString("Seq(", ", ", ")")}; """
++ s"++$compilerVersion!; "

Expand Down
6 changes: 3 additions & 3 deletions compiler/src/dotty/tools/backend/sjs/JSExportsGen.scala
Original file line number Diff line number Diff line change
Expand Up @@ -330,12 +330,12 @@ final class JSExportsGen(jsCodeGen: JSCodeGen)(using Context) {
if (isProp)
genExportProperty(alts, jsName, static)
else
genExportMethod(alts.map(Exported), jsName, static)
genExportMethod(alts.map(Exported.apply), jsName, static)
}
}

def genJSConstructorDispatch(alts: List[Symbol]): (Option[List[js.ParamDef]], js.JSMethodDef) = {
val exporteds = alts.map(Exported)
val exporteds = alts.map(Exported.apply)

val isConstructorOfNestedJSClass = exporteds.head.isConstructorOfNestedJSClass
assert(exporteds.tail.forall(_.isConstructorOfNestedJSClass == isConstructorOfNestedJSClass),
Expand Down Expand Up @@ -391,7 +391,7 @@ final class JSExportsGen(jsCodeGen: JSCodeGen)(using Context) {
} else {
val formalArgsRegistry = new FormalArgsRegistry(1, false)
val List(arg) = formalArgsRegistry.genFormalArgs()
val body = genExportSameArgc(jsName, formalArgsRegistry, setters.map(Exported), static, None)
val body = genExportSameArgc(jsName, formalArgsRegistry, setters.map(Exported.apply), static, None)
Some((arg, body))
}
}
Expand Down
24 changes: 1 addition & 23 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -676,28 +676,6 @@ object desugar {
mods.is(Private) || (!mods.is(Protected) && mods.hasPrivateWithin)
}

/** Does one of the parameter's types (in the first param clause)
* mention a preceding parameter?
*/
def isParamDependent = constrVparamss match
case vparams :: _ =>
val paramNames = vparams.map(_.name).toSet
vparams.exists(_.tpt.existsSubTree {
case Ident(name: TermName) => paramNames.contains(name)
case _ => false
})
case _ => false

val companionParent =
if constrTparams.nonEmpty
|| constrVparamss.length > 1
|| mods.is(Abstract)
|| restrictedAccess
|| isParamDependent
|| isEnumCase
then anyRef
else
constrVparamss.foldRight(classTypeRef)((vparams, restpe) => Function(vparams map (_.tpt), restpe))
val applyMeths =
if (mods.is(Abstract)) Nil
else {
Expand Down Expand Up @@ -727,7 +705,7 @@ object desugar {
val toStringMeth =
DefDef(nme.toString_, Nil, Nil, TypeTree(), Literal(Constant(className.toString))).withMods(Modifiers(Override | Synthetic))

companionDefs(companionParent, applyMeths ::: unapplyMeth :: toStringMeth :: companionMembers)
companionDefs(anyRef, applyMeths ::: unapplyMeth :: toStringMeth :: companionMembers)
}
else if (companionMembers.nonEmpty || companionDerived.nonEmpty || isEnum)
companionDefs(anyRef, companionMembers)
Expand Down
8 changes: 0 additions & 8 deletions compiler/src/dotty/tools/dotc/ast/TreeInfo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -586,14 +586,6 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>
case _ => false
}

/** Is tree a compiler-generated `.apply` node that refers to the
* apply of a function class?
*/
def isSyntheticApply(tree: Tree): Boolean = tree match {
case Select(qual, nme.apply) => tree.span.end == qual.span.end
case _ => false
}

/** Strips layers of `.asInstanceOf[T]` / `_.$asInstanceOf[T]()` from an expression */
def stripCast(tree: Tree)(using Context): Tree = {
def isCast(sel: Tree) = sel.symbol.isTypeCast
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ final class JrtClassPath(fs: java.nio.file.FileSystem) extends ClassPath with No
}

case class DirectoryClassPath(dir: JFile) extends JFileDirectoryLookup[ClassFileEntryImpl] with NoSourcePaths {
override def findClass(className: String): Option[ClassRepresentation] = findClassFile(className) map ClassFileEntryImpl
override def findClass(className: String): Option[ClassRepresentation] = findClassFile(className) map ClassFileEntryImpl.apply

def findClassFile(className: String): Option[AbstractFile] = {
val relativePath = FileUtils.dirPath(className)
Expand All @@ -220,7 +220,7 @@ case class DirectorySourcePath(dir: JFile) extends JFileDirectoryLookup[SourceFi
protected def createFileEntry(file: AbstractFile): SourceFileEntryImpl = SourceFileEntryImpl(file)
protected def isMatchingFile(f: JFile): Boolean = endsScalaOrJava(f.getName)

override def findClass(className: String): Option[ClassRepresentation] = findSourceFile(className) map SourceFileEntryImpl
override def findClass(className: String): Option[ClassRepresentation] = findSourceFile(className) map SourceFileEntryImpl.apply

private def findSourceFile(className: String): Option[AbstractFile] = {
val relativePath = FileUtils.dirPath(className)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ case class VirtualDirectoryClassPath(dir: VirtualDirectory) extends ClassPath wi
def asURLs: Seq[URL] = Seq(new URL(dir.name))
def asClassPathStrings: Seq[String] = Seq(dir.path)

override def findClass(className: String): Option[ClassRepresentation] = findClassFile(className) map ClassFileEntryImpl
override def findClass(className: String): Option[ClassRepresentation] = findClassFile(className) map ClassFileEntryImpl.apply

def findClassFile(className: String): Option[AbstractFile] = {
val relativePath = FileUtils.dirPath(className) + ".class"
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ object Parsers {
if source.isSelfContained then new ScriptParser(source)
else new Parser(source)

private val InCase: Region => Region = Scanners.InCase
private val InCond: Region => Region = Scanners.InBraces
private val InCase: Region => Region = Scanners.InCase.apply
private val InCond: Region => Region = Scanners.InBraces.apply

abstract class ParserCommon(val source: SourceFile)(using Context) {

Expand Down
38 changes: 24 additions & 14 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,14 @@ object Typer {
*/
private[typer] val HiddenSearchFailure = new Property.Key[List[SearchFailure]]

/** Is tree a compiler-generated `.apply` node that refers to the
* apply of a function class?
*/
private[typer] def isSyntheticApply(tree: tpd.Tree): Boolean = tree match {
case tree: tpd.Select => tree.hasAttachment(InsertedApply)
case _ => false
}

/** Add `fail` to the list of search failures attached to `tree` */
def rememberSearchFailure(tree: tpd.Tree, fail: SearchFailure) =
tree.putAttachment(HiddenSearchFailure,
Expand Down Expand Up @@ -2843,11 +2851,6 @@ class Typer extends Namer
case _ => false
}

def isSyntheticApply(tree: Tree): Boolean = tree match {
case tree: Select => tree.hasAttachment(InsertedApply)
case _ => false
}

def tryApply(using Context) = {
val pt1 = pt.withContext(ctx)
val sel = typedSelect(untpd.Select(untpd.TypedSplice(tree), nme.apply), pt1)
Expand Down Expand Up @@ -3222,9 +3225,6 @@ class Typer extends Namer
* Examples for these cases are found in run/implicitFuns.scala and neg/i2006.scala.
*/
def adaptNoArgsUnappliedMethod(wtp: MethodType, functionExpected: Boolean, arity: Int): Tree = {
def isExpandableApply =
defn.isContextFunctionClass(tree.symbol.maybeOwner) && functionExpected

/** Is reference to this symbol `f` automatically expanded to `f()`? */
def isAutoApplied(sym: Symbol): Boolean =
sym.isConstructor
Expand All @@ -3241,7 +3241,7 @@ class Typer extends Namer
!tree.symbol.isConstructor &&
!tree.symbol.isAllOf(InlineMethod) &&
!ctx.mode.is(Mode.Pattern) &&
!(isSyntheticApply(tree) && !isExpandableApply)) {
!(isSyntheticApply(tree) && !functionExpected)) {
if (!defn.isFunctionType(pt))
pt match {
case SAMType(_) if !pt.classSymbol.hasAnnotation(defn.FunctionalInterfaceAnnot) =>
Expand All @@ -3266,16 +3266,26 @@ class Typer extends Namer
defn.isContextFunctionClass(underlying.classSymbol)
}

def adaptNoArgsOther(wtp: Type): Tree = {
if (isContextFunctionRef(wtp) &&
!untpd.isContextualClosure(tree) &&
def adaptNoArgsOther(wtp: Type, functionExpected: Boolean): Tree = {
val implicitFun = isContextFunctionRef(wtp) && !untpd.isContextualClosure(tree)
def caseCompanion =
functionExpected &&
tree.symbol.is(Module) &&
tree.symbol.companionClass.is(Case) &&
!tree.tpe.baseClasses.exists(defn.isFunctionClass) && {
report.warning("The method `apply` is inserted. The auto insertion will be deprecated, please write `" + tree.show + ".apply` explicitly.", tree.sourcePos)
true
}

if ((implicitFun || caseCompanion) &&
!isApplyProto(pt) &&
pt != AssignProto &&
!ctx.mode.is(Mode.Pattern) &&
!ctx.isAfterTyper &&
!ctx.isInlineContext) {
typr.println(i"insert apply on implicit $tree")
typed(untpd.Select(untpd.TypedSplice(tree), nme.apply), pt, locked)
val sel = untpd.Select(untpd.TypedSplice(tree), nme.apply).withAttachment(InsertedApply, ())
try typed(sel, pt, locked) finally sel.removeAttachment(InsertedApply)
}
else if (ctx.mode is Mode.Pattern) {
checkEqualityEvidence(tree, pt)
Expand Down Expand Up @@ -3390,7 +3400,7 @@ class Typer extends Namer
}
adaptNoArgsUnappliedMethod(wtp, funExpected, arity)
case _ =>
adaptNoArgsOther(wtp)
adaptNoArgsOther(wtp, functionExpected)
}
}

Expand Down
10 changes: 5 additions & 5 deletions doc-tool/src/dotty/tools/dottydoc/core/DocASTPhase.scala
Original file line number Diff line number Diff line change
Expand Up @@ -114,11 +114,11 @@ class DocASTPhase extends Phase {
case c @ TypeDef(n, rhs) if c.symbol.isClass =>
//TODO: should not `collectMember` from `rhs` - instead: get from symbol, will get inherited members as well
val parameters = (c.symbol, annotations(c.symbol), n.show, collectMembers(rhs), flags(c), path(c.symbol), typeParams(c.symbol), constructors(c.symbol), superTypes(c), None, Nil, None)
if (c.symbol.is(Flags.CaseClass)) {
CaseClassImpl.tupled(parameters) :: Nil
} else {
ClassImpl.tupled(parameters) :: Nil
}
val constr =
if (c.symbol.is(Flags.CaseClass)) CaseClassImpl.apply
else ClassImpl.apply

constr.tupled(parameters) :: Nil

/** def */
case d: DefDef =>
Expand Down
2 changes: 1 addition & 1 deletion doc-tool/src/dotty/tools/dottydoc/util/MemberLookup.scala
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ trait MemberLookup {
): EntityLink = {
val link =
lookup(Some(entity), packages, query)
.map(LinkToEntity)
.map(LinkToEntity.apply)
.getOrElse(Tooltip(query))

EntityLink(title, link)
Expand Down
8 changes: 4 additions & 4 deletions scala3doc/src/dotty/dokka/tasty/ScalaDocSupport.scala
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,10 @@ trait ScaladocSupport { self: TastyParser =>
addOpt(parsed.version)(dkkd.Version(_))
addOpt(parsed.since)(dkkd.Since(_))
addOpt(parsed.deprecated)(dkkd.Deprecated(_))
addSeq(parsed.todo)(ScalaTagWrapper.Todo)
addSeq(parsed.see)(ScalaTagWrapper.See)
addSeq(parsed.note)(ScalaTagWrapper.Note)
addSeq(parsed.example)(ScalaTagWrapper.Example)
addSeq(parsed.todo)(ScalaTagWrapper.Todo.apply)
addSeq(parsed.see)(ScalaTagWrapper.See.apply)
addSeq(parsed.note)(ScalaTagWrapper.Note.apply)
addSeq(parsed.example)(ScalaTagWrapper.Example.apply)

addOpt(parsed.constructor)(dkkd.Constructor(_))
addSeq(parsed.valueParams){ case (name, tag) =>
Expand Down
Empty file.
4 changes: 4 additions & 0 deletions tests/neg-custom-args/fatal-warnings/i6190b.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-- Error: tests/neg-custom-args/fatal-warnings/i6190b.scala:3:29 -------------------------------------------------------
3 |def foo = List("1", "2").map(Rule) // error
| ^^^^
| The method `apply` is inserted. The auto insertion will be deprecated, please write `Rule.apply` explicitly.
3 changes: 3 additions & 0 deletions tests/neg-custom-args/fatal-warnings/i6190b.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
case class Rule(name: String)

def foo = List("1", "2").map(Rule) // error
2 changes: 1 addition & 1 deletion tests/neg/enums.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ enum E4 {
case C4(x: Int)
}
object E4 {
val x1: Int => E4 = C4 // error: found: C4, required: Int => E4
val x1: Int => E4 = C4 // ok
val x2: Int => E4 = C4(_) // ok
}

Expand Down
6 changes: 6 additions & 0 deletions tests/neg/i6190.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class Rule(name: String)
object Rule {
def apply(name: String): Rule = new Rule(name)
}

def foo = List("1", "2").map(Rule) // error
2 changes: 1 addition & 1 deletion tests/pos/t3137.scala → tests/neg/t3137.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@ trait AA {
}

class BB extends AA {
case class C(v: Int)
case class C(v: Int) // error
}
6 changes: 6 additions & 0 deletions tests/pos-special/fatal-warnings/i6190a.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
case class Rule(name: String)
object Rule extends (String => Rule) {
def apply(name: String): Rule = new Rule(name)
}

def foo = List("1", "2").map(Rule)
3 changes: 3 additions & 0 deletions tests/pos-special/fatal-warnings/i6190c.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
case class Rule(name: String)

def foo = List("1", "2").map(Rule.apply)
2 changes: 1 addition & 1 deletion tests/run-macros/tasty-extractors-2.check
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ TypeRef(ThisType(TypeRef(NoPrefix(), "scala")), "Unit")
Inlined(None, Nil, Block(List(ClassDef("Foo", DefDef("<init>", Nil, List(Nil), Inferred(), None), List(Apply(Select(New(Inferred()), "<init>"), Nil)), Nil, None, List(DefDef("a", Nil, Nil, Inferred(), Some(Literal(Constant.Int(0))))))), Literal(Constant.Unit())))
TypeRef(ThisType(TypeRef(NoPrefix(), "scala")), "Unit")

Inlined(None, Nil, Block(List(ClassDef("Foo", DefDef("<init>", Nil, List(Nil), Inferred(), None), List(Apply(Select(New(Inferred()), "<init>"), Nil), TypeSelect(Select(Ident("_root_"), "scala"), "Product"), TypeSelect(Select(Ident("_root_"), "scala"), "Serializable")), Nil, None, List(DefDef("copy", Nil, List(Nil), Inferred(), Some(Apply(Select(New(Inferred()), "<init>"), Nil))))), ValDef("Foo", TypeIdent("Foo$"), Some(Apply(Select(New(TypeIdent("Foo$")), "<init>"), Nil))), ClassDef("Foo$", DefDef("<init>", Nil, List(Nil), Inferred(), None), List(Apply(Select(New(Inferred()), "<init>"), Nil), Applied(Inferred(), List(Inferred()))), Nil, Some(ValDef("_", Singleton(Ident("Foo")), None)), List(DefDef("apply", Nil, List(Nil), Inferred(), Some(Apply(Select(New(Inferred()), "<init>"), Nil))), DefDef("unapply", Nil, List(List(ValDef("x$1", Inferred(), None))), Singleton(Literal(Constant.Boolean(true))), Some(Literal(Constant.Boolean(true)))), DefDef("toString", Nil, Nil, Inferred(), Some(Literal(Constant.String("Foo"))))))), Literal(Constant.Unit())))
Inlined(None, Nil, Block(List(ClassDef("Foo", DefDef("<init>", Nil, List(Nil), Inferred(), None), List(Apply(Select(New(Inferred()), "<init>"), Nil), TypeSelect(Select(Ident("_root_"), "scala"), "Product"), TypeSelect(Select(Ident("_root_"), "scala"), "Serializable")), Nil, None, List(DefDef("copy", Nil, List(Nil), Inferred(), Some(Apply(Select(New(Inferred()), "<init>"), Nil))))), ValDef("Foo", TypeIdent("Foo$"), Some(Apply(Select(New(TypeIdent("Foo$")), "<init>"), Nil))), ClassDef("Foo$", DefDef("<init>", Nil, List(Nil), Inferred(), None), List(Apply(Select(New(Inferred()), "<init>"), Nil)), Nil, Some(ValDef("_", Singleton(Ident("Foo")), None)), List(DefDef("apply", Nil, List(Nil), Inferred(), Some(Apply(Select(New(Inferred()), "<init>"), Nil))), DefDef("unapply", Nil, List(List(ValDef("x$1", Inferred(), None))), Singleton(Literal(Constant.Boolean(true))), Some(Literal(Constant.Boolean(true)))), DefDef("toString", Nil, Nil, Inferred(), Some(Literal(Constant.String("Foo"))))))), Literal(Constant.Unit())))
TypeRef(ThisType(TypeRef(NoPrefix(), "scala")), "Unit")

Inlined(None, Nil, Block(List(ClassDef("Foo1", DefDef("<init>", Nil, List(List(ValDef("a", TypeIdent("Int"), None))), Inferred(), None), List(Apply(Select(New(Inferred()), "<init>"), Nil)), Nil, None, List(ValDef("a", Inferred(), None)))), Literal(Constant.Unit())))
Expand Down