Skip to content
Open
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
70 changes: 39 additions & 31 deletions compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6123,39 +6123,47 @@ object Types extends TypeUtils {
approxWildcardArgs(tp)
end samParent

def samClass(tp: Type)(using Context): Symbol = tp match
case tp: ClassInfo =>
val cls = tp.cls
def takesNoArgs(tp: Type) =
!tp.classSymbol.primaryConstructor.exists
def unapply(tp: Type)(using Context): Option[(MethodType, Type)] =
deconstruct(tp, false)

/** Optionally ignores `final` and `sealed`, so that error messages can point to this specific problem. */
def deconstruct(tp: Type, ignoreFinalOrSealed: Boolean)(using Context): Option[(MethodType, Type)] =
def samClass(tp: Type)(using Context): Symbol = tp match {
case tp: ClassInfo =>
val cls = tp.cls

def takesNoArgs(tp: Type) =
!tp.classSymbol.primaryConstructor.exists
// e.g. `ContextFunctionN` does not have constructors
|| tp.applicableConstructors(argTypes = Nil, adaptVarargs = true).lengthCompare(1) == 0
// we require a unique constructor so that SAM expansion is deterministic
val noArgsNeeded: Boolean =
takesNoArgs(tp)
&& (!cls.is(Trait) || takesNoArgs(tp.parents.head))
def isInstantiable =
!cls.isOneOf(FinalOrSealed) && (tp.appliedRef <:< tp.selfType)
if noArgsNeeded && isInstantiable then cls
else NoSymbol
case tp: AppliedType =>
samClass(tp.superType)
case tp: TypeRef =>
samClass(tp.underlying)
case tp: RefinedType =>
samClass(tp.underlying)
case tp: TypeBounds =>
samClass(tp.underlying)
case tp: TypeVar =>
samClass(tp.underlying)
case tp: AnnotatedType =>
samClass(tp.underlying)
case tp: FlexibleType =>
samClass(tp.underlying)
case _ =>
NoSymbol
|| tp.applicableConstructors(argTypes = Nil, adaptVarargs = true).lengthCompare(1) == 0

def unapply(tp: Type)(using Context): Option[(MethodType, Type)] =
// we require a unique constructor so that SAM expansion is deterministic
val noArgsNeeded: Boolean =
takesNoArgs(tp)
&& (!cls.is(Trait) || takesNoArgs(tp.parents.head))

def isInstantiable =
(ignoreFinalOrSealed || !cls.isOneOf(FinalOrSealed)) && (tp.appliedRef <:< tp.selfType)

if noArgsNeeded && isInstantiable then cls
else NoSymbol
case tp: AppliedType =>
samClass(tp.superType)
case tp: TypeRef =>
samClass(tp.underlying)
case tp: RefinedType =>
samClass(tp.underlying)
case tp: TypeBounds =>
samClass(tp.underlying)
case tp: TypeVar =>
samClass(tp.underlying)
case tp: AnnotatedType =>
samClass(tp.underlying)
case tp: FlexibleType =>
samClass(tp.underlying)
case _ =>
NoSymbol
}
val cls = samClass(tp)
if cls.exists then
val absMems =
Expand Down
8 changes: 7 additions & 1 deletion compiler/src/dotty/tools/dotc/reporting/messages.scala
Original file line number Diff line number Diff line change
Expand Up @@ -359,8 +359,14 @@ class TypeMismatch(val found: Type, expected: Type, val inTree: Option[untpd.Tre
if (found1 frozen_<:< expected1) || reported.fbounded then (found, expected)
else (found1, expected1)
val (foundStr, expectedStr) = Formatting.typeDiff(found2.normalized, expected2.normalized)
val extra =
if defn.isFunctionNType(found)
&& SAMType.deconstruct(expected, ignoreFinalOrSealed = false).isEmpty
&& SAMType.deconstruct(expected, ignoreFinalOrSealed = true).nonEmpty
then i"\nNote that conversion from a function to a single abstract method type is only possible if the type is neither ${hl("final")} nor ${hl("sealed")}"
else ""
i"""|${preface}Found: $foundStr
|Required: $expectedStr${reported.notes}"""
|Required: $expectedStr${reported.notes}$extra"""
end msg

override def msgPostscript(using Context): String =
Expand Down
8 changes: 8 additions & 0 deletions tests/neg/closure-to-sealed-trait.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
-- [E007] Type Mismatch Error: tests/neg/closure-to-sealed-trait.scala:4:18 --------------------------------------------
4 |def test: Foo = { (x: String) => "" } // error
| ^^^^^^^^^^^^^^^^^
|Found: String => String
|Required: Foo
|Note that conversion from a function to a single abstract method type is only possible if the type is neither final nor sealed
|
| longer explanation available when compiling with `-explain`
4 changes: 4 additions & 0 deletions tests/neg/closure-to-sealed-trait.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
sealed trait Foo {
def foo(x: String): String
}
def test: Foo = { (x: String) => "" } // error
Loading