Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backport "Fail when a poly function value has a different number of type params than the expected poly function" to 3.5.2 #21459

Merged
merged 3 commits into from
Aug 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions compiler/src/dotty/tools/dotc/typer/Namer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -848,7 +848,7 @@ class Namer { typer: Typer =>
else
try
completeInCreationContext(denot)
if (denot.isCompleted) registerIfChild(denot)
if (denot.isCompleted) registerIfChildInCreationContext(denot)
catch
case ex: CompilationUnit.SuspendException =>
val completer = SuspendCompleter()
Expand Down Expand Up @@ -937,10 +937,12 @@ class Namer { typer: Typer =>
denot.markAbsent()
end invalidateIfClashingSynthetic

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

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

/** Intentionally left without `implicit ctx` parameter. We need
/** Intentionally left without `using Context` parameter. We need
* to pick up the context at the point where the completer was created.
*/
def completeInCreationContext(denot: SymDenotation): Unit = {
Expand Down
52 changes: 27 additions & 25 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1895,32 +1895,34 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
val untpd.Function(vparams: List[untpd.ValDef] @unchecked, body) = fun: @unchecked
val dpt = pt.dealias

// If the expected type is a polymorphic function with the same number of
// type and value parameters, then infer the types of value parameters from the expected type.
val inferredVParams = dpt match
case defn.PolyFunctionOf(poly @ PolyType(_, mt: MethodType))
if tparams.lengthCompare(poly.paramNames) == 0 && vparams.lengthCompare(mt.paramNames) == 0 =>
vparams.zipWithConserve(mt.paramInfos): (vparam, formal) =>
// Unlike in typedFunctionValue, `formal` cannot be a TypeBounds since
// it must be a valid method parameter type.
if vparam.tpt.isEmpty && isFullyDefined(formal, ForceDegree.failBottom) then
cpy.ValDef(vparam)(tpt = new untpd.InLambdaTypeTree(isResult = false, (tsyms, vsyms) =>
// We don't need to substitute `mt` by `vsyms` because we currently disallow
// dependencies between value parameters of a closure.
formal.substParams(poly, tsyms.map(_.typeRef)))
)
else vparam
case _ =>
vparams

val resultTpt = dpt match
dpt match
case defn.PolyFunctionOf(poly @ PolyType(_, mt: MethodType)) =>
untpd.InLambdaTypeTree(isResult = true, (tsyms, vsyms) =>
mt.resultType.substParams(mt, vsyms.map(_.termRef)).substParams(poly, tsyms.map(_.typeRef)))
case _ => untpd.TypeTree()

val desugared = desugar.makeClosure(tparams, inferredVParams, body, resultTpt, tree.span)
typed(desugared, pt)
if tparams.lengthCompare(poly.paramNames) == 0 && vparams.lengthCompare(mt.paramNames) == 0 then
// If the expected type is a polymorphic function with the same number of
// type and value parameters, then infer the types of value parameters from the expected type.
val inferredVParams = vparams.zipWithConserve(mt.paramInfos): (vparam, formal) =>
// Unlike in typedFunctionValue, `formal` cannot be a TypeBounds since
// it must be a valid method parameter type.
if vparam.tpt.isEmpty && isFullyDefined(formal, ForceDegree.failBottom) then
cpy.ValDef(vparam)(tpt = new untpd.InLambdaTypeTree(isResult = false, (tsyms, vsyms) =>
// We don't need to substitute `mt` by `vsyms` because we currently disallow
// dependencies between value parameters of a closure.
formal.substParams(poly, tsyms.map(_.typeRef)))
)
else vparam
val resultTpt =
untpd.InLambdaTypeTree(isResult = true, (tsyms, vsyms) =>
mt.resultType.substParams(mt, vsyms.map(_.termRef)).substParams(poly, tsyms.map(_.typeRef)))
val desugared = desugar.makeClosure(tparams, inferredVParams, body, resultTpt, tree.span)
typed(desugared, pt)
else
val msg =
em"""|Provided polymorphic function value doesn't match the expected type $dpt.
|Expected type should be a polymorphic function with the same number of type and value parameters."""
errorTree(EmptyTree, msg, tree.srcPos)
case _ =>
val desugared = desugar.makeClosure(tparams, vparams, body, untpd.TypeTree(), tree.span)
typed(desugared, pt)
end typedPolyFunctionValue

def typedClosure(tree: untpd.Closure, pt: Type)(using Context): Tree = {
Expand Down
5 changes: 5 additions & 0 deletions tests/neg/i20533.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-- Error: tests/neg/i20533.scala:5:8 -----------------------------------------------------------------------------------
5 | [X] => (x, y) => Map(x -> y) // error
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| Provided polymorphic function value doesn't match the expected type [X, Y] => (x$1: X, x$2: Y) => Map[X, Y].
| Expected type should be a polymorphic function with the same number of type and value parameters.
6 changes: 6 additions & 0 deletions tests/neg/i20533.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
def mapF(h: [X, Y] => (X, Y) => Map[X, Y]): Unit = ???

def test =
mapF(
[X] => (x, y) => Map(x -> y) // error
)
6 changes: 6 additions & 0 deletions tests/pos/i21154/A.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import Z.*

object A:
val a: Option[AOptions] = ???
val b: Option[BOptions] = ???
val c: Option[COptions] = ???
9 changes: 9 additions & 0 deletions tests/pos/i21154/Z.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
//> using options -Ytest-pickler-check

// in the original issue https://github.com/scala/scala3/issues/21154, the non-deterministic tasty
// depends on the order of compilation of files, the use-site (A.scala) has to come first,
// and the file defining the enum has to come second (Z.scala), A.scala in namer will force Z to complete.
enum Z:
case AOptions()
case BOptions()
case COptions()
Loading