Skip to content

Commit aed8a87

Browse files
authored
Merge pull request #12131 from dotty-staging/backport-fix-11938-new
[Backport] Overloading resolution: Handle SAM types more like Java and Scala 2
2 parents 3389f40 + 990a01f commit aed8a87

File tree

9 files changed

+86
-35
lines changed

9 files changed

+86
-35
lines changed

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,7 @@ class Definitions {
133133
ClassInfo(ScalaPackageClass.thisType, cls, ObjectType :: Nil, decls)
134134
}
135135
}
136-
val flags0 = Trait | NoInits
137-
val flags = if (name.isContextFunction) flags0 | Final else flags0
136+
val flags = Trait | NoInits
138137
newPermanentClassSymbol(ScalaPackageClass, name, flags, completer)
139138
}
140139

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5043,7 +5043,7 @@ object Types {
50435043
NoType
50445044
}
50455045
def isInstantiatable(tp: Type)(using Context): Boolean = zeroParamClass(tp) match {
5046-
case cinfo: ClassInfo =>
5046+
case cinfo: ClassInfo if !cinfo.cls.isOneOf(FinalOrSealed) =>
50475047
val selfType = cinfo.selfType.asSeenFrom(tp, cinfo.cls)
50485048
tp <:< selfType
50495049
case _ =>

compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,8 @@ enum ErrorMessageID extends java.lang.Enum[ErrorMessageID] {
174174
OverrideTypeMismatchErrorID,
175175
OverrideErrorID,
176176
MatchableWarningID,
177-
IllegalParameterInitID
177+
IllegalParameterInitID,
178+
CannotExtendFunctionID
178179

179180
def errorNumber = ordinal - 2
180181
}

compiler/src/dotty/tools/dotc/reporting/messages.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1558,6 +1558,12 @@ import transform.SymUtils._
15581558
def explain = ""
15591559
}
15601560

