Skip to content

Commit 1249c73

Browse files
Backport "Fail when a poly function value has a different number of type params than the expected poly function" to 3.5.2 (#21459)
Backports #21248 to the 3.5.2 branch. PR submitted by the release tooling. [skip ci]
2 parents 9de63ca + fbe05ae commit 1249c73

File tree

7 files changed

+1399
-29
lines changed

7 files changed

+1399
-29
lines changed

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

+6-4
Original file line numberDiff line numberDiff line change
@@ -848,7 +848,7 @@ class Namer { typer: Typer =>
848848
else
849849
try
850850
completeInCreationContext(denot)
851-
if (denot.isCompleted) registerIfChild(denot)
851+
if (denot.isCompleted) registerIfChildInCreationContext(denot)
852852
catch
853853
case ex: CompilationUnit.SuspendException =>
854854
val completer = SuspendCompleter()
@@ -937,10 +937,12 @@ class Namer { typer: Typer =>
937937
denot.markAbsent()
938938
end invalidateIfClashingSynthetic
939939

940-
/** If completed symbol is an enum value or a named class, register it as a child
940+
/** Intentionally left without `using Context` parameter.
941+
* This action should be performed in the context of where the completer was created.
942+
* If completed symbol is an enum value or a named class, register it as a child
941943
* in all direct parent classes which are sealed.
942944
*/
943-
def registerIfChild(denot: SymDenotation)(using Context): Unit = {
945+
def registerIfChildInCreationContext(denot: SymDenotation): Unit = {
944946
val sym = denot.symbol
945947

946948
def register(child: Symbol, parentCls: ClassSymbol) = {
@@ -964,7 +966,7 @@ class Namer { typer: Typer =>
964966
end if
965967
}
966968

967-
/** Intentionally left without `implicit ctx` parameter. We need
969+
/** Intentionally left without `using Context` parameter. We need
968970
* to pick up the context at the point where the completer was created.
969971
*/
970972
def completeInCreationContext(denot: SymDenotation): Unit = {

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

+27-25
Original file line numberDiff line numberDiff line change
@@ -1895,32 +1895,34 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
18951895
val untpd.Function(vparams: List[untpd.ValDef] @unchecked, body) = fun: @unchecked
18961896
val dpt = pt.dealias
18971897

1898-
// If the expected type is a polymorphic function with the same number of
1899-
// type and value parameters, then infer the types of value parameters from the expected type.
1900-
val inferredVParams = dpt match
1901-
case defn.PolyFunctionOf(poly @ PolyType(_, mt: MethodType))
1902-
if tparams.lengthCompare(poly.paramNames) == 0 && vparams.lengthCompare(mt.paramNames) == 0 =>
1903-
vparams.zipWithConserve(mt.paramInfos): (vparam, formal) =>
1904-
// Unlike in typedFunctionValue, `formal` cannot be a TypeBounds since
1905-
// it must be a valid method parameter type.
1906-
if vparam.tpt.isEmpty && isFullyDefined(formal, ForceDegree.failBottom) then
1907-
cpy.ValDef(vparam)(tpt = new untpd.InLambdaTypeTree(isResult = false, (tsyms, vsyms) =>
1908-
// We don't need to substitute `mt` by `vsyms` because we currently disallow
1909-
// dependencies between value parameters of a closure.
1910-
formal.substParams(poly, tsyms.map(_.typeRef)))
1911-
)
1912-
else vparam
1913-
case _ =>
1914-
vparams
1915-
1916-
val resultTpt = dpt match
1898+
dpt match
19171899
case defn.PolyFunctionOf(poly @ PolyType(_, mt: MethodType)) =>
1918-
untpd.InLambdaTypeTree(isResult = true, (tsyms, vsyms) =>
1919-
mt.resultType.substParams(mt, vsyms.map(_.termRef)).substParams(poly, tsyms.map(_.typeRef)))
1920-
case _ => untpd.TypeTree()
1921-
1922-
val desugared = desugar.makeClosure(tparams, inferredVParams, body, resultTpt, tree.span)
1923-
typed(desugared, pt)
1900+
if tparams.lengthCompare(poly.paramNames) == 0 && vparams.lengthCompare(mt.paramNames) == 0 then
1901+
// If the expected type is a polymorphic function with the same number of
1902+
// type and value parameters, then infer the types of value parameters from the expected type.
1903+
val inferredVParams = vparams.zipWithConserve(mt.paramInfos): (vparam, formal) =>
1904+
// Unlike in typedFunctionValue, `formal` cannot be a TypeBounds since
1905+
// it must be a valid method parameter type.
1906+
if vparam.tpt.isEmpty && isFullyDefined(formal, ForceDegree.failBottom) then
1907+
cpy.ValDef(vparam)(tpt = new untpd.InLambdaTypeTree(isResult = false, (tsyms, vsyms) =>
1908+
// We don't need to substitute `mt` by `vsyms` because we currently disallow
1909+
// dependencies between value parameters of a closure.
1910+
formal.substParams(poly, tsyms.map(_.typeRef)))
1911+
)
1912+
else vparam
1913+
val resultTpt =
1914+
untpd.InLambdaTypeTree(isResult = true, (tsyms, vsyms) =>
1915+
mt.resultType.substParams(mt, vsyms.map(_.termRef)).substParams(poly, tsyms.map(_.typeRef)))
1916+
val desugared = desugar.makeClosure(tparams, inferredVParams, body, resultTpt, tree.span)
1917+
typed(desugared, pt)
1918+
else
1919+
val msg =
1920+
em"""|Provided polymorphic function value doesn't match the expected type $dpt.
1921+
|Expected type should be a polymorphic function with the same number of type and value parameters."""
1922+
errorTree(EmptyTree, msg, tree.srcPos)
1923+
case _ =>
1924+
val desugared = desugar.makeClosure(tparams, vparams, body, untpd.TypeTree(), tree.span)
1925+
typed(desugared, pt)
19241926
end typedPolyFunctionValue
19251927

19261928
def typedClosure(tree: untpd.Closure, pt: Type)(using Context): Tree = {

tests/neg/i20533.check

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
-- Error: tests/neg/i20533.scala:5:8 -----------------------------------------------------------------------------------
2+
5 | [X] => (x, y) => Map(x -> y) // error
3+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
4+
| Provided polymorphic function value doesn't match the expected type [X, Y] => (x$1: X, x$2: Y) => Map[X, Y].
5+
| Expected type should be a polymorphic function with the same number of type and value parameters.

tests/neg/i20533.scala

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
def mapF(h: [X, Y] => (X, Y) => Map[X, Y]): Unit = ???
2+
3+
def test =
4+
mapF(
5+
[X] => (x, y) => Map(x -> y) // error
6+
)

tests/pos/i21154/A.scala

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import Z.*
2+
3+
object A:
4+
val a: Option[AOptions] = ???
5+
val b: Option[BOptions] = ???
6+
val c: Option[COptions] = ???

tests/pos/i21154/Z.scala

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
//> using options -Ytest-pickler-check
2+
3+
// in the original issue https://github.com/scala/scala3/issues/21154, the non-deterministic tasty
4+
// depends on the order of compilation of files, the use-site (A.scala) has to come first,
5+
// and the file defining the enum has to come second (Z.scala), A.scala in namer will force Z to complete.
6+
enum Z:
7+
case AOptions()
8+
case BOptions()
9+
case COptions()

0 commit comments

Comments
 (0)