Skip to content

Commit 82ff24f

Browse files
committed
Check user defined PolyFunction refinements
`PolyFunction` must be refined with an `apply` method that has a single parameter list with no by-name nor varargs parameters. It may optionally have type parameters. Some of these restrictions could be lifted later, but for now these features are not properly handled by the compiler. Fixes #8299 Fixes #18302 [Cherry-picked e5ca0c4][modified]
1 parent e056b2e commit 82ff24f

17 files changed

+119
-0
lines changed

compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1157,6 +1157,17 @@ class Definitions {
11571157
if tpe.refinedName == nme.apply && tpe.parent.derivesFrom(defn.PolyFunctionClass) =>
11581158
Some(mt)
11591159
case _ => None
1160+
1161+
def isValidPolyFunctionInfo(info: Type)(using Context): Boolean =
1162+
def isValidMethodType(info: Type) = info match
1163+
case info: MethodType =>
1164+
!info.resType.isInstanceOf[MethodOrPoly] && // Has only one parameter list
1165+
!info.isVarArgsMethod &&
1166+
!info.paramInfos.exists(_.isInstanceOf[ExprType]) // No by-name parameters
1167+
case _ => false
1168+
info match
1169+
case info: PolyType => isValidMethodType(info.resType)
1170+
case _ => isValidMethodType(info)
11601171
}
11611172

11621173
object ErasedFunctionOf {

compiler/src/dotty/tools/dotc/transform/PostTyper.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,13 +375,15 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
375375
case tree: ValDef =>
376376
registerIfHasMacroAnnotations(tree)
377377
checkErasedDef(tree)
378+
Checking.checkPolyFunctionType(tree.tpt)
378379
val tree1 = cpy.ValDef(tree)(rhs = normalizeErasedRhs(tree.rhs, tree.symbol))
379380
if tree1.removeAttachment(desugar.UntupledParam).isDefined then
380381
checkStableSelection(tree.rhs)
381382
processValOrDefDef(super.transform(tree1))
382383
case tree: DefDef =>
383384
registerIfHasMacroAnnotations(tree)
384385
checkErasedDef(tree)
386+
Checking.checkPolyFunctionType(tree.tpt)
385387
annotateContextResults(tree)
386388
val tree1 = cpy.DefDef(tree)(rhs = normalizeErasedRhs(tree.rhs, tree.symbol))
387389
processValOrDefDef(superAcc.wrapDefDef(tree1)(super.transform(tree1).asInstanceOf[DefDef]))
@@ -483,6 +485,9 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
483485
)
484486
case Block(_, Closure(_, _, tpt)) if ExpandSAMs.needsWrapperClass(tpt.tpe) =>
485487
superAcc.withInvalidCurrentClass(super.transform(tree))
488+
case tree: RefinedTypeTree =>
489+
Checking.checkPolyFunctionType(tree)
490+
super.transform(tree)
486491
case _: Quote =>
487492
ctx.compilationUnit.needsStaging = true
488493
super.transform(tree)

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -804,6 +804,31 @@ object Checking {
804804
else Feature.checkExperimentalFeature("features", imp.srcPos)
805805
case _ =>
806806
end checkExperimentalImports
807+
808+
/** Checks that PolyFunction only have valid refinements.
809+
*
810+
* It only supports `apply` methods with one parameter list and optional type arguments.
811+
*/
812+
def checkPolyFunctionType(tree: Tree)(using Context): Unit = new TreeTraverser {
813+
def traverse(tree: Tree)(using Context): Unit = tree match
814+
case tree: RefinedTypeTree if tree.tpe.derivesFrom(defn.PolyFunctionClass) =>
815+
if tree.refinements.isEmpty then
816+
reportNoRefinements(tree.srcPos)
817+
tree.refinements.foreach {
818+
case refinement: DefDef if refinement.name != nme.apply =>
819+
report.error("PolyFunction only supports apply method refinements", refinement.srcPos)
820+
case refinement: DefDef if !defn.PolyFunctionOf.isValidPolyFunctionInfo(refinement.tpe.widen) =>
821+
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)
822+
case _ =>
823+
}
824+
case _: RefTree if tree.symbol == defn.PolyFunctionClass =>
825+
reportNoRefinements(tree.srcPos)
826+
case _ =>
827+
traverseChildren(tree)
828+
829+
def reportNoRefinements(pos: SrcPos) =
830+
report.error("PolyFunction subtypes must refine the apply method", pos)
831+
}.traverse(tree)
807832
}
808833

