From 8812638655ce004d2354716468ebba5b8a18ac14 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 31 May 2023 18:28:22 +0100 Subject: [PATCH 1/3] Convert SAM result types to function types --- compiler/src/dotty/tools/dotc/typer/Typer.scala | 5 ++++- tests/pos/i17183.scala | 9 +++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 tests/pos/i17183.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 2e7444af8e96..375289b5b400 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1335,7 +1335,10 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case RefinedType(parent, nme.apply, mt @ MethodTpe(_, formals, restpe)) if (defn.isNonRefinedFunction(parent) || defn.isErasedFunctionType(parent)) && formals.length == defaultArity => (formals, untpd.DependentTypeTree(syms => restpe.substParams(mt, syms.map(_.termRef)))) - case SAMType(mt @ MethodTpe(_, formals, restpe)) => + case pt1 @ SAMType(mt @ MethodTpe(_, formals, methResType)) => + val restpe = methResType match + case mt: MethodType if !mt.isParamDependent => mt.toFunctionType(isJava = pt1.classSymbol.is(JavaDefined)) + case tp => tp (formals, if (mt.isResultDependent) untpd.DependentTypeTree(syms => restpe.substParams(mt, syms.map(_.termRef))) diff --git a/tests/pos/i17183.scala b/tests/pos/i17183.scala new file mode 100644 index 000000000000..b53a2b138985 --- /dev/null +++ b/tests/pos/i17183.scala @@ -0,0 +1,9 @@ +trait Dependency + +trait MyFunc { + def apply(a: Int, b: String)(using Dependency): String +} + +case class Context(f: MyFunc) + +def test = Context(f = (_, _) => ???) From 9880cea9d9d730cf85e028a75dd389535bd66500 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Mon, 5 Jun 2023 14:35:36 +0100 Subject: [PATCH 2/3] Avoid SAM with param-dependent result crashing --- compiler/src/dotty/tools/dotc/core/Types.scala | 11 +++++++++++ .../src/dotty/tools/dotc/typer/Applications.scala | 2 +- compiler/src/dotty/tools/dotc/typer/Typer.scala | 9 +++------ tests/neg/i17183.check | 14 ++++++++++++++ tests/neg/i17183.scala | 11 +++++++++++ 5 files changed, 40 insertions(+), 7 deletions(-) create mode 100644 tests/neg/i17183.check create mode 100644 tests/neg/i17183.scala diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index bbec037ebef1..32c5e39a7e22 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -5568,6 +5568,17 @@ object Types { else None } else None + + object WithFunctionType: + def unapply(tp: Type)(using Context): Option[(MethodType, Type)] = tp match + case SAMType(mt) if !isParamDependentRec(mt) => + Some((mt, mt.toFunctionType(isJava = tp.classSymbol.is(JavaDefined)))) + case _ => None + + private def isParamDependentRec(mt: MethodType)(using Context): Boolean = + mt.isParamDependent || mt.resultType.match + case mt: MethodType => isParamDependentRec(mt) + case _ => false } // ----- TypeMaps -------------------------------------------------------------------- diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index fbed4b77d3fe..4d03e9760ff6 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -696,7 +696,7 @@ trait Applications extends Compatibility { def SAMargOK = defn.isFunctionType(argtpe1) && formal.match - case SAMType(sam) => argtpe <:< sam.toFunctionType(isJava = formal.classSymbol.is(JavaDefined)) + case SAMType.WithFunctionType(_, fntpe) => argtpe <:< fntpe case _ => false isCompatible(argtpe, formal) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 375289b5b400..3f3c4021edbd 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1335,10 +1335,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case RefinedType(parent, nme.apply, mt @ MethodTpe(_, formals, restpe)) if (defn.isNonRefinedFunction(parent) || defn.isErasedFunctionType(parent)) && formals.length == defaultArity => (formals, untpd.DependentTypeTree(syms => restpe.substParams(mt, syms.map(_.termRef)))) - case pt1 @ SAMType(mt @ MethodTpe(_, formals, methResType)) => - val restpe = methResType match - case mt: MethodType if !mt.isParamDependent => mt.toFunctionType(isJava = pt1.classSymbol.is(JavaDefined)) - case tp => tp + case SAMType.WithFunctionType(mt @ MethodTpe(_, formals, _), defn.FunctionOf(_, restpe, _)) => (formals, if (mt.isResultDependent) untpd.DependentTypeTree(syms => restpe.substParams(mt, syms.map(_.termRef))) @@ -4131,8 +4128,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case closure(Nil, id @ Ident(nme.ANON_FUN), _) if defn.isFunctionType(wtp) && !defn.isFunctionType(pt) => pt match { - case SAMType(sam) - if wtp <:< sam.toFunctionType(isJava = pt.classSymbol.is(JavaDefined)) => + case SAMType.WithFunctionType(_, fntpe) + if wtp <:< fntpe => // was ... && isFullyDefined(pt, ForceDegree.flipBottom) // but this prevents case blocks from implementing polymorphic partial functions, // since we do not know the result parameter a priori. Have to wait until the diff --git a/tests/neg/i17183.check b/tests/neg/i17183.check new file mode 100644 index 000000000000..37e0c5fd75c2 --- /dev/null +++ b/tests/neg/i17183.check @@ -0,0 +1,14 @@ +-- [E081] Type Error: tests/neg/i17183.scala:11:24 --------------------------------------------------------------------- +11 |def test = Context(f = (_, _) => ???) // error // error + | ^ + | Missing parameter type + | + | I could not infer the type of the parameter _$1 of expanded function: + | (_$1, _$2) => ???. +-- [E081] Type Error: tests/neg/i17183.scala:11:27 --------------------------------------------------------------------- +11 |def test = Context(f = (_, _) => ???) // error // error + | ^ + | Missing parameter type + | + | I could not infer the type of the parameter _$2 of expanded function: + | (_$1, _$2) => ???. diff --git a/tests/neg/i17183.scala b/tests/neg/i17183.scala new file mode 100644 index 000000000000..a7d2d51c5935 --- /dev/null +++ b/tests/neg/i17183.scala @@ -0,0 +1,11 @@ +trait Dependency { + trait More +} + +trait MyFunc { + def apply(a: Int, b: String)(using dep: Dependency, more: dep.More): String +} + +case class Context(f: MyFunc) + +def test = Context(f = (_, _) => ???) // error // error From f641a873024146bd35c66b02b6480da2df3b8e36 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 6 Jun 2023 11:32:45 +0100 Subject: [PATCH 3/3] Fix guarding & converting SAM result type to a function type In decomposeProtoFunction I wanted to reuse WithFunctionType by running toFunctionType on the whole SAM and then dropping the first parameters. But with result dependent functions that results in a refined type, which is composed of: * the function with a result that is a non-depedent approximation * the original, non-function, method type So I went back to calling toFunctionType on the method result type specifically, like it used to be. Which means I stopped using the SAMType.WithFunctionType extract, so I merged it with its remaining usage with subtype checking and called the SAMType.isSamCompatible. --- .../src/dotty/tools/dotc/core/Types.scala | 17 +++++++------- .../dotty/tools/dotc/typer/Applications.scala | 4 +--- .../src/dotty/tools/dotc/typer/Typer.scala | 22 +++++++++---------- 3 files changed, 19 insertions(+), 24 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 32c5e39a7e22..b58a923457cd 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -5569,16 +5569,15 @@ object Types { } else None - object WithFunctionType: - def unapply(tp: Type)(using Context): Option[(MethodType, Type)] = tp match - case SAMType(mt) if !isParamDependentRec(mt) => - Some((mt, mt.toFunctionType(isJava = tp.classSymbol.is(JavaDefined)))) - case _ => None + def isSamCompatible(lhs: Type, rhs: Type)(using Context): Boolean = rhs match + case SAMType(mt) if !isParamDependentRec(mt) => + lhs <:< mt.toFunctionType(isJava = rhs.classSymbol.is(JavaDefined)) + case _ => false - private def isParamDependentRec(mt: MethodType)(using Context): Boolean = - mt.isParamDependent || mt.resultType.match - case mt: MethodType => isParamDependentRec(mt) - case _ => false + def isParamDependentRec(mt: MethodType)(using Context): Boolean = + mt.isParamDependent || mt.resultType.match + case mt: MethodType => isParamDependentRec(mt) + case _ => false } // ----- TypeMaps -------------------------------------------------------------------- diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 4d03e9760ff6..17b79ab0f801 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -695,9 +695,7 @@ trait Applications extends Compatibility { val argtpe1 = argtpe.widen def SAMargOK = - defn.isFunctionType(argtpe1) && formal.match - case SAMType.WithFunctionType(_, fntpe) => argtpe <:< fntpe - case _ => false + defn.isFunctionType(argtpe1) && SAMType.isSamCompatible(argtpe, formal) isCompatible(argtpe, formal) // Only allow SAM-conversion to PartialFunction if implicit conversions diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 3f3c4021edbd..e42c0eec165f 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1335,7 +1335,10 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case RefinedType(parent, nme.apply, mt @ MethodTpe(_, formals, restpe)) if (defn.isNonRefinedFunction(parent) || defn.isErasedFunctionType(parent)) && formals.length == defaultArity => (formals, untpd.DependentTypeTree(syms => restpe.substParams(mt, syms.map(_.termRef)))) - case SAMType.WithFunctionType(mt @ MethodTpe(_, formals, _), defn.FunctionOf(_, restpe, _)) => + case pt1 @ SAMType(mt @ MethodTpe(_, formals, _)) if !SAMType.isParamDependentRec(mt) => + val restpe = mt.resultType match + case mt: MethodType => mt.toFunctionType(isJava = pt1.classSymbol.is(JavaDefined)) + case tp => tp (formals, if (mt.isResultDependent) untpd.DependentTypeTree(syms => restpe.substParams(mt, syms.map(_.termRef))) @@ -4126,17 +4129,12 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer // convert function literal to SAM closure tree match { case closure(Nil, id @ Ident(nme.ANON_FUN), _) - if defn.isFunctionType(wtp) && !defn.isFunctionType(pt) => - pt match { - case SAMType.WithFunctionType(_, fntpe) - if wtp <:< fntpe => - // was ... && isFullyDefined(pt, ForceDegree.flipBottom) - // but this prevents case blocks from implementing polymorphic partial functions, - // since we do not know the result parameter a priori. Have to wait until the - // body is typechecked. - return toSAM(tree) - case _ => - } + if defn.isFunctionType(wtp) && !defn.isFunctionType(pt) && SAMType.isSamCompatible(wtp, pt) => + // was ... && isFullyDefined(pt, ForceDegree.flipBottom) + // but this prevents case blocks from implementing polymorphic partial functions, + // since we do not know the result parameter a priori. Have to wait until the + // body is typechecked. + return toSAM(tree) case _ => }