Skip to content

Commit 0c97e3e

Browse files
committed
Implement safe widening of singleton types for abstract type constructors
1 parent ce57b71 commit 0c97e3e

File tree

5 files changed

+39
-1
lines changed

5 files changed

+39
-1
lines changed

compiler/src/dotty/tools/dotc/core/TypeComparer.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3716,7 +3716,7 @@ class MatchReducer(initctx: Context) extends TypeComparer(initctx) {
37163716
false
37173717

37183718
case MatchTypeCasePattern.AbstractTypeConstructor(tycon, argPatterns) =>
3719-
scrut.dealias match
3719+
scrut.dealias.widenSingletonNonParamRefs match
37203720
case scrutDealias @ AppliedType(scrutTycon, args) if scrutTycon =:= tycon =>
37213721
matchArgs(argPatterns, args, tycon.typeParams, scrutIsWidenedAbstract)
37223722
case _ =>

compiler/src/dotty/tools/dotc/core/Types.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1400,6 +1400,18 @@ object Types extends TypeUtils {
14001400
case _ => this
14011401
}
14021402

1403+
/** Widen singleton types that are safe to widen (not parameters)
1404+
* to allow matching against abstract type constructors.
1405+
* See issue #20453
1406+
*/
1407+
final def widenSingletonNonParamRefs(using Context): Type = stripped match {
1408+
case tp: TermRef if !tp.symbol.is(TypeParam) =>
1409+
val denot = tp.denot
1410+
if denot.isOverloaded then this else denot.info.widenSingletonNonParamRefs
1411+
case tp: SingletonType => tp.underlying.widenSingletonNonParamRefs
1412+
case _ => this
1413+
}
1414+
14031415
/** Widen from TermRef to its underlying non-termref
14041416
* base type, while also skipping Expr types.
14051417
*/

tests/neg/i20453.check

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
-- [E007] Type Mismatch Error: tests/neg/i20453.scala:7:4 --------------------------------------------------------------
2+
7 | ("name", "age") // error
3+
| ^^^^^^^^^^^^^^^
4+
| Found: (String, String)
5+
| Required: NamedTupleDecomposition.Names[(f : (name : String, age : Int))]
6+
|
7+
| Note: a match type could not be fully reduced:
8+
|
9+
| trying to reduce NamedTupleDecomposition.Names[(f : (name : String, age : Int))]
10+
| failed since selector (f : (name : String, age : Int))
11+
| does not match case NamedTuple.NamedTuple[n, _] => n
12+
| and cannot be shown to be disjoint from it either.
13+
|
14+
| longer explanation available when compiling with `-explain`

tests/neg/i20453.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import scala.language.experimental.namedTuples
2+
3+
// Ensure we don't widen parameter singleton types
4+
// which would cause unsoundness similar to #19746
5+
object Test:
6+
def foo[T](f: (name: String, age: Int)): NamedTupleDecomposition.Names[f.type] =
7+
("name", "age") // error

tests/pos/i20453.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import scala.language.experimental.namedTuples
2+
3+
object Test:
4+
val f: (name: String, age: Int) = ???
5+
val x: NamedTupleDecomposition.Names[f.type] = ("name", "age")

0 commit comments

Comments
 (0)