diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 7e0867f087eb..30c20ded03ff 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -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 = diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 7e5fbb1d17ed..ba4f76fa7d04 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -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 = diff --git a/tests/neg/closure-to-sealed-trait.check b/tests/neg/closure-to-sealed-trait.check new file mode 100644 index 000000000000..f96a2b63ce02 --- /dev/null +++ b/tests/neg/closure-to-sealed-trait.check @@ -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` diff --git a/tests/neg/closure-to-sealed-trait.scala b/tests/neg/closure-to-sealed-trait.scala new file mode 100644 index 000000000000..8ca3ac494d38 --- /dev/null +++ b/tests/neg/closure-to-sealed-trait.scala @@ -0,0 +1,4 @@ +sealed trait Foo { + def foo(x: String): String +} +def test: Foo = { (x: String) => "" } // error