From 19af0d2baf47a3e8b49c3636ae7488867ae3f867 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Thu, 10 Jul 2025 07:55:45 -0700 Subject: [PATCH 1/2] Tweak syntax and use vars --- .../dotty/tools/dotc/typer/Applications.scala | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 0bcf2f5cce69..62fad75c5868 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1570,20 +1570,17 @@ trait Applications extends Compatibility { def trySelectUnapply(qual: untpd.Tree)(fallBack: (Tree, TyperState) => Tree): Tree = { // try first for non-overloaded, then for overloaded occurrences def tryWithName(name: TermName)(fallBack: (Tree, TyperState) => Tree)(using Context): Tree = - /** Returns `true` if there are type parameters after the last explicit - * (non-implicit) term parameters list. - */ - @tailrec - def hasTrailingTypeParams(paramss: List[List[Symbol]], acc: Boolean = false): Boolean = + // `true` if there are type parameters not followed by explicit term parameters. + def hasTrailingTypeParams(paramss: List[List[Symbol]], trailingTypeParams: Boolean): Boolean = paramss match - case Nil => acc - case params :: rest => - val newAcc = - params match - case param :: _ if param.isType => true - case param :: _ if param.isTerm && !param.isOneOf(GivenOrImplicit) => false - case _ => acc - hasTrailingTypeParams(paramss.tail, newAcc) + case params :: paramss => + val isTrailingTypeParams = + params match + case param :: _ if param.isType => true + case param :: _ if param.isTerm && !param.isOneOf(GivenOrImplicit) => false + case _ => trailingTypeParams + hasTrailingTypeParams(paramss, isTrailingTypeParams) + case nil => trailingTypeParams def tryWithProto(qual: untpd.Tree, targs: List[Tree], pt: Type)(using Context) = val proto = UnapplyFunProto(pt, this) @@ -1591,7 +1588,7 @@ trait Applications extends Compatibility { val result = if targs.isEmpty then typedExpr(unapp, proto) else typedExpr(unapp, PolyProto(targs, proto)).appliedToTypeTrees(targs) - if result.symbol.exists && hasTrailingTypeParams(result.symbol.paramSymss) then + if result.symbol.exists && hasTrailingTypeParams(result.symbol.paramSymss, trailingTypeParams = false) then // We don't accept `unapply` or `unapplySeq` methods with type // parameters after the last explicit term parameter because we // can't encode them: `UnApply` nodes cannot take type paremeters. From 17324cc8a7c3dc36437f172f8650bb23bd1620d0 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Thu, 10 Jul 2025 08:33:13 -0700 Subject: [PATCH 2/2] Tweak check to fail if type param follows explicit param --- .../dotty/tools/dotc/typer/Applications.scala | 23 +++++++-------- tests/pos/i23499.scala | 28 +++++++++++++++++++ 2 files changed, 40 insertions(+), 11 deletions(-) create mode 100644 tests/pos/i23499.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 62fad75c5868..a4257bd89c3e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1570,17 +1570,18 @@ trait Applications extends Compatibility { def trySelectUnapply(qual: untpd.Tree)(fallBack: (Tree, TyperState) => Tree): Tree = { // try first for non-overloaded, then for overloaded occurrences def tryWithName(name: TermName)(fallBack: (Tree, TyperState) => Tree)(using Context): Tree = - // `true` if there are type parameters not followed by explicit term parameters. - def hasTrailingTypeParams(paramss: List[List[Symbol]], trailingTypeParams: Boolean): Boolean = - paramss match - case params :: paramss => - val isTrailingTypeParams = + // `true` if there are no type parameters following the first explicit term parameter. + def hasOnlyLeadingTypeParams(fun: Symbol): Boolean = + def checkForIt(paramss: List[List[Symbol]], leading: Int): Boolean = + inline def isLeading = leading > 0 + paramss match + case params :: paramss => params match - case param :: _ if param.isType => true - case param :: _ if param.isTerm && !param.isOneOf(GivenOrImplicit) => false - case _ => trailingTypeParams - hasTrailingTypeParams(paramss, isTrailingTypeParams) - case nil => trailingTypeParams + case param :: _ if param.isType && !isLeading => false + case param :: _ if param.isTerm && !param.isOneOf(GivenOrImplicit) => checkForIt(paramss, leading - 1) + case _ => checkForIt(paramss, leading) + case nil => true + checkForIt(fun.paramSymss, leading = if fun.is(Extension) then 2 else 1) def tryWithProto(qual: untpd.Tree, targs: List[Tree], pt: Type)(using Context) = val proto = UnapplyFunProto(pt, this) @@ -1588,7 +1589,7 @@ trait Applications extends Compatibility { val result = if targs.isEmpty then typedExpr(unapp, proto) else typedExpr(unapp, PolyProto(targs, proto)).appliedToTypeTrees(targs) - if result.symbol.exists && hasTrailingTypeParams(result.symbol.paramSymss, trailingTypeParams = false) then + if result.symbol.exists && !hasOnlyLeadingTypeParams(result.symbol) then // We don't accept `unapply` or `unapplySeq` methods with type // parameters after the last explicit term parameter because we // can't encode them: `UnApply` nodes cannot take type paremeters. diff --git a/tests/pos/i23499.scala b/tests/pos/i23499.scala new file mode 100644 index 000000000000..bcab0960091c --- /dev/null +++ b/tests/pos/i23499.scala @@ -0,0 +1,28 @@ + + +def summonsTest = + given Type[String] = ??? + val opt1: Option[Wrapper[String]] = Wrapper.unapply[String] // ok + val opt2 = Wrapper.unapply[String] + opt2.map(_.getValue) // only error when using workaround + +def patternMatchTest = + Type[String] match + case Wrapper(v) => v.getValue // was error // was error + +type Type[A] = Class[A] // any rhs would work here +object Type: + def apply[A]: Type[A] = ??? + +trait Wrapper[T]: + def getValue: T = ??? +object Wrapper: + def unapply[T](implicit ev: Type[T]): Option[Wrapper[T]] = None + + /* Workaround: + @annotation.targetName("unapplyValue") + def unapply[T](ev: Type[T]): Option[Wrapper[T]] = unapply(using ev) + + @annotation.targetName("unapplySummon") + def unapply[T](using Type[T]): Option[Wrapper[T]] = ??? + */