Skip to content

Disallow synthesized lambdas in statement position #11769

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 7 commits into from
Mar 17, 2021
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
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/Compiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ class Compiler {
new ExplicitOuter, // Add accessors to outer classes from nested ones.
new ExplicitSelf, // Make references to non-trivial self types explicit as casts
new ElimByName, // Expand by-name parameter references
new StringInterpolatorOpt) :: // Optimizes raw and s string interpolators by rewriting them to string concatentations
new StringInterpolatorOpt) :: // Optimizes raw and s string interpolators by rewriting them to string concatenations
List(new PruneErasedDefs, // Drop erased definitions from scopes and simplify erased expressions
new InlinePatterns, // Remove placeholders of inlined patterns
new VCInlineMethods, // Inlines calls to value class methods
Expand Down
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1306,9 +1306,10 @@ object desugar {
* def $anonfun(params) = body
* Closure($anonfun)
*/
def makeClosure(params: List[ValDef], body: Tree, tpt: Tree = null, isContextual: Boolean)(using Context): Block =
def makeClosure(params: List[ValDef], body: Tree, tpt: Tree = null, isContextual: Boolean, span: Span)(using Context): Block =
Block(
DefDef(nme.ANON_FUN, params :: Nil, if (tpt == null) TypeTree() else tpt, body)
.withSpan(span)
.withMods(synthetic | Artifact),
Closure(Nil, Ident(nme.ANON_FUN), if (isContextual) ContextualEmptyTree else EmptyTree))

Expand Down
5 changes: 5 additions & 0 deletions compiler/src/dotty/tools/dotc/ast/TreeInfo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,11 @@ trait UntypedTreeInfo extends TreeInfo[Untyped] { self: Trees.Instance[Untyped]
def isFunctionWithUnknownParamType(tree: Tree): Boolean =
functionWithUnknownParamType(tree).isDefined

def isFunction(tree: Tree): Boolean = tree match
case Function(_, _) | Match(EmptyTree, _) => true
case Block(Nil, expr) => isFunction(expr)
case _ => false

/** Is `tree` an context function or closure, possibly nested in a block? */
def isContextualClosure(tree: Tree)(using Context): Boolean = unsplice(tree) match {
case tree: FunctionWithMods => tree.mods.is(Given)
Expand Down
8 changes: 8 additions & 0 deletions compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@ object ErrorReporting {
def wrongNumberOfTypeArgs(fntpe: Type, expectedArgs: List[ParamInfo], actual: List[untpd.Tree], pos: SrcPos)(using Context): ErrorType =
errorType(WrongNumberOfTypeArgs(fntpe, expectedArgs, actual), pos)

def missingArgs(tree: Tree, mt: Type)(using Context): Unit =
val meth = err.exprStr(methPart(tree))
mt match
case mt: MethodType if mt.paramNames.isEmpty =>
report.error(MissingEmptyArgumentList(meth), tree.srcPos)
case _ =>
report.error(em"missing arguments for $meth", tree.srcPos)

class Errors(using Context) {

/** An explanatory note to be added to error messages
Expand Down
37 changes: 23 additions & 14 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1356,7 +1356,7 @@ class Typer extends Namer
else cpy.ValDef(param)(
tpt = untpd.TypeTree(
inferredParamType(param, protoFormal(i)).translateFromRepeated(toArray = false)))
desugar.makeClosure(inferredParams, fnBody, resultTpt, isContextual)
desugar.makeClosure(inferredParams, fnBody, resultTpt, isContextual, tree.span)
}
typed(desugared, pt)
}
Expand Down Expand Up @@ -3114,12 +3114,9 @@ class Typer extends Namer
def readapt(tree: Tree, shouldTryGadtHealing: Boolean = tryGadtHealing)(using Context) = adapt(tree, pt, locked, shouldTryGadtHealing)
def readaptSimplified(tree: Tree)(using Context) = readapt(simplify(tree, pt, locked))

def missingArgs(mt: MethodType) = {
val meth = err.exprStr(methPart(tree))
if (mt.paramNames.length == 0) report.error(MissingEmptyArgumentList(meth), tree.srcPos)
else report.error(em"missing arguments for $meth", tree.srcPos)
def missingArgs(mt: MethodType) =
ErrorReporting.missingArgs(tree, mt)
tree.withType(mt.resultType)
}

def adaptOverloaded(ref: TermRef) = {
val altDenots =
Expand Down Expand Up @@ -3413,19 +3410,19 @@ class Typer extends Namer
// - we reference a typelevel method
// - we are in a pattern
// - the current tree is a synthetic apply which is not expandable (eta-expasion would simply undo that)
if (arity >= 0 &&
!tree.symbol.isConstructor &&
!tree.symbol.isAllOf(InlineMethod) &&
!ctx.mode.is(Mode.Pattern) &&
!(isSyntheticApply(tree) && !functionExpected)) {
if arity >= 0
&& !tree.symbol.isConstructor
&& !tree.symbol.isAllOf(InlineMethod)
&& !ctx.mode.is(Mode.Pattern)
&& !(isSyntheticApply(tree) && !functionExpected)
then
if (!defn.isFunctionType(pt))
pt match {
case SAMType(_) if !pt.classSymbol.hasAnnotation(defn.FunctionalInterfaceAnnot) =>
report.warning(ex"${tree.symbol} is eta-expanded even though $pt does not have the @FunctionalInterface annotation.", tree.srcPos)
case _ =>
}
simplify(typed(etaExpand(tree, wtp, arity), pt), pt, locked)
}
else if (wtp.paramInfos.isEmpty && isAutoApplied(tree.symbol))
readaptSimplified(tpd.Apply(tree, Nil))
else if (wtp.isImplicitMethod)
Expand Down Expand Up @@ -3832,8 +3829,20 @@ class Typer extends Namer
&& !tree.isInstanceOf[Inlined]
&& isPureExpr(tree)
&& !isSelfOrSuperConstrCall(tree)
then
report.warning(PureExpressionInStatementPosition(original, exprOwner), original.srcPos)
then tree match
case closureDef(meth)
if meth.span == meth.rhs.span.toSynthetic && !untpd.isFunction(original) =>
// It's a synthesized lambda, for instance via an eta expansion: report a hard error
// There are two tests for synthetic lambdas which both have to be true.
// The first test compares spans of closure definition with the closure's right hand
// side. This is usually accurate but can fail for compiler-generated test code.
// See repl.DocTests for two failing tests. The second tests rules out closures
// if the original tree was a lambda. This does not work always either since
// sometimes we do not have the original anymore and use the transformed tree instead.
// But taken together, the two criteria are quite accurate.
missingArgs(tree, tree.tpe.widen)
case _ =>
report.warning(PureExpressionInStatementPosition(original, exprOwner), original.srcPos)

/** Types the body Scala 2 macro declaration `def f = macro <body>` */
private def typedScala2MacroBody(call: untpd.Tree)(using Context): Tree =
Expand Down
4 changes: 2 additions & 2 deletions tests/neg-custom-args/erased/erased-pathdep-1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
object Test {

fun1(new Bar)
fun2(new Bar)
fun3(new Bar)
val _ = fun2(new Bar)
val _ = fun3(new Bar)

def fun1[F >: Bar <: Foo](erased f: F): f.X = null.asInstanceOf[f.X] // error // error
def fun2[F >: Bar <: Foo](erased f: F)(erased bar: f.B): f.B = null.asInstanceOf[f.B] // error // error // error
Expand Down
8 changes: 8 additions & 0 deletions tests/neg/i11761.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
def go(x: Int): Unit =
go // error
go // error
go // error

def foo: Unit =
(x: Int) => go(x) // warning

8 changes: 4 additions & 4 deletions tests/neg/i5311.check
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
-- [E007] Type Mismatch Error: tests/neg/i5311.scala:11:9 --------------------------------------------------------------
-- [E007] Type Mismatch Error: tests/neg/i5311.scala:11:8 --------------------------------------------------------------
11 | baz((x : s.T[Int]) => x) // error
| ^^^^^^^^^^^^^^^^^^
| Found: s.T[Int] => s.T[Int]
| Required: m.Foo
| ^^^^^^^^^^^^^^^^^^^
| Found: s.T[Int] => s.T[Int]
| Required: m.Foo

longer explanation available when compiling with `-explain`
8 changes: 4 additions & 4 deletions tests/neg/i7359-g.check
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
-- [E007] Type Mismatch Error: tests/neg/i7359-g.scala:5:25 ------------------------------------------------------------
-- [E007] Type Mismatch Error: tests/neg/i7359-g.scala:5:19 ------------------------------------------------------------
5 |val m : SAMTrait = () => "Hello" // error
| ^^^^^^^
| Found: () => String
| Required: SAMTrait
| ^^^^^^^^^^^^^
| Found: () => String
| Required: SAMTrait

longer explanation available when compiling with `-explain`
2 changes: 1 addition & 1 deletion tests/pos/i3873.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
object Test {
inline def sum2(ys: List[Int]): Unit = {
ys.foldLeft(1)
val _ = ys.foldLeft(1)
}
val h1 = (xs: List[Int]) => sum2(xs)
}
2 changes: 1 addition & 1 deletion tests/pos/typedapply.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ object typedapply {

foo[Int, String](1, "abc")

foo[Int, String] _
val x = foo[Int, String] _

}