1561+
class CannotExtendContextFunction(sym: Symbol)(using Context)
1562+
extends SyntaxMsg(CannotExtendFunctionID) {
1563+
def msg = em"""$sym cannot extend a context function class"""
1564+
def explain = ""
1565+
}
1566+
15611567
class JavaEnumParentArgs(parent: Type)(using Context)
15621568
extends TypeMsg(JavaEnumParentArgsID) {
15631569
def msg = em"""not enough arguments for constructor Enum: ${hl("(name: String, ordinal: Int)")}: ${hl(parent.show)}"""

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

Lines changed: 35 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -614,7 +614,6 @@ trait Applications extends Compatibility {
614614

615615
/** The degree to which an argument has to match a formal parameter */
616616
enum ArgMatch:
617-
case SubType // argument is a relaxed subtype of formal
618617
case Compatible // argument is compatible with formal
619618
case CompatibleCAP // capture-converted argument is compatible with formal
620619

@@ -635,21 +634,38 @@ trait Applications extends Compatibility {
635634
// matches expected type
636635
false
637636
case argtpe =>
638-
def SAMargOK = formal match {
639-
case SAMType(sam) => argtpe <:< sam.toFunctionType(isJava = formal.classSymbol.is(JavaDefined))
640-
case _ => false
641-
}
642-
if argMatch == ArgMatch.SubType then
643-
argtpe relaxed_<:< formal.widenExpr
644-
else
645-
isCompatible(argtpe, formal)
646-
|| ctx.mode.is(Mode.ImplicitsEnabled) && SAMargOK
647-
|| argMatch == ArgMatch.CompatibleCAP
648-
&& {
649-
val argtpe1 = argtpe.widen
650-
val captured = captureWildcards(argtpe1)
651-
(captured ne argtpe1) && isCompatible(captured, formal.widenExpr)
652-
}
637+
val argtpe1 = argtpe.widen
638+
639+
def SAMargOK =
640+
defn.isFunctionType(argtpe1) && formal.match
641+
case SAMType(sam) => argtpe <:< sam.toFunctionType(isJava = formal.classSymbol.is(JavaDefined))
642+
case _ => false
643+
644+
isCompatible(argtpe, formal)
645+
// Only allow SAM-conversion to PartialFunction if implicit conversions
646+
// are enabled. This is necessary to avoid ambiguity between an overload
647+
// taking a PartialFunction and one taking a Function1 because
648+
// PartialFunction extends Function1 but Function1 is SAM-convertible to
649+
// PartialFunction. Concretely, given:
650+
//
651+
// def foo(a: Int => Int): Unit = println("1")
652+
// def foo(a: PartialFunction[Int, Int]): Unit = println("2")
653+
//
654+
// - `foo(x => x)` will print 1, because the PartialFunction overload
655+
// won't be seen as applicable in the first call to
656+
// `resolveOverloaded`, this behavior happens to match what Java does
657+
// since PartialFunction is not a SAM type according to Java
658+
// (`isDefined` is abstract).
659+
// - `foo { case x if x % 2 == 0 => x }` will print 2, because both
660+
// overloads are applicable, but PartialFunction is a subtype of
661+
// Function1 so it's more specific.
662+
|| (!formal.isRef(defn.PartialFunctionClass) || ctx.mode.is(Mode.ImplicitsEnabled)) && SAMargOK
663+
|| argMatch == ArgMatch.CompatibleCAP
664+
&& {
665+
val argtpe1 = argtpe.widen
666+
val captured = captureWildcards(argtpe1)
667+
(captured ne argtpe1) && isCompatible(captured, formal.widenExpr)
668+
}
653669

654670
/** The type of the given argument */
655671
protected def argType(arg: Arg, formal: Type): Type
@@ -1863,17 +1879,10 @@ trait Applications extends Compatibility {
18631879
else
18641880
alts
18651881

1866-
def narrowByTrees(alts: List[TermRef], args: List[Tree], resultType: Type): List[TermRef] = {
1867-
val alts2 = alts.filterConserve(alt =>
1868-
isApplicableMethodRef(alt, args, resultType, keepConstraint = false, ArgMatch.SubType)
1882+
def narrowByTrees(alts: List[TermRef], args: List[Tree], resultType: Type): List[TermRef] =
1883+
alts.filterConserve(alt =>
1884+
isApplicableMethodRef(alt, args, resultType, keepConstraint = false, ArgMatch.CompatibleCAP)
18691885
)
1870-
if (alts2.isEmpty && !ctx.isAfterTyper)
1871-
alts.filterConserve(alt =>
1872-
isApplicableMethodRef(alt, args, resultType, keepConstraint = false, ArgMatch.CompatibleCAP)
1873-
)
1874-
else
1875-
alts2
1876-
}
18771886

18781887
record("resolveOverloaded.FunProto", alts.length)
18791888
val alts1 = narrowBySize(alts)

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ object RefChecks {
9696

9797
/** Check that self type of this class conforms to self types of parents
9898
* and required classes. Also check that only `enum` constructs extend
99-
* `java.lang.Enum`.
99+
* `java.lang.Enum` and no user-written class extends ContextFunctionN.
100100
*/
101101
private def checkParents(cls: Symbol, parentTrees: List[Tree])(using Context): Unit = cls.info match {
102102
case cinfo: ClassInfo =>
@@ -132,6 +132,8 @@ object RefChecks {
132132
case _ =>
133133
false
134134
}
135+
if psyms.exists(defn.isContextFunctionClass) then
136+
report.error(CannotExtendContextFunction(cls), cls.sourcePos)
135137

136138
/** Check that arguments passed to trait parameters conform to the parameter types
137139
* in the current class. This is necessary since parameter types might be narrowed

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

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1398,10 +1398,6 @@ class Typer extends Namer
13981398
else
13991399
report.error(ex"result type of lambda is an underspecified SAM type $pt", tree.srcPos)
14001400
pt
1401-
if (pt.classSymbol.isOneOf(FinalOrSealed)) {
1402-
val offendingFlag = pt.classSymbol.flags & FinalOrSealed
1403-
report.error(ex"lambda cannot implement $offendingFlag ${pt.classSymbol}", tree.srcPos)
1404-
}
14051401
TypeTree(targetTpe)
14061402
case _ =>
14071403
if (mt.isParamDependent)

tests/neg/i11938.scala

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import java.util.function.Function
2+
3+
object Test {
4+
def foo[V](v: V): Int = 1
5+
def foo[U](fn: Function[Int, U]): Int = 2
6+
7+
def main(args: Array[String]): Unit = {
8+
val f: Int => Int = x => x
9+
foo(f) // error
10+
// Like Scala 2, we emit an error here because the Function1 argument was
11+
// deemed SAM-convertible to Function, even though it's not a lambda
12+
// expression and therefore not convertible. If we wanted to support this,
13+
// we would have to tweak TestApplication#argOK to look at the shape of
14+
// `arg` and turn off SAM conversions when it's a non-closure tree.
15+
}
16+
}

tests/run/i11938.scala

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import java.util.function.Function
2+
3+
object Test {
4+
def foo[V](v: V): Int = 1
5+
def foo[U](fn: Function[Int, U]): Int = 2
6+
7+
def foo2(a: Int => Int): Int = 1
8+
def foo2(a: PartialFunction[Int, Int]): Int = 2
9+
10+
def main(args: Array[String]): Unit = {
11+
assert(foo((x: Int) => x) == 2)
12+
val jf: Function[Int, Int] = x => x
13+
assert(foo(jf) == 2)
14+
15+
assert(foo2(x => x) == 1)
16+
val f: Int => Int = x => x
17+
assert(foo2(f) == 1)
18+
assert(foo2({ case x if x % 2 == 0 => x }) == 2)
19+
val pf: PartialFunction[Int, Int] = { case x if x % 2 == 0 => x }
20+
assert(foo2(pf) == 2)
21+
}
22+
}

0 commit comments

Comments
 (0)