Skip to content

Commit 0c29629

Browse files
oderskyWojciechMazur
authored andcommitted
Re-use isConcrete checking in match types for NamedTyple.From
- Move isConcrete to a new object `MatchTypes`. We should also move other MatchType-related stuff from Types and TypeComparer here. Type and TypeComparer are already unconfortably big, and MatchTypes are a coherent topic where everything should work together. - Streamline isConcrete a bit. - Re-use isConcrete for a similar test in CheckRealizable. - Re-use isConcrete for evaluating NamedTuple.From Fixes #20517 [Cherry-picked 532a9da]
1 parent 72b1ab2 commit 0c29629

File tree

6 files changed

+86
-62
lines changed

6 files changed

+86
-62
lines changed

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

+1-9
Original file line numberDiff line numberDiff line change
@@ -116,15 +116,7 @@ class CheckRealizable(using Context) {
116116
case _: SingletonType | NoPrefix =>
117117
Realizable
118118
case tp =>
119-
def isConcrete(tp: Type): Boolean = tp.dealias match {
120-
case tp: TypeRef => tp.symbol.isClass
121-
case tp: TypeParamRef => false
122-
case tp: TypeProxy => isConcrete(tp.underlying)
123-
case tp: AndType => isConcrete(tp.tp1) && isConcrete(tp.tp2)
124-
case tp: OrType => isConcrete(tp.tp1) && isConcrete(tp.tp2)
125-
case _ => false
126-
}
127-
if (!isConcrete(tp)) NotConcrete
119+
if !MatchTypes.isConcrete(tp) then NotConcrete
128120
else boundsRealizability(tp).andAlso(memberRealizability(tp))
129121
}
130122

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package dotty.tools
2+
package dotc
3+
package core
4+
5+
import Types.*, Contexts.*, Symbols.*, Flags.*, Decorators.*
6+
7+
object MatchTypes:
8+
9+
/* Concreteness checking
10+
*
11+
* When following a baseType and reaching a non-wildcard, in-variant-pos type capture,
12+
* we have to make sure that the scrutinee is concrete enough to uniquely determine
13+
* the values of the captures. This comes down to checking that we do not follow any
14+
* upper bound of an abstract type.
15+
*
16+
* See notably neg/wildcard-match.scala for examples of this.
17+
*
18+
* See neg/i13780.scala, neg/i13780-1.scala and neg/i19746.scala for
19+
* ClassCastException reproducers if we disable this check.
20+
*/
21+
def isConcrete(tp: Type)(using Context): Boolean =
22+
val tp1 = tp.normalized
23+
24+
tp1 match
25+
case tp1: TypeRef =>
26+
if tp1.symbol.isClass then true
27+
else
28+
tp1.info match
29+
case info: AliasingBounds => isConcrete(info.alias)
30+
case _ => false
31+
case tp1: AppliedType =>
32+
isConcrete(tp1.tycon) && isConcrete(tp1.superType)
33+
case tp1: HKTypeLambda =>
34+
true
35+
case tp1: TermRef =>
36+
!tp1.symbol.is(Param) && isConcrete(tp1.underlying)
37+
case tp1: TermParamRef =>
38+
false
39+
case tp1: SingletonType =>
40+
isConcrete(tp1.underlying)
41+
case tp1: ExprType =>
42+
isConcrete(tp1.underlying)
43+
case tp1: AnnotatedType =>
44+
isConcrete(tp1.parent)
45+
case tp1: RefinedOrRecType =>
46+
isConcrete(tp1.underlying)
47+
case tp1: AndOrType =>
48+
isConcrete(tp1.tp1) && isConcrete(tp1.tp2)
49+
case tp1: TypeVar =>
50+
isConcrete(tp1.underlying)
51+
case tp1: LazyRef =>
52+
isConcrete(tp1.ref)
53+
case tp1: FlexibleType =>
54+
isConcrete(tp1.hi)
55+
case _ =>
56+
false
57+
end isConcrete
58+
59+
end MatchTypes

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

+1-52
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import reporting.trace
2424
import annotation.constructorOnly
2525
import cc.*
2626
import NameKinds.WildcardParamName
27+
import MatchTypes.isConcrete
2728

