From 982f21a0cec8bce40ed4443ee4c8a98c6d258bec Mon Sep 17 00:00:00 2001 From: odersky Date: Fri, 11 Jul 2025 13:45:26 +0200 Subject: [PATCH 1/3] Guard against invalid prefixes in argForParam Fixes #23504 --- compiler/src/dotty/tools/dotc/core/Types.scala | 5 ++++- tests/neg/i23504.scala | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 tests/neg/i23504.scala diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 61b3b958fca3..94043f940654 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -2736,7 +2736,10 @@ object Types extends TypeUtils { while (tparams.nonEmpty && args.nonEmpty) { if (tparams.head.eq(tparam)) return args.head match { - case _: TypeBounds if !widenAbstract => TypeRef(pre, tparam) + case _: TypeBounds if !widenAbstract => + if !NamedType.validPrefix(pre) then + throw TypeError(em"invalid prefix $pre cannot replace parameter $tparam in result of selection") + TypeRef(pre, tparam) case arg => arg } tparams = tparams.tail diff --git a/tests/neg/i23504.scala b/tests/neg/i23504.scala new file mode 100644 index 000000000000..9d79b074a4da --- /dev/null +++ b/tests/neg/i23504.scala @@ -0,0 +1,2 @@ +def test = + Seq.empty[[T] =>> () => ?].head() // error \ No newline at end of file From 40e076d228fb37c8d7fea23a7fb832e7586affc7 Mon Sep 17 00:00:00 2001 From: odersky Date: Fri, 11 Jul 2025 17:43:42 +0200 Subject: [PATCH 2/3] Add failing test to exclude list We should come back to this and allow this test by catching the TypeError and reporting it. --- compiler/test/dotc/neg-best-effort-unpickling.excludelist | 3 +++ 1 file changed, 3 insertions(+) diff --git a/compiler/test/dotc/neg-best-effort-unpickling.excludelist b/compiler/test/dotc/neg-best-effort-unpickling.excludelist index 3c33aaaaac4c..bde14caa671d 100644 --- a/compiler/test/dotc/neg-best-effort-unpickling.excludelist +++ b/compiler/test/dotc/neg-best-effort-unpickling.excludelist @@ -24,3 +24,6 @@ context-function-syntax.scala # Failure to disambiguate overloaded reference i23402b.scala + +# Unhandled TypeError exception +i23504.scala From 4a959b1181f4a17d9722fbe0fda010da435e9cf4 Mon Sep 17 00:00:00 2001 From: odersky Date: Sat, 12 Jul 2025 13:05:54 +0200 Subject: [PATCH 3/3] More systematic handling of invalid prefixes It looks like there are many situations where an illegal higher-kinded type in an argument for a value type parameter causes illegal TermRefs and TypeRefs to be constructed, leading to an assertion error. We now turn the assertion error into a specialized exception which eventually leads to a TypeError being thrown. The problem is we cannot detect illegal kinds in arguments early enough to prevent these situations. We do detect them later, but the damage can already be done before that. --- compiler/src/dotty/tools/dotc/core/Types.scala | 9 ++++----- compiler/src/dotty/tools/dotc/core/Uniques.scala | 15 +++++++++++++-- tests/neg/i23504.scala | 3 ++- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 94043f940654..1e5a4597f23e 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -2302,7 +2302,7 @@ object Types extends TypeUtils { def _1: Type def _2: Designator - assert(NamedType.validPrefix(prefix), s"invalid prefix $prefix") + if !NamedType.validPrefix(prefix) then throw InvalidPrefix() private var myName: Name | Null = null private var lastDenotation: Denotation | Null = null @@ -2736,10 +2736,7 @@ object Types extends TypeUtils { while (tparams.nonEmpty && args.nonEmpty) { if (tparams.head.eq(tparam)) return args.head match { - case _: TypeBounds if !widenAbstract => - if !NamedType.validPrefix(pre) then - throw TypeError(em"invalid prefix $pre cannot replace parameter $tparam in result of selection") - TypeRef(pre, tparam) + case _: TypeBounds if !widenAbstract => TypeRef(pre, tparam) case arg => arg } tparams = tparams.tail @@ -3070,6 +3067,8 @@ object Types extends TypeUtils { apply(prefix, designatorFor(prefix, name, denot)).withDenot(denot) } + class InvalidPrefix extends Exception + // --- Other SingletonTypes: ThisType/SuperType/ConstantType --------------------------- /** The type cls.this diff --git a/compiler/src/dotty/tools/dotc/core/Uniques.scala b/compiler/src/dotty/tools/dotc/core/Uniques.scala index da6b0aba88bd..b50905c22c98 100644 --- a/compiler/src/dotty/tools/dotc/core/Uniques.scala +++ b/compiler/src/dotty/tools/dotc/core/Uniques.scala @@ -3,6 +3,7 @@ package core import Types.*, Contexts.*, util.Stats.*, Hashable.*, Names.* import config.Config +import Symbols.Symbol import Decorators.* import util.{WeakHashSet, Stats} import WeakHashSet.Entry @@ -41,8 +42,10 @@ object Uniques: val h = doHash(null, designator, prefix) if monitored then recordCaching(h, classOf[NamedType]) def newType = - if (isTerm) new CachedTermRef(prefix, designator, h) - else new CachedTypeRef(prefix, designator, h) + try + if isTerm then new CachedTermRef(prefix, designator, h) + else new CachedTypeRef(prefix, designator, h) + catch case ex: InvalidPrefix => badPrefix(prefix, designator) if h == NotCached then newType else // Inlined from WeakHashSet#put @@ -61,6 +64,14 @@ object Uniques: linkedListLoop(oldHead) end if + end enterIfNew + + private def badPrefix(prefix: Type, desig: Designator)(using Context): Nothing = + def name = desig match + case desig: Name => desig + case desig: Symbol => desig.name + throw TypeError(em"invalid prefix $prefix when trying to form $prefix . $name") + end NamedTypeUniques final class AppliedUniques extends WeakHashSet[AppliedType](Config.initialUniquesCapacity * 2) with Hashable: diff --git a/tests/neg/i23504.scala b/tests/neg/i23504.scala index 9d79b074a4da..e53337eaa105 100644 --- a/tests/neg/i23504.scala +++ b/tests/neg/i23504.scala @@ -1,2 +1,3 @@ def test = - Seq.empty[[T] =>> () => ?].head() // error \ No newline at end of file + Seq.empty[[T] =>> () => ?].head() // error + Seq.empty[[T] =>> Int => Int].head(1) // error \ No newline at end of file