Skip to content

Commit 3b94fb2

Browse files
Backport "Check user defined PolyFunction refinements " to LTS (#20700)
Backports #18457 to the LTS branch. PR submitted by the release tooling. [skip ci]
2 parents 1a43a43 + 54f5421 commit 3b94fb2

19 files changed

+126
-1
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.isMethodWithByNameArgs // 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/core/Types.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,12 @@ object Types {
419419
case _ => false
420420
}
421421

422+
/** Is this the type of a method that has a by-name parameters? */
423+
def isMethodWithByNameArgs(using Context): Boolean = stripPoly match {
424+
case mt: MethodType => mt.paramInfos.exists(_.isInstanceOf[ExprType])
425+
case _ => false
426+
}
427+
422428
/** Is this the type of a method with a leading empty parameter list?
423429
*/
424430
def isNullaryMethod(using Context): Boolean = stripPoly match {

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 {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -507,7 +507,7 @@ object Nullables:
507507
def postProcessByNameArgs(fn: TermRef, app: Tree)(using Context): Tree =
508508
fn.widen match
509509
case mt: MethodType
510-
if mt.paramInfos.exists(_.isInstanceOf[ExprType]) && !fn.symbol.is(Inline) =>
510+
if mt.isMethodWithByNameArgs && !fn.symbol.is(Inline) =>
511511
app match
512512
case Apply(fn, args) =>
513513
object dropNotNull extends TreeMap:

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)