Skip to content

Commit

Permalink
Disallow use of PolyFunction in user code
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
nicolasstucki committed Nov 15, 2023
1 parent 1b4b390 commit 4ed7893
Show file tree
Hide file tree
Showing 10 changed files with 57 additions and 83 deletions.
8 changes: 2 additions & 6 deletions compiler/src/dotty/tools/dotc/transform/PostTyper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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(
Expand All @@ -382,15 +383,13 @@ 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)
processValOrDefDef(super.transform(tree1))
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]))
Expand Down Expand Up @@ -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)
Expand Down
35 changes: 4 additions & 31 deletions compiler/src/dotty/tools/dotc/typer/Checking.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
48 changes: 26 additions & 22 deletions tests/neg/i10075.check
Original file line number Diff line number Diff line change
@@ -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.
3 changes: 2 additions & 1 deletion tests/neg/i10075.scala
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
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
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
}

Expand Down
2 changes: 1 addition & 1 deletion tests/neg/i18302a.scala
Original file line number Diff line number Diff line change
@@ -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
8 changes: 4 additions & 4 deletions tests/neg/i18302b.check
Original file line number Diff line number Diff line change
@@ -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.
8 changes: 4 additions & 4 deletions tests/neg/i18302c.check
Original file line number Diff line number Diff line change
@@ -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.
8 changes: 4 additions & 4 deletions tests/neg/i18302d.check
Original file line number Diff line number Diff line change
@@ -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.
10 changes: 5 additions & 5 deletions tests/neg/i18302e.check
Original file line number Diff line number Diff line change
@@ -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.
10 changes: 5 additions & 5 deletions tests/neg/i18302f.check
Original file line number Diff line number Diff line change
@@ -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.

0 comments on commit 4ed7893

Please sign in to comment.