From 4ed789309c73e3ad0772346f5be0209a201625e2 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 14 Nov 2023 13:31:17 +0100 Subject: [PATCH] Disallow use of `PolyFunction` in user code The `PolyFunction` trait should only be used for compiler generated encoded lambdas. Any other use case that was allowed before was accidental. In the future, we might consider supporting these if there is a good use case. This would probably require a SIP. --- .../tools/dotc/transform/PostTyper.scala | 8 +--- .../src/dotty/tools/dotc/typer/Checking.scala | 35 ++------------ tests/neg/i10075.check | 48 ++++++++++--------- tests/neg/i10075.scala | 3 +- tests/neg/i18302a.scala | 2 +- tests/neg/i18302b.check | 8 ++-- tests/neg/i18302c.check | 8 ++-- tests/neg/i18302d.check | 8 ++-- tests/neg/i18302e.check | 10 ++-- tests/neg/i18302f.check | 10 ++-- 10 files changed, 57 insertions(+), 83 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 7aa770a25bb2..79ac75432da9 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -287,6 +287,7 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => super.transform(tree)(using gadtCtx) case tree: Ident => if tree.isType then + Checking.checkPolyFunctionType(tree) checkNotPackage(tree) else checkNoConstructorProxy(tree) @@ -299,6 +300,7 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => registerNeedsInlining(tree) if name.isTypeName then Checking.checkRealizable(qual.tpe, qual.srcPos) + Checking.checkPolyFunctionType(tree) withMode(Mode.Type)(super.transform(checkNotPackage(tree))) else checkNoConstructorProxy(tree) @@ -370,7 +372,6 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => val callTrace = ref(call.symbol)(using ctx.withSource(pos.source)).withSpan(pos.span) cpy.Inlined(tree)(callTrace, transformSub(bindings), transform(expansion)(using inlineContext(tree))) case templ: Template => - Checking.checkPolyFunctionExtension(templ) withNoCheckNews(templ.parents.flatMap(newPart)) { forwardParamAccessors(templ) synthMbr.addSyntheticMembers( @@ -382,7 +383,6 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => case tree: ValDef => registerIfHasMacroAnnotations(tree) checkErasedDef(tree) - Checking.checkPolyFunctionType(tree.tpt) val tree1 = cpy.ValDef(tree)(rhs = normalizeErasedRhs(tree.rhs, tree.symbol)) if tree1.removeAttachment(desugar.UntupledParam).isDefined then checkStableSelection(tree.rhs) @@ -390,7 +390,6 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => case tree: DefDef => registerIfHasMacroAnnotations(tree) checkErasedDef(tree) - Checking.checkPolyFunctionType(tree.tpt) annotateContextResults(tree) val tree1 = cpy.DefDef(tree)(rhs = normalizeErasedRhs(tree.rhs, tree.symbol)) processValOrDefDef(superAcc.wrapDefDef(tree1)(super.transform(tree1).asInstanceOf[DefDef])) @@ -495,9 +494,6 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => ) case Block(_, Closure(_, _, tpt)) if ExpandSAMs.needsWrapperClass(tpt.tpe) => superAcc.withInvalidCurrentClass(super.transform(tree)) - case tree: RefinedTypeTree => - Checking.checkPolyFunctionType(tree) - super.transform(tree) case _: Quote | _: QuotePattern => ctx.compilationUnit.needsStaging = true super.transform(tree) diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index e7d25c797c34..ac71fabe36fa 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -825,38 +825,11 @@ object Checking { case _ => end checkExperimentalImports - /** Checks that PolyFunction only have valid refinements. - * - * It only supports `apply` methods with one parameter list and optional type arguments. - */ - def checkPolyFunctionType(tree: Tree)(using Context): Unit = new TreeTraverser { - def traverse(tree: Tree)(using Context): Unit = tree match - case tree: RefinedTypeTree if tree.tpe.derivesFrom(defn.PolyFunctionClass) => - if tree.refinements.isEmpty then - reportNoRefinements(tree.srcPos) - tree.refinements.foreach { - case refinement: DefDef if refinement.name != nme.apply => - report.error("PolyFunction only supports apply method refinements", refinement.srcPos) - case refinement: DefDef if !defn.PolyFunctionOf.isValidPolyFunctionInfo(refinement.tpe.widen) => - report.error("Implementation restriction: PolyFunction apply must have exactly one parameter list and optionally type arguments. No by-name nor varags are allowed.", refinement.srcPos) - case _ => - } - case _: RefTree if tree.symbol == defn.PolyFunctionClass => - reportNoRefinements(tree.srcPos) - case _ => - traverseChildren(tree) - - def reportNoRefinements(pos: SrcPos) = - report.error("PolyFunction subtypes must refine the apply method", pos) - }.traverse(tree) + /** Checks that PolyFunction is not used directly */ + def checkPolyFunctionType(tree: RefTree)(using Context): Unit = + if tree.symbol == defn.PolyFunctionClass then + report.error(s"Direct use of `PolyFunction` is not allowed. Use lambda instead.", tree.srcPos) - /** Check that users do not extend the `PolyFunction` trait. - * We only allow compiler generated `PolyFunction`s. - */ - def checkPolyFunctionExtension(templ: Template)(using Context): Unit = - templ.parents.find(_.tpe.derivesFrom(defn.PolyFunctionClass)) match - case Some(parent) => report.error(s"`PolyFunction` marker trait is reserved for compiler generated refinements", parent.srcPos) - case None => } trait Checking { diff --git a/tests/neg/i10075.check b/tests/neg/i10075.check index 6f3e9ab4334a..bffcb9c155f3 100644 --- a/tests/neg/i10075.check +++ b/tests/neg/i10075.check @@ -1,32 +1,36 @@ --- Error: tests/neg/i10075.scala:8:24 ---------------------------------------------------------------------------------- -8 |trait PolyTrait extends PolyFunction // error +-- Error: tests/neg/i10075.scala:9:24 ---------------------------------------------------------------------------------- +9 |trait PolyTrait extends PolyFunction // error | ^^^^^^^^^^^^ - | `PolyFunction` marker trait is reserved for compiler generated refinements --- Error: tests/neg/i10075.scala:10:24 --------------------------------------------------------------------------------- -10 |class PolyClass extends PolyTrait { // error - | ^^^^^^^^^ - | `PolyFunction` marker trait is reserved for compiler generated refinements --- Error: tests/neg/i10075.scala:14:26 --------------------------------------------------------------------------------- -14 |object PolyObject extends PolyFunction // error + | Direct use of `PolyFunction` is not allowed. Use lambda instead. +-- Error: tests/neg/i10075.scala:11:24 --------------------------------------------------------------------------------- +11 |class PolyClass extends PolyFunction { // error + | ^^^^^^^^^^^^ + | Direct use of `PolyFunction` is not allowed. Use lambda instead. +-- Error: tests/neg/i10075.scala:15:26 --------------------------------------------------------------------------------- +15 |object PolyObject extends PolyFunction // error | ^^^^^^^^^^^^ - | `PolyFunction` marker trait is reserved for compiler generated refinements + | Direct use of `PolyFunction` is not allowed. Use lambda instead. -- Error: tests/neg/i10075.scala:2:14 ---------------------------------------------------------------------------------- 2 |val foo = new PolyFunction { } // error | ^^^^^^^^^^^^ - | `PolyFunction` marker trait is reserved for compiler generated refinements --- Error: tests/neg/i10075.scala:3:14 ---------------------------------------------------------------------------------- -3 |val bar = new PolyFunction { def bar = 23 } // error - | ^^^^^^^^^^^^ - | `PolyFunction` marker trait is reserved for compiler generated refinements + | Direct use of `PolyFunction` is not allowed. Use lambda instead. +-- Error: tests/neg/i10075.scala:3:21 ---------------------------------------------------------------------------------- +3 |val foo2 = new scala.PolyFunction { } // error + | ^^^^^^^^^^^^^^^^^^ + | Direct use of `PolyFunction` is not allowed. Use lambda instead. -- Error: tests/neg/i10075.scala:4:14 ---------------------------------------------------------------------------------- -4 |val baz = new PolyFunction { def apply = 23 } // error +4 |val bar = new PolyFunction { def bar = 23 } // error | ^^^^^^^^^^^^ - | `PolyFunction` marker trait is reserved for compiler generated refinements + | Direct use of `PolyFunction` is not allowed. Use lambda instead. -- Error: tests/neg/i10075.scala:5:14 ---------------------------------------------------------------------------------- -5 |val qux = new PolyFunction { def apply[T] = 47 } // error +5 |val baz = new PolyFunction { def apply = 23 } // error + | ^^^^^^^^^^^^ + | Direct use of `PolyFunction` is not allowed. Use lambda instead. +-- Error: tests/neg/i10075.scala:6:14 ---------------------------------------------------------------------------------- +6 |val qux = new PolyFunction { def apply[T] = 47 } // error | ^^^^^^^^^^^^ - | `PolyFunction` marker trait is reserved for compiler generated refinements --- Error: tests/neg/i10075.scala:6:15 ---------------------------------------------------------------------------------- -6 |val quxx = new PolyFunction { def apply[T](x: T): T = x } // error + | Direct use of `PolyFunction` is not allowed. Use lambda instead. +-- Error: tests/neg/i10075.scala:7:15 ---------------------------------------------------------------------------------- +7 |val quxx = new PolyFunction { def apply[T](x: T): T = x } // error | ^^^^^^^^^^^^ - | `PolyFunction` marker trait is reserved for compiler generated refinements + | Direct use of `PolyFunction` is not allowed. Use lambda instead. diff --git a/tests/neg/i10075.scala b/tests/neg/i10075.scala index e1a255ec8b54..073af0b55613 100644 --- a/tests/neg/i10075.scala +++ b/tests/neg/i10075.scala @@ -1,5 +1,6 @@ val poly = [T] => (x: T) => x val foo = new PolyFunction { } // error +val foo2 = new scala.PolyFunction { } // error val bar = new PolyFunction { def bar = 23 } // error val baz = new PolyFunction { def apply = 23 } // error val qux = new PolyFunction { def apply[T] = 47 } // error @@ -7,7 +8,7 @@ val quxx = new PolyFunction { def apply[T](x: T): T = x } // error trait PolyTrait extends PolyFunction // error -class PolyClass extends PolyTrait { // error +class PolyClass extends PolyFunction { // error def apply[T](x: T): T = x } diff --git a/tests/neg/i18302a.scala b/tests/neg/i18302a.scala index dc4bc703c404..76e40441e86e 100644 --- a/tests/neg/i18302a.scala +++ b/tests/neg/i18302a.scala @@ -1,4 +1,4 @@ def test = polyFun(1) -def polyFun: PolyFunction { def apply(x: Int): Int } = +def polyFun: PolyFunction { def apply(x: Int): Int } = // error new PolyFunction { def apply(x: Int): Int = x + 1 } // error diff --git a/tests/neg/i18302b.check b/tests/neg/i18302b.check index 624c0cc0e415..b55859b800cf 100644 --- a/tests/neg/i18302b.check +++ b/tests/neg/i18302b.check @@ -1,8 +1,8 @@ --- Error: tests/neg/i18302b.scala:3:32 --------------------------------------------------------------------------------- +-- Error: tests/neg/i18302b.scala:3:13 --------------------------------------------------------------------------------- 3 |def polyFun: PolyFunction { def apply(x: Int)(y: Int): Int } = // error - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - |Implementation restriction: PolyFunction apply must have exactly one parameter list and optionally type arguments. No by-name nor varags are allowed. + | ^^^^^^^^^^^^ + | Direct use of `PolyFunction` is not allowed. Use lambda instead. -- Error: tests/neg/i18302b.scala:4:6 ---------------------------------------------------------------------------------- 4 | new PolyFunction: // error | ^^^^^^^^^^^^ - | `PolyFunction` marker trait is reserved for compiler generated refinements + | Direct use of `PolyFunction` is not allowed. Use lambda instead. diff --git a/tests/neg/i18302c.check b/tests/neg/i18302c.check index 67dffcfae98c..1b8d7ceda9b6 100644 --- a/tests/neg/i18302c.check +++ b/tests/neg/i18302c.check @@ -1,8 +1,8 @@ --- Error: tests/neg/i18302c.scala:4:32 --------------------------------------------------------------------------------- +-- Error: tests/neg/i18302c.scala:4:13 --------------------------------------------------------------------------------- 4 |def polyFun: PolyFunction { def foo(x: Int): Int } = // error - | ^^^^^^^^^^^^^^^^^^^^ - | PolyFunction only supports apply method refinements + | ^^^^^^^^^^^^ + | Direct use of `PolyFunction` is not allowed. Use lambda instead. -- Error: tests/neg/i18302c.scala:5:6 ---------------------------------------------------------------------------------- 5 | new PolyFunction { def foo(x: Int): Int = x + 1 } // error | ^^^^^^^^^^^^ - | `PolyFunction` marker trait is reserved for compiler generated refinements + | Direct use of `PolyFunction` is not allowed. Use lambda instead. diff --git a/tests/neg/i18302d.check b/tests/neg/i18302d.check index f052735a4db2..e7c3c6845f28 100644 --- a/tests/neg/i18302d.check +++ b/tests/neg/i18302d.check @@ -1,8 +1,8 @@ --- Error: tests/neg/i18302d.scala:1:32 --------------------------------------------------------------------------------- +-- Error: tests/neg/i18302d.scala:1:13 --------------------------------------------------------------------------------- 1 |def polyFun: PolyFunction { def apply: Int } = // error - | ^^^^^^^^^^^^^^ - |Implementation restriction: PolyFunction apply must have exactly one parameter list and optionally type arguments. No by-name nor varags are allowed. + | ^^^^^^^^^^^^ + | Direct use of `PolyFunction` is not allowed. Use lambda instead. -- Error: tests/neg/i18302d.scala:2:6 ---------------------------------------------------------------------------------- 2 | new PolyFunction { def apply: Int = 1 } // error | ^^^^^^^^^^^^ - | `PolyFunction` marker trait is reserved for compiler generated refinements + | Direct use of `PolyFunction` is not allowed. Use lambda instead. diff --git a/tests/neg/i18302e.check b/tests/neg/i18302e.check index 7fbe19e8213d..1fc23d30aeb9 100644 --- a/tests/neg/i18302e.check +++ b/tests/neg/i18302e.check @@ -1,12 +1,12 @@ -- Error: tests/neg/i18302e.scala:1:13 --------------------------------------------------------------------------------- 1 |def polyFun: PolyFunction { } = // error - | ^^^^^^^^^^^^^^^^^ - | PolyFunction subtypes must refine the apply method + | ^^^^^^^^^^^^ + | Direct use of `PolyFunction` is not allowed. Use lambda instead. -- Error: tests/neg/i18302e.scala:2:6 ---------------------------------------------------------------------------------- 2 | new PolyFunction { } // error | ^^^^^^^^^^^^ - | `PolyFunction` marker trait is reserved for compiler generated refinements + | Direct use of `PolyFunction` is not allowed. Use lambda instead. -- Error: tests/neg/i18302e.scala:4:15 --------------------------------------------------------------------------------- 4 |def polyFun(f: PolyFunction { }) = () // error - | ^^^^^^^^^^^^^^^^^ - | PolyFunction subtypes must refine the apply method + | ^^^^^^^^^^^^ + | Direct use of `PolyFunction` is not allowed. Use lambda instead. diff --git a/tests/neg/i18302f.check b/tests/neg/i18302f.check index 5231e894fabb..0208d5afb342 100644 --- a/tests/neg/i18302f.check +++ b/tests/neg/i18302f.check @@ -1,20 +1,20 @@ -- Error: tests/neg/i18302f.scala:1:13 --------------------------------------------------------------------------------- 1 |def polyFun: PolyFunction = // error | ^^^^^^^^^^^^ - | PolyFunction subtypes must refine the apply method + | Direct use of `PolyFunction` is not allowed. Use lambda instead. -- Error: tests/neg/i18302f.scala:2:6 ---------------------------------------------------------------------------------- 2 | new PolyFunction { } // error | ^^^^^^^^^^^^ - | `PolyFunction` marker trait is reserved for compiler generated refinements + | Direct use of `PolyFunction` is not allowed. Use lambda instead. -- Error: tests/neg/i18302f.scala:4:16 --------------------------------------------------------------------------------- 4 |def polyFun2(a: PolyFunction) = () // error | ^^^^^^^^^^^^ - | PolyFunction subtypes must refine the apply method + | Direct use of `PolyFunction` is not allowed. Use lambda instead. -- Error: tests/neg/i18302f.scala:6:14 --------------------------------------------------------------------------------- 6 |val polyFun3: PolyFunction = // error | ^^^^^^^^^^^^ - | PolyFunction subtypes must refine the apply method + | Direct use of `PolyFunction` is not allowed. Use lambda instead. -- Error: tests/neg/i18302f.scala:7:6 ---------------------------------------------------------------------------------- 7 | new PolyFunction { } // error | ^^^^^^^^^^^^ - | `PolyFunction` marker trait is reserved for compiler generated refinements + | Direct use of `PolyFunction` is not allowed. Use lambda instead.