Skip to content

Commit 8fcce8f

Browse files
authored
Merge pull request #11769 from dotty-staging/fix-11761
Disallow synthesized lambdas in statement position
2 parents a4fd829 + bc29318 commit 8fcce8f

File tree

12 files changed

+60
-29
lines changed

12 files changed

+60
-29
lines changed

compiler/src/dotty/tools/dotc/Compiler.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ class Compiler {
8080
new ExplicitOuter, // Add accessors to outer classes from nested ones.
8181
new ExplicitSelf, // Make references to non-trivial self types explicit as casts
8282
new ElimByName, // Expand by-name parameter references
83-
new StringInterpolatorOpt) :: // Optimizes raw and s string interpolators by rewriting them to string concatentations
83+
new StringInterpolatorOpt) :: // Optimizes raw and s string interpolators by rewriting them to string concatenations
8484
List(new PruneErasedDefs, // Drop erased definitions from scopes and simplify erased expressions
8585
new InlinePatterns, // Remove placeholders of inlined patterns
8686
new VCInlineMethods, // Inlines calls to value class methods

compiler/src/dotty/tools/dotc/ast/Desugar.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1306,9 +1306,10 @@ object desugar {
13061306
* def $anonfun(params) = body
13071307
* Closure($anonfun)
13081308
*/
1309-
def makeClosure(params: List[ValDef], body: Tree, tpt: Tree = null, isContextual: Boolean)(using Context): Block =
1309+
def makeClosure(params: List[ValDef], body: Tree, tpt: Tree = null, isContextual: Boolean, span: Span)(using Context): Block =
13101310
Block(
13111311
DefDef(nme.ANON_FUN, params :: Nil, if (tpt == null) TypeTree() else tpt, body)
1312+
.withSpan(span)
13121313
.withMods(synthetic | Artifact),
13131314
Closure(Nil, Ident(nme.ANON_FUN), if (isContextual) ContextualEmptyTree else EmptyTree))
13141315

compiler/src/dotty/tools/dotc/ast/TreeInfo.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,11 @@ trait UntypedTreeInfo extends TreeInfo[Untyped] { self: Trees.Instance[Untyped]
329329
def isFunctionWithUnknownParamType(tree: Tree): Boolean =
330330
functionWithUnknownParamType(tree).isDefined
331331

332+
def isFunction(tree: Tree): Boolean = tree match
333+
case Function(_, _) | Match(EmptyTree, _) => true
334+
case Block(Nil, expr) => isFunction(expr)
335+
case _ => false
336+
332337
/** Is `tree` an context function or closure, possibly nested in a block? */
333338
def isContextualClosure(tree: Tree)(using Context): Boolean = unsplice(tree) match {
334339
case tree: FunctionWithMods => tree.mods.is(Given)

compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,14 @@ object ErrorReporting {
4343
def wrongNumberOfTypeArgs(fntpe: Type, expectedArgs: List[ParamInfo], actual: List[untpd.Tree], pos: SrcPos)(using Context): ErrorType =
4444
errorType(WrongNumberOfTypeArgs(fntpe, expectedArgs, actual), pos)
4545

46+
def missingArgs(tree: Tree, mt: Type)(using Context): Unit =
47+
val meth = err.exprStr(methPart(tree))
48+
mt match
49+
case mt: MethodType if mt.paramNames.isEmpty =>
50+
report.error(MissingEmptyArgumentList(meth), tree.srcPos)
51+
case _ =>
52+
report.error(em"missing arguments for $meth", tree.srcPos)
53+
4654
class Errors(using Context) {
4755

4856
/** An explanatory note to be added to error messages

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1352,7 +1352,7 @@ class Typer extends Namer
13521352
else cpy.ValDef(param)(
13531353
tpt = untpd.TypeTree(
13541354
inferredParamType(param, protoFormal(i)).translateFromRepeated(toArray = false)))
1355-
desugar.makeClosure(inferredParams, fnBody, resultTpt, isContextual)
1355+
desugar.makeClosure(inferredParams, fnBody, resultTpt, isContextual, tree.span)
13561356
}
13571357
typed(desugared, pt)
13581358
}
@@ -3110,12 +3110,9 @@ class Typer extends Namer
31103110
def readapt(tree: Tree, shouldTryGadtHealing: Boolean = tryGadtHealing)(using Context) = adapt(tree, pt, locked, shouldTryGadtHealing)
31113111
def readaptSimplified(tree: Tree)(using Context) = readapt(simplify(tree, pt, locked))
31123112

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

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

38473856
/** Types the body Scala 2 macro declaration `def f = macro <body>` */
38483857
private def typedScala2MacroBody(call: untpd.Tree)(using Context): Tree =

tests/neg-custom-args/erased/erased-pathdep-1.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
object Test {
44

55
fun1(new Bar)
6-
fun2(new Bar)
7-
fun3(new Bar)
6+
val _ = fun2(new Bar)
7+
val _ = fun3(new Bar)
88

99
def fun1[F >: Bar <: Foo](erased f: F): f.X = null.asInstanceOf[f.X] // error // error
1010
def fun2[F >: Bar <: Foo](erased f: F)(erased bar: f.B): f.B = null.asInstanceOf[f.B] // error // error // error

tests/neg/i11761.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
def go(x: Int): Unit =
2+
go // error
3+
go // error
4+
go // error
5+
6+
def foo: Unit =
7+
(x: Int) => go(x) // warning
8+

tests/neg/i5311.check

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

77
longer explanation available when compiling with `-explain`

tests/neg/i7359-g.check

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
-- [E007] Type Mismatch Error: tests/neg/i7359-g.scala:5:25 ------------------------------------------------------------
1+
-- [E007] Type Mismatch Error: tests/neg/i7359-g.scala:5:19 ------------------------------------------------------------
22
5 |val m : SAMTrait = () => "Hello" // error
3-
| ^^^^^^^
4-
| Found: () => String
5-
| Required: SAMTrait
3+
| ^^^^^^^^^^^^^
4+
| Found: () => String
5+
| Required: SAMTrait
66

77
longer explanation available when compiling with `-explain`

tests/pos/i3873.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
object Test {
22
inline def sum2(ys: List[Int]): Unit = {
3-
ys.foldLeft(1)
3+
val _ = ys.foldLeft(1)
44
}
55
val h1 = (xs: List[Int]) => sum2(xs)
66
}

tests/pos/typedapply.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@ object typedapply {
66

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

9-
foo[Int, String] _
9+
val x = foo[Int, String] _
1010

1111
}

0 commit comments

Comments
 (0)