2829
/** Provides methods to compare types.
2930
*/
@@ -3402,58 +3403,6 @@ class MatchReducer(initctx: Context) extends TypeComparer(initctx) {
34023403

34033404
// See https://docs.scala-lang.org/sips/match-types-spec.html#matching
34043405
def matchSpeccedPatMat(spec: MatchTypeCaseSpec.SpeccedPatMat): MatchResult =
3405-
/* Concreteness checking
3406-
*
3407-
* When following a baseType and reaching a non-wildcard, in-variant-pos type capture,
3408-
* we have to make sure that the scrutinee is concrete enough to uniquely determine
3409-
* the values of the captures. This comes down to checking that we do not follow any
3410-
* upper bound of an abstract type.
3411-
*
3412-
* See notably neg/wildcard-match.scala for examples of this.
3413-
*
3414-
* See neg/i13780.scala, neg/i13780-1.scala and neg/i19746.scala for
3415-
* ClassCastException reproducers if we disable this check.
3416-
*/
3417-
3418-
def isConcrete(tp: Type): Boolean =
3419-
val tp1 = tp.normalized
3420-
3421-
tp1 match
3422-
case tp1: TypeRef =>
3423-
if tp1.symbol.isClass then true
3424-
else
3425-
tp1.info match
3426-
case info: AliasingBounds => isConcrete(info.alias)
3427-
case _ => false
3428-
case tp1: AppliedType =>
3429-
isConcrete(tp1.tycon) && isConcrete(tp1.superType)
3430-
case tp1: HKTypeLambda =>
3431-
true
3432-
case tp1: TermRef =>
3433-
!tp1.symbol.is(Param) && isConcrete(tp1.underlying)
3434-
case tp1: TermParamRef =>
3435-
false
3436-
case tp1: SingletonType =>
3437-
isConcrete(tp1.underlying)
3438-
case tp1: ExprType =>
3439-
isConcrete(tp1.underlying)
3440-
case tp1: AnnotatedType =>
3441-
isConcrete(tp1.parent)
3442-
case tp1: RefinedType =>
3443-
isConcrete(tp1.underlying)
3444-
case tp1: RecType =>
3445-
isConcrete(tp1.underlying)
3446-
case tp1: AndOrType =>
3447-
isConcrete(tp1.tp1) && isConcrete(tp1.tp2)
3448-
case tp1: FlexibleType =>
3449-
isConcrete(tp1.hi)
3450-
case _ =>
3451-
val tp2 = tp1.stripped.stripLazyRef
3452-
(tp2 ne tp) && isConcrete(tp2)
3453-
end isConcrete
3454-
3455-
// Actual matching logic
3456-
34573406
val instances = Array.fill[Type](spec.captureCount)(NoType)
34583407
val noInstances = mutable.ListBuffer.empty[(TypeName, TypeBounds)]
34593408

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ object TypeEval:
101101
expectArgsNum(1)
102102
val arg = tp.args.head
103103
val cls = arg.classSymbol
104-
if cls.is(CaseClass) then
104+
if MatchTypes.isConcrete(arg) && cls.is(CaseClass) then
105105
val fields = cls.caseAccessors
106106
val fieldLabels = fields.map: field =>
107107
ConstantType(Constant(field.name.toString))

tests/neg/i20517.check

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
-- [E007] Type Mismatch Error: tests/neg/i20517.scala:10:43 ------------------------------------------------------------
2+
10 | def dep(foo: Foo[Any]): From[foo.type] = (elem = "") // error
3+
| ^^^^^^^^^^^
4+
| Found: (elem : String)
5+
| Required: NamedTuple.From[(foo : Foo[Any])]
6+
|
7+
| longer explanation available when compiling with `-explain`

tests/neg/i20517.scala

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import scala.language.experimental.namedTuples
2+
import NamedTuple.From
3+
4+
case class Foo[+T](elem: T)
5+
6+
trait Base[M[_]]:
7+
def dep(foo: Foo[Any]): M[foo.type]
8+
9+
class SubAny extends Base[From]:
10+
def dep(foo: Foo[Any]): From[foo.type] = (elem = "") // error
11+
12+
object Test:
13+
@main def run =
14+
val f: Foo[Int] = Foo(elem = 1)
15+
val b: Base[From] = SubAny()
16+
val nt: (elem: Int) = b.dep(f)
17+
val x: Int = nt.elem // was ClassCastException

0 commit comments

Comments
 (0)