From cf520a05d36145b1617e099167c5129cc22a527f Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Sat, 16 Mar 2024 16:40:23 +0000 Subject: [PATCH 1/3] 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 2ef48d9cff10372e42914d6f7ad530119954da58 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Sat, 16 Mar 2024 16:46:46 +0000 Subject: [PATCH 2/3] 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]] From c65bdb30eb10074c06c4823d0414b50fb84e36d2 Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 18 Mar 2024 12:56:21 +0100 Subject: [PATCH 3/3] Move retry logic to one special case in typeParams --- .../src/dotty/tools/dotc/core/TypeApplications.scala | 12 +++++++++++- compiler/src/dotty/tools/dotc/typer/Typer.scala | 11 +---------- .../quote-type-variable-no-inference-3.check | 4 ++-- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeApplications.scala b/compiler/src/dotty/tools/dotc/core/TypeApplications.scala index 223d3bb11515..eeb18eaa9cc7 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeApplications.scala @@ -182,7 +182,17 @@ class TypeApplications(val self: Type) extends AnyVal { val tsym = self.symbol if (tsym.isClass) tsym.typeParams else tsym.infoOrCompleter match { - case info: LazyType if isTrivial(self.prefix, tsym) => info.completerTypeParams(tsym) + case info: LazyType if isTrivial(self.prefix, tsym) => + val tparams = info.completerTypeParams(tsym) + if tsym.isCompleted then tsym.info.typeParams + // Completers sometimes represent parameters as symbols where + // the completed type represents them as paramrefs. Make sure we get + // a stable result by calling `typeParams` recursively. Test case + // is pos/i19942.scala, where parameter F0 has initially a Namer#TypeDefCompleter. + // After calling its completerTypeParams, we get a list of parameter symbols + // and as a side effect F0 is completed. Calling typeParams on the completed + // type gives a list of paramrefs. + else tparams case _ => self.info.typeParams } case self: AppliedType => diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index bc7fa3438374..e6fd7c5abcbb 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -4281,16 +4281,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer def adaptType(tp: Type): Tree = { val tree1 = - 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 + if (pt eq AnyTypeConstructorProto) || tp.typeParamSymbols.isEmpty then tree else { if (ctx.isJava) // Cook raw type diff --git a/tests/neg-macros/quote-type-variable-no-inference-3.check b/tests/neg-macros/quote-type-variable-no-inference-3.check index 91476728654d..e98900069740 100644 --- a/tests/neg-macros/quote-type-variable-no-inference-3.check +++ b/tests/neg-macros/quote-type-variable-no-inference-3.check @@ -1,10 +1,10 @@ -- Warning: tests/neg-macros/quote-type-variable-no-inference-3.scala:5:22 --------------------------------------------- 5 | case '{ $_ : F[t, t]; () } => // warn // error | ^ - | Ignored bound <: Comparable[U] + | Ignored bound <: Comparable[Any] | | Consider defining bounds explicitly: - | '{ type t <: Comparable[U]; ... } + | '{ type t <: Comparable[Any]; ... } -- Warning: tests/neg-macros/quote-type-variable-no-inference-3.scala:6:49 --------------------------------------------- 6 | case '{ type u <: Comparable[`u`]; $_ : F[u, u] } => | ^