From 9710c5596e3b70c921ee98e3bfa7cc3de3c8583b Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Tue, 11 Nov 2025 22:57:36 +0100 Subject: [PATCH 1/4] 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 5d6cd7f7018a..2ce475321ee3 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -504,9 +504,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 5228a37bfa93..216d69d1b248 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 0424406d6e7f..3ec686d5c213 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -1351,25 +1351,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 60fbe218d904..4769e5361a1e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -1027,18 +1027,6 @@ trait Implicits: 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 @@ -1086,8 +1074,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 67f2e8b78b4a..77ec1709f961 100644 --- a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala @@ -781,7 +781,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 10c85b56653fcc3b97c0977902898cd1df6dedae Mon Sep 17 00:00:00 2001 From: Tomasz Godzik Date: Tue, 18 Nov 2025 10:49:25 +0100 Subject: [PATCH 2/4] 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. [Cherry-picked 6e4b0a0bb5659bf82b1cf00dac9c48da2913ceaf][modified] From f63e6ea2e7dda0fc82bf54d960c230dd14551c5d Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Thu, 13 Nov 2025 00:36:44 +0100 Subject: [PATCH 3/4] Slightly improve the error message after the previous commit --- compiler/src/dotty/tools/dotc/ast/tpd.scala | 10 +++++++++- tests/neg/i8623.check | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index 2ce475321ee3..5318904417de 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -467,8 +467,16 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { else if (tp.symbol hasAnnotation defn.ScalaStaticAnnot) 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 - if (pre.isSingleton) followOuterLinks(singleton(pre.dealias, needLoad)).select(tp) + checkNoSkolemInPrefix(tp) + if pre.isSingleton then followOuterLinks(singleton(pre.dealias, needLoad)).select(tp) else val res = Select(TypeTree(pre), tp) if needLoad && !res.symbol.isStatic then 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 | From 9761d637ff70f8a488841e5e5677624b590e861d Mon Sep 17 00:00:00 2001 From: Tomasz Godzik Date: Tue, 18 Nov 2025 10:50:08 +0100 Subject: [PATCH 4/4] Slightly improve the error message after the previous commit [Cherry-picked 451a4b502a2adfc91d14719e4997883c15d88b38][modified]