From f3aea49b6f7754ffa79beab007fcdac4a37dc4c5 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Wed, 22 Oct 2025 17:38:36 +0200 Subject: [PATCH 1/6] Try to instantiate `pt` if this is possible by looking into union/intersection types --- compiler/src/dotty/tools/dotc/typer/Typer.scala | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 815c88ceb465..d9cd13f5fa23 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1929,15 +1929,13 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer NoType } - pt.stripNull() match { - case pt: TypeVar - if untpd.isFunctionWithUnknownParamType(tree) && !calleeType.exists => - // try to instantiate `pt` if this is possible. If it does not - // work the error will be reported later in `inferredParam`, - // when we try to infer the parameter type. - isFullyDefined(pt, ForceDegree.flipBottom) - case _ => - } + if pt.existsPart(_.isInstanceOf[TypeVar], StopAt.Static) + && untpd.isFunctionWithUnknownParamType(tree) + && !calleeType.exists then + // try to instantiate `pt` if this is possible. If it does not + // work the error will be reported later in `inferredParam`, + // when we try to infer the parameter type. + isFullyDefined(pt, ForceDegree.flipBottom) val (protoFormals, resultTpt) = decomposeProtoFunction(pt, params.length, tree.srcPos) From 5b03dca156e523a20ca232d9c03dc8d53dd4588e Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Thu, 23 Oct 2025 10:55:23 +0200 Subject: [PATCH 2/6] Refactor type instantiation logic when typing function values to handle union types --- compiler/src/dotty/tools/dotc/typer/Typer.scala | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index d9cd13f5fa23..9dae22c4b1f7 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1929,13 +1929,21 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer NoType } - if pt.existsPart(_.isInstanceOf[TypeVar], StopAt.Static) - && untpd.isFunctionWithUnknownParamType(tree) - && !calleeType.exists then + def instantiateInUnion(tp: Type): Unit = tp match + case tp: OrType => + instantiateInUnion(tp.tp1) + instantiateInUnion(tp.tp2) + case tp: FlexibleType => + instantiateInUnion(tp.hi) + case tp: TypeVar => + isFullyDefined(tp, ForceDegree.flipBottom) + case _ => + + if untpd.isFunctionWithUnknownParamType(tree) && !calleeType.exists then // try to instantiate `pt` if this is possible. If it does not // work the error will be reported later in `inferredParam`, // when we try to infer the parameter type. - isFullyDefined(pt, ForceDegree.flipBottom) + instantiateInUnion(pt) val (protoFormals, resultTpt) = decomposeProtoFunction(pt, params.length, tree.srcPos) From 0b80a02a174d0e78816797b7ca6d22cce9a13e47 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Thu, 23 Oct 2025 11:29:53 +0200 Subject: [PATCH 3/6] Update comments; add test cases for function type inference with union types. --- .../src/dotty/tools/dotc/typer/Typer.scala | 16 +++++----- tests/pos/infer-function-type-in-union.scala | 32 +++++++++++++++++++ 2 files changed, 40 insertions(+), 8 deletions(-) create mode 100644 tests/pos/infer-function-type-in-union.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 9dae22c4b1f7..e961b69273ee 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1929,21 +1929,21 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer NoType } - def instantiateInUnion(tp: Type): Unit = tp match + def tryToInstantiateInUnion(tp: Type): Unit = tp match case tp: OrType => - instantiateInUnion(tp.tp1) - instantiateInUnion(tp.tp2) + tryToInstantiateInUnion(tp.tp1) + tryToInstantiateInUnion(tp.tp2) case tp: FlexibleType => - instantiateInUnion(tp.hi) + tryToInstantiateInUnion(tp.hi) case tp: TypeVar => isFullyDefined(tp, ForceDegree.flipBottom) case _ => if untpd.isFunctionWithUnknownParamType(tree) && !calleeType.exists then - // try to instantiate `pt` if this is possible. If it does not - // work the error will be reported later in `inferredParam`, - // when we try to infer the parameter type. - instantiateInUnion(pt) + // Try to instantiate `pt` when possible, including type variables in union types + // to help finding function types. If it does not work the error will be reported + // later in `inferredParam`, when we try to infer the parameter type. + tryToInstantiateInUnion(pt) val (protoFormals, resultTpt) = decomposeProtoFunction(pt, params.length, tree.srcPos) diff --git a/tests/pos/infer-function-type-in-union.scala b/tests/pos/infer-function-type-in-union.scala new file mode 100644 index 000000000000..dcd3edcd8987 --- /dev/null +++ b/tests/pos/infer-function-type-in-union.scala @@ -0,0 +1,32 @@ + +def f[T](x: T): T = ??? +def f2[T](x: T | T): T = ??? +def f3[T](x: T | Null): T = ??? +def f4[T](x: Int | T): T = ??? + +trait MyOption[+T] + +object MyOption: + def apply[T](x: T | Null): MyOption[T] = ??? + +def test = + val g: AnyRef => Boolean = f { + x => x eq null // ok + } + val g2: AnyRef => Boolean = f2 { + x => x eq null // ok + } + val g3: AnyRef => Boolean = f3 { + x => x eq null // was error + } + val g4: AnyRef => Boolean = f4 { + x => x eq null // was error + } + + val o1: MyOption[String] = MyOption(null) + val o2: MyOption[String => Boolean] = MyOption { + x => x.length > 0 + } + val o3: MyOption[(String, String) => Boolean] = MyOption { + (x, y) => x.length > y.length + } \ No newline at end of file From abb7d87a2204ceeee03b91b9f2778af172ab1163 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Thu, 23 Oct 2025 14:20:45 +0200 Subject: [PATCH 4/6] Try to only instantiate one type variable bounded function types --- .../src/dotty/tools/dotc/typer/Typer.scala | 40 ++++++++++++++----- tests/pos/infer-function-type-in-union.scala | 12 +++++- 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index e961b69273ee..5cedc6d37ea9 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1929,21 +1929,39 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer NoType } - def tryToInstantiateInUnion(tp: Type): Unit = tp match - case tp: OrType => - tryToInstantiateInUnion(tp.tp1) - tryToInstantiateInUnion(tp.tp2) + /** Try to instantiate one type variable bounded by function types that appear + * deeply inside `tp`, including union or intersection types. + */ + def tryToInstantiateDeeply(tp: Type): Boolean = tp match + case tp: AndOrType => + tryToInstantiateDeeply(tp.tp1) + || tryToInstantiateDeeply(tp.tp2) case tp: FlexibleType => - tryToInstantiateInUnion(tp.hi) - case tp: TypeVar => + tryToInstantiateDeeply(tp.hi) + case tp: TypeVar if isConstrainedByFunctionType(tp) => + // Only instantiate if the type variable is constrained by function types isFullyDefined(tp, ForceDegree.flipBottom) - case _ => + case _ => false + + def isConstrainedByFunctionType(tvar: TypeVar): Boolean = + val origin = tvar.origin + val bounds = ctx.typerState.constraint.bounds(origin) + def containsFunctionType(tp: Type): Boolean = tp.dealias match + case tp if defn.isFunctionType(tp) => true + case SAMType(_, _) => true + case tp: AndOrType => + containsFunctionType(tp.tp1) || containsFunctionType(tp.tp2) + case tp: FlexibleType => + containsFunctionType(tp.hi) + case _ => false + containsFunctionType(bounds.lo) || containsFunctionType(bounds.hi) if untpd.isFunctionWithUnknownParamType(tree) && !calleeType.exists then - // Try to instantiate `pt` when possible, including type variables in union types - // to help finding function types. If it does not work the error will be reported - // later in `inferredParam`, when we try to infer the parameter type. - tryToInstantiateInUnion(pt) + // Try to instantiate `pt` when possible, by searching a nested type variable + // bounded by function types to help infer parameter types. + // If it does not work the error will be reported later in `inferredParam`, + // when we try to infer the parameter type. + tryToInstantiateDeeply(pt) val (protoFormals, resultTpt) = decomposeProtoFunction(pt, params.length, tree.srcPos) diff --git a/tests/pos/infer-function-type-in-union.scala b/tests/pos/infer-function-type-in-union.scala index dcd3edcd8987..f631761b3897 100644 --- a/tests/pos/infer-function-type-in-union.scala +++ b/tests/pos/infer-function-type-in-union.scala @@ -29,4 +29,14 @@ def test = } val o3: MyOption[(String, String) => Boolean] = MyOption { (x, y) => x.length > y.length - } \ No newline at end of file + } + + +class Box[T] +val box: Box[Unit] = ??? +def ff1[T, U](x: T | U, y: Box[U]): T = ??? +def ff2[T, U](x: T & U): T = ??? + +def test2 = + val a1: Any => Any = ff1(x => x, box) + val a2: Any => Any = ff2(x => x) \ No newline at end of file From 048151d09d724facd7efef7b4259fed1b921abb7 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Wed, 29 Oct 2025 14:27:05 +0100 Subject: [PATCH 5/6] Match the single TypeVar case with the previous logic --- compiler/src/dotty/tools/dotc/typer/Typer.scala | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 5cedc6d37ea9..3a09440979a2 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1932,7 +1932,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer /** Try to instantiate one type variable bounded by function types that appear * deeply inside `tp`, including union or intersection types. */ - def tryToInstantiateDeeply(tp: Type): Boolean = tp match + def tryToInstantiateDeeply(tp: Type): Boolean = tp.dealias match case tp: AndOrType => tryToInstantiateDeeply(tp.tp1) || tryToInstantiateDeeply(tp.tp2) @@ -1946,6 +1946,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer def isConstrainedByFunctionType(tvar: TypeVar): Boolean = val origin = tvar.origin val bounds = ctx.typerState.constraint.bounds(origin) + // The search is done by the best-effort, and we don't look into TypeVars recursively. def containsFunctionType(tp: Type): Boolean = tp.dealias match case tp if defn.isFunctionType(tp) => true case SAMType(_, _) => true @@ -1957,11 +1958,15 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer containsFunctionType(bounds.lo) || containsFunctionType(bounds.hi) if untpd.isFunctionWithUnknownParamType(tree) && !calleeType.exists then - // Try to instantiate `pt` when possible, by searching a nested type variable - // bounded by function types to help infer parameter types. + // Try to instantiate `pt` when possible. + // * If `pt` is a type variable, we try to instantiate it directly. + // * If `pt` is a more complex type, we try to instantiate it deeply by searching + // a nested type variable bounded by function types to help infer parameter types. // If it does not work the error will be reported later in `inferredParam`, // when we try to infer the parameter type. - tryToInstantiateDeeply(pt) + pt match + case pt: TypeVar => isFullyDefined(pt, ForceDegree.flipBottom) + case _ => tryToInstantiateDeeply(pt) val (protoFormals, resultTpt) = decomposeProtoFunction(pt, params.length, tree.srcPos) From d71d68278d49cc379369a5add52026509b547d4e Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Sun, 2 Nov 2025 22:23:00 +0100 Subject: [PATCH 6/6] Update compiler/src/dotty/tools/dotc/typer/Typer.scala Co-authored-by: odersky --- compiler/src/dotty/tools/dotc/typer/Typer.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 3a09440979a2..c57ced36f2d8 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1961,7 +1961,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer // Try to instantiate `pt` when possible. // * If `pt` is a type variable, we try to instantiate it directly. // * If `pt` is a more complex type, we try to instantiate it deeply by searching - // a nested type variable bounded by function types to help infer parameter types. + // a nested type variable bounded by a function type to help infer parameter types. // If it does not work the error will be reported later in `inferredParam`, // when we try to infer the parameter type. pt match