From 6e4b0a0bb5659bf82b1cf00dac9c48da2913ceaf Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Tue, 11 Nov 2025 22:57:36 +0100 Subject: [PATCH 1/2] Error instead of crash when generating trees referring to skolems Previously, the valueOf inline call succeeded (because the ValueOf synthesizer calls `tpd.ref` which calls `tpd.singleton`), leading to an invalid tree which crashed in the backend with: "assertion failed: Cannot create ClassBType from NoSymbol". Fixed by throwing a TypeError from `tpd.singleton`, unfortunatley this means tests/neg/i8623.scala gets a worse error message because the implicit search fails early, but arguably this is more correct. --- compiler/src/dotty/tools/dotc/ast/tpd.scala | 3 ++- .../tools/dotc/reporting/ErrorMessageID.scala | 2 +- .../dotty/tools/dotc/reporting/messages.scala | 19 --------------- .../dotty/tools/dotc/typer/Implicits.scala | 14 ----------- .../dotty/tools/dotc/typer/Synthesizer.scala | 9 +++++++- tests/neg/i8623.check | 17 +++++++------- tests/neg/valueOf-skolem.scala | 23 +++++++++++++++++++ 7 files changed, 42 insertions(+), 45 deletions(-) create mode 100644 tests/neg/valueOf-skolem.scala diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index 909387bbb809..ab8a12c3c07d 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -517,9 +517,10 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { def singleton(tp: Type, needLoad: Boolean = true)(using Context): Tree = tp.dealias match { case tp: TermRef => ref(tp, needLoad) case tp: ThisType => This(tp.cls) - case tp: SkolemType => singleton(tp.narrow(), needLoad) case SuperType(qual, _) => singleton(qual, needLoad) case ConstantType(value) => Literal(value) + case tp: SkolemType => + throw TypeError(em"cannot construct a tree referring to skolem $tp") } /** A tree representing a `newXYZArray` operation of the right diff --git a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala index 630adc0c0884..5f5a0c01db17 100644 --- a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala +++ b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala @@ -155,7 +155,7 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe case UnknownNamedEnclosingClassOrObjectID // errorNumber: 139 case IllegalCyclicTypeReferenceID // errorNumber: 140 case MissingTypeParameterInTypeAppID // errorNumber: 141 - case SkolemInInferredID // errorNumber: 142 + case SkolemInInferredID extends ErrorMessageID(isActive = false) // errorNumber: 142 case ErasedTypesCanOnlyBeFunctionTypesID // errorNumber: 143 case CaseClassMissingNonImplicitParamListID // errorNumber: 144 case EnumerationsShouldNotBeEmptyID // errorNumber: 145 diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index c281cbab544e..a2afaab0ecce 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -1353,25 +1353,6 @@ extends CyclicMsg(CyclicReferenceInvolvingImplicitID) { |""" } -class SkolemInInferred(tree: tpd.Tree, pt: Type, argument: tpd.Tree)(using Context) -extends TypeMsg(SkolemInInferredID): - def msg(using Context) = - def argStr = - if argument.isEmpty then "" - else i" from argument of type ${argument.tpe.widen}" - i"""Failure to generate given instance for type $pt$argStr) - | - |I found: $tree - |But the part corresponding to `` is not a reference that can be generated. - |This might be because resolution yielded as given instance a function that is not - |known to be total and side-effect free.""" - def explain(using Context) = - i"""The part of given resolution that corresponds to `` produced a term that - |is not a stable reference. Therefore a given instance could not be generated. - | - |To trouble-shoot the problem, try to supply an explicit expression instead of - |relying on implicit search at this point.""" - class SuperQualMustBeParent(qual: untpd.Ident, cls: ClassSymbol)(using Context) extends ReferenceMsg(SuperQualMustBeParentID) { def msg(using Context) = i"""|$qual does not name a parent of $cls""" diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index d3e3a0d06bd8..8b4e00a11d35 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -1089,18 +1089,6 @@ trait Implicits: val res = implicitArgTree(defn.CanEqualClass.typeRef.appliedTo(ltp, rtp), span) implicits.println(i"CanEqual witness found for $ltp / $rtp: $res: ${res.tpe}") - object hasSkolem extends TreeAccumulator[Boolean]: - def apply(x: Boolean, tree: Tree)(using Context): Boolean = - x || { - tree match - case tree: Ident => tree.symbol.isSkolem - case Select(qual, _) => apply(x, qual) - case Apply(fn, _) => apply(x, fn) - case TypeApply(fn, _) => apply(x, fn) - case _: This => false - case _ => foldOver(x, tree) - } - /** Find an implicit parameter or conversion. * @param pt The expected type of the parameter or conversion. * @param argument If an implicit conversion is searched, the argument to which @@ -1148,8 +1136,6 @@ trait Implicits: result.tstate.commit() if result.gstate ne ctx.gadt then ctx.gadtState.restore(result.gstate) - if hasSkolem(false, result.tree) then - report.error(SkolemInInferred(result.tree, pt, argument), ctx.source.atSpan(span)) implicits.println(i"success: $result") implicits.println(i"committing ${result.tstate.constraint} yielding ${ctx.typerState.constraint} in ${ctx.typerState}") result diff --git a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala index 3b114de6a05c..7bfda4e4b5fa 100644 --- a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala @@ -828,7 +828,14 @@ object Synthesizer: /** Tuple used to store the synthesis result with a list of errors. */ type TreeWithErrors = (Tree, List[String]) - private def withNoErrors(tree: Tree): TreeWithErrors = (tree, List.empty) + + private inline def withNoErrors(inline tree: => Tree): TreeWithErrors = + try + (tree, List.empty) + catch + case tp: TypeError => + withErrors(tp.getMessage) + private def withErrors(errors: String*): TreeWithErrors = (EmptyTree, errors.toList) private val EmptyTreeNoError: TreeWithErrors = withNoErrors(EmptyTree) diff --git a/tests/neg/i8623.check b/tests/neg/i8623.check index 39337a7839d8..aa41250ab1a2 100644 --- a/tests/neg/i8623.check +++ b/tests/neg/i8623.check @@ -1,13 +1,12 @@ --- [E142] Type Error: tests/neg/i8623.scala:11:2 ----------------------------------------------------------------------- +-- [E008] Not Found Error: tests/neg/i8623.scala:11:9 ------------------------------------------------------------------ 11 | unseal.pos // error - | ^^^^^^ - | Failure to generate given instance for type ?{ pos: ? } from argument of type ?1.tasty.Tree) + | ^^^^^^^^^^ + | value pos is not a member of ?1.tasty.Tree. + | Extension methods were tried, but the search failed with: | - | I found: .tasty.pos(unseal(given_QC[Any])) - | But the part corresponding to `` is not a reference that can be generated. - | This might be because resolution yielded as given instance a function that is not - | known to be total and side-effect free. + | cannot construct a tree referring to skolem (?1 : QC) + | + | where: ?1 is an unknown value of type QC + | | | where: ?1 is an unknown value of type QC - | - | longer explanation available when compiling with `-explain` diff --git a/tests/neg/valueOf-skolem.scala b/tests/neg/valueOf-skolem.scala new file mode 100644 index 000000000000..4507315e91da --- /dev/null +++ b/tests/neg/valueOf-skolem.scala @@ -0,0 +1,23 @@ +case class Foo( + aaaa: Int +) + +case class Bar( + foo: Foo, + bar: Bla[foo.aaaa.type] +) + +class Bla[T](using Ev[T]) + +class Ev[T](x: T) +object Ev: + inline given ev: [T] => Ev[T] = + Ev(valueOf[T]) + +object Test: + def test: Unit = + val x = + Bar( + Foo(0), + Bla() // error + ) From 451a4b502a2adfc91d14719e4997883c15d88b38 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Thu, 13 Nov 2025 00:36:44 +0100 Subject: [PATCH 2/2] Slightly improve the error message after the previous commit --- compiler/src/dotty/tools/dotc/ast/tpd.scala | 8 ++++++++ tests/neg/i8623.check | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index ab8a12c3c07d..23dde1139c03 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -480,7 +480,15 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { else if tp.symbol.hasAnnotation(defn.ScalaStaticAnnot) then Ident(tp) else + // Throw an error here if we detect a skolem to improve the error message in tests/neg/i8623.scala + def checkNoSkolemInPrefix(pre: Type): Unit = pre.dealias match + case pre: SkolemType => + throw TypeError(em"cannot construct a tree referring to $tp because of skolem prefix $pre") + case pre: TermRef => checkNoSkolemInPrefix(pre.prefix) + case _ => + val pre = tp.prefix + checkNoSkolemInPrefix(tp) if pre.isSingleton then followOuterLinks(singleton(pre.dealias, needLoad)).select(tp) else val res = Select(TypeTree(pre), tp) diff --git a/tests/neg/i8623.check b/tests/neg/i8623.check index aa41250ab1a2..61c6988c3c37 100644 --- a/tests/neg/i8623.check +++ b/tests/neg/i8623.check @@ -4,7 +4,7 @@ | value pos is not a member of ?1.tasty.Tree. | Extension methods were tried, but the search failed with: | - | cannot construct a tree referring to skolem (?1 : QC) + | cannot construct a tree referring to ?1.tasty.type because of skolem prefix (?1 : QC) | | where: ?1 is an unknown value of type QC |