From cb91472f9647fc3d7b140f1a8bc1f9efddef6859 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Sat, 16 Mar 2024 16:40:23 +0000 Subject: [PATCH 1/2] Fix eta-expanding guards in adaptType --- .../src/dotty/tools/dotc/typer/Typer.scala | 11 ++++++++- tests/pos/i19942.scala | 23 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 tests/pos/i19942.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 47bd25e5d45e..bc7fa3438374 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -4281,7 +4281,16 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer def adaptType(tp: Type): Tree = { val tree1 = - if ((pt eq AnyTypeConstructorProto) || tp.typeParamSymbols.isEmpty) tree + if pt eq AnyTypeConstructorProto then tree + else if tp.typeParamSymbols.isEmpty || tp.typeParamSymbols.isEmpty then + // call typeParamSymbols twice, to get the stable results + // (see also note inside typeParamSymbols) + // given `type LifecycleF = [_] =>> Any` in pos/i19942.scala + // with an Ident of LifecycleF, calling typeParams will return: + // 1. [type _] a list of the symbol _ in the type def tree, on the first call + // 2. [+_] a list of a lambda param, afterwards + // we only want to eta-expand if there are real type param symbols, so we check twice + tree else { if (ctx.isJava) // Cook raw type diff --git a/tests/pos/i19942.scala b/tests/pos/i19942.scala new file mode 100644 index 000000000000..9be97f635df4 --- /dev/null +++ b/tests/pos/i19942.scala @@ -0,0 +1,23 @@ +type LifecycleF = [_] =>> Any +trait Lifecycle[+F[_], +A] + +trait LifecycleTag[R] +object LifecycleTag: + implicit def resourceTag[R <: Lifecycle[F0, A0], F0[_], A0]: LifecycleTag[R] = ??? + +trait MakeDSL[T]: + final def fromResource[R <: Lifecycle[LifecycleF, T]](implicit tag: LifecycleTag[R]): Any = ??? + +object distage: + // Cannot be defined in the same scope as rest of code + final type Identity[+A] = A +import distage.* + +trait Resource +trait DependentResource() extends Lifecycle[Identity, Resource] + +@main def Test = { + val dsl: MakeDSL[Resource] = ??? + val fails = dsl.fromResource[DependentResource] + val works = dsl.fromResource[DependentResource](using LifecycleTag.resourceTag[DependentResource, Identity, Resource]) +} From 19d0ecb8a1e42858859f45586e46e51d0e0a08fb Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Sat, 16 Mar 2024 16:46:46 +0000 Subject: [PATCH 2/2] Add second test case from comment 1 --- tests/pos/i19942.1.scala | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 tests/pos/i19942.1.scala diff --git a/tests/pos/i19942.1.scala b/tests/pos/i19942.1.scala new file mode 100644 index 000000000000..20f923886089 --- /dev/null +++ b/tests/pos/i19942.1.scala @@ -0,0 +1,29 @@ +trait Alternative[F[_]] + +opaque type Derived[A] = A +object Derived: + extension [A](derived: Derived[A]) def instance: A = derived + infix type <<<[F[_], G[_]] = [x] =>> F[G[x]] + +import Derived.* +import scala.compiletime.summonInline + +type DerivedAlternative[F[_]] = Derived[Alternative[F]] +object DerivedAlternative: + inline def apply[F[_]]: Alternative[F] = + import DerivedAlternative.given + summonInline[DerivedAlternative[F]].instance + given nested[F[_], G[_]]: DerivedAlternative[F <<< G] = ??? + +object auto: + object alternative: + transparent inline given [F[_]]: Alternative[F] = DerivedAlternative[F] + +trait Test: + import Test.* + import auto.alternative.given + val fails = summon[Alternative[OptList]] + +// Fails if companion object defined AFTER trait +object Test: + type OptList[A] = Option[List[A]]