Skip to content

Commit ef74d39

Browse files
committed
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
1 parent 0e60d36 commit ef74d39

File tree

6 files changed

+86
-62
lines changed

6 files changed

+86
-62
lines changed

Diff for: 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

Diff for: compiler/src/dotty/tools/dotc/core/MatchTypes.scala

+59
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

Diff for: 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
*/
@@ -3409,58 +3410,6 @@ class MatchReducer(initctx: Context) extends TypeComparer(initctx) {
34093410

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

Diff for: 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))

Diff for: 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`

Diff for: 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)