809834
trait Checking {

tests/neg/i18302b.check

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
-- Error: tests/neg/i18302b.scala:3:32 ---------------------------------------------------------------------------------
2+
3 |def polyFun: PolyFunction { def apply(x: Int)(y: Int): Int } = // error
3+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
4+
|Implementation restriction: PolyFunction apply must have exactly one parameter list and optionally type arguments. No by-name nor varags are allowed.

tests/neg/i18302b.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
def test = polyFun(1)(2)
2+
3+
def polyFun: PolyFunction { def apply(x: Int)(y: Int): Int } = // error
4+
new PolyFunction:
5+
def apply(x: Int)(y: Int): Int = x + y

tests/neg/i18302c.check

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
-- Error: tests/neg/i18302c.scala:4:32 ---------------------------------------------------------------------------------
2+
4 |def polyFun: PolyFunction { def foo(x: Int): Int } = // error
3+
| ^^^^^^^^^^^^^^^^^^^^
4+
| PolyFunction only supports apply method refinements

tests/neg/i18302c.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import scala.reflect.Selectable.reflectiveSelectable
2+
3+
def test = polyFun.foo(1)
4+
def polyFun: PolyFunction { def foo(x: Int): Int } = // error
5+
new PolyFunction { def foo(x: Int): Int = x + 1 }

tests/neg/i18302d.check

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
-- Error: tests/neg/i18302d.scala:1:32 ---------------------------------------------------------------------------------
2+
1 |def polyFun: PolyFunction { def apply: Int } = // error
3+
| ^^^^^^^^^^^^^^
4+
|Implementation restriction: PolyFunction apply must have exactly one parameter list and optionally type arguments. No by-name nor varags are allowed.

tests/neg/i18302d.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
def polyFun: PolyFunction { def apply: Int } = // error
2+
new PolyFunction { def apply: Int = 1 }

tests/neg/i18302e.check

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
-- Error: tests/neg/i18302e.scala:1:13 ---------------------------------------------------------------------------------
2+
1 |def polyFun: PolyFunction { } = // error
3+
| ^^^^^^^^^^^^^^^^^
4+
| PolyFunction subtypes must refine the apply method
5+
-- Error: tests/neg/i18302e.scala:4:15 ---------------------------------------------------------------------------------
6+
4 |def polyFun(f: PolyFunction { }) = () // error
7+
| ^^^^^^^^^^^^^^^^^
8+
| PolyFunction subtypes must refine the apply method

tests/neg/i18302e.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
def polyFun: PolyFunction { } = // error
2+
new PolyFunction { }
3+
4+
def polyFun(f: PolyFunction { }) = () // error

tests/neg/i18302f.check

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
-- Error: tests/neg/i18302f.scala:1:13 ---------------------------------------------------------------------------------
2+
1 |def polyFun: PolyFunction = // error
3+
| ^^^^^^^^^^^^
4+
| PolyFunction subtypes must refine the apply method
5+
-- Error: tests/neg/i18302f.scala:4:16 ---------------------------------------------------------------------------------
6+
4 |def polyFun2(a: PolyFunction) = () // error
7+
| ^^^^^^^^^^^^
8+
| PolyFunction subtypes must refine the apply method
9+
-- Error: tests/neg/i18302f.scala:6:14 ---------------------------------------------------------------------------------
10+
6 |val polyFun3: PolyFunction = // error
11+
| ^^^^^^^^^^^^
12+
| PolyFunction subtypes must refine the apply method

tests/neg/i18302f.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
def polyFun: PolyFunction = // error
2+
new PolyFunction { }
3+
4+
def polyFun2(a: PolyFunction) = () // error
5+
6+
val polyFun3: PolyFunction = // error
7+
new PolyFunction { }

tests/neg/i18302i.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
def polyFun1: Option[PolyFunction] = ??? // error
2+
def polyFun2: PolyFunction & Any = ??? // error
3+
def polyFun3: Any & PolyFunction = ??? // error
4+
def polyFun4: PolyFunction | Any = ??? // error
5+
def polyFun5: Any | PolyFunction = ??? // error
6+
def polyFun6(a: Any | PolyFunction) = ??? // error

tests/neg/i18302j.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
def polyFunByName: PolyFunction { def apply(thunk: => Int): Int } = // error
2+
new PolyFunction { def apply(thunk: => Int): Int = 1 }
3+
4+
def polyFunVarArgs: PolyFunction { def apply(args: Int*): Int } = // error
5+
new PolyFunction { def apply(thunk: Int*): Int = 1 }

tests/neg/i8299.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package example
2+
3+
object Main {
4+
def main(a: Array[String]): Unit = {
5+
val p: PolyFunction = // error: PolyFunction subtypes must refine the apply method
6+
[A] => (xs: List[A]) => xs.headOption
7+
}
8+
}

tests/pos/i18302a.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
def test = polyFun(1)
2+
3+
def polyFun: PolyFunction { def apply(x: Int): Int } =
4+
new PolyFunction { def apply(x: Int): Int = x + 1 }

0 commit comments

Comments
 